patch 9.2.0017: popup: cannot allow to specify transparency
Commit:
https://github.com/vim/vim/commit/95e8faa24f944b9f395089c38948aef9722750ec
Author: Yasuhiro Matsumoto <
matt...@gmail.com>
Date: Wed Feb 18 17:58:54 2026 +0000
patch 9.2.0017: popup: cannot allow to specify transparency
Problem: popup: Popup windows do not support a transparency setting.
Solution: Add the "opacity" option to popup windows to support
transparency when using the GUI or 'termguicolors'
(Yasuhiro Matsumoto).
closes: #19272
Signed-off-by: Yasuhiro Matsumoto <
matt...@gmail.com>
Signed-off-by: Christian Brabandt <
c...@256bit.org>
diff --git a/runtime/doc/popup.txt b/runtime/doc/popup.txt
index 5f7d7ab53..021410eb2 100644
--- a/runtime/doc/popup.txt
+++ b/runtime/doc/popup.txt
@@ -1,4 +1,4 @@
-*popup.txt* For Vim version 9.2. Last change: 2026 Feb 14
+*popup.txt* For Vim version 9.2. Last change: 2026 Feb 18
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -21,6 +21,7 @@ Displaying text in a popup window. *popup* *popup-window* *popupwin*
Popup filter |popup-filter|
Popup callback |popup-callback|
Popup scrollbar |popup-scrollbar|
+ Popup opacity |popup-opacity|
Popup mask |popup-mask|
4. Examples |popup-examples|
@@ -772,6 +773,13 @@ The second argument of |popup_create()| is a dictionary with options:
the popup window.
highlight Highlight group name to use for the text, stored in
the 'wincolor' option.
+ opacity Opacity of the popup, a value between 0 and 100:
+ 0 is fully transparent (background text fully visible)
+ 100 is fully opaque (default, no transparency)
+ Values in between blend the popup background with the
+ underlying text, making it partially transparent.
+ Requires 'termguicolors' to be set.
+ Also see |popup-opacity|.
padding List with numbers, defining the padding
above/right/below/left of the popup (similar to CSS).
An empty list uses a padding of 1 all around. The
@@ -1046,6 +1054,42 @@ A click in the lower half will scroll the text up one line. However, this is
limited so that the popup does not get smaller.
+
+POPUP OPACITY *popup-opacity*
+
+A popup window can be made semi-transparent by setting the "opacity" option.
+The opacity value ranges from 0 to 100:
+ 0 Fully transparent - the popup background is invisible and the
+ text behind the popup is fully visible.
+ 100 Fully opaque (default) - the popup is not transparent at all.
+ 1-99 Partially transparent - the popup background is blended with
+ the underlying text, making both partially visible.
+
+The transparency effect requires using the GUI or having 'termguicolors'
+enabled in the terminal. Without it, the opacity setting has no effect.
+
+When a popup is transparent:
+- The popup's background color is blended with the background text
+- The popup's text (foreground) remains fully visible and unblended
+- Text behind the popup is visible through transparent areas
+- The more transparent the popup (lower opacity), the more clearly the
+ background text can be seen
+
+This can be useful for:
+- Creating overlay windows that don't completely obscure underlying text
+- Showing contextual information without blocking the view
+- Creating visual effects and modern UI designs
+
+Example with 50% opacity: >
+ let winid = popup_create('Semi-transparent text', #{
+ \ line: 5,
+ \ col: 10,
+ \ opacity: 50,
+ \ })
+
+The opacity can be changed dynamically using |popup_setoptions()|: >
+ call popup_setoptions(winid, #{opacity: 80})
+
POPUP MASK *popup-mask*
To minimize the text that the popup covers, parts of it can be made
diff --git a/runtime/doc/tags b/runtime/doc/tags
index b041d00a3..2c7d10766 100644
--- a/runtime/doc/tags
+++ b/runtime/doc/tags
@@ -9771,6 +9771,7 @@ popup-mapping popup.txt /*popup-mapping*
popup-mask popup.txt /*popup-mask*
popup-menu gui.txt /*popup-menu*
popup-menu-added version5.txt /*popup-menu-added*
+popup-opacity popup.txt /*popup-opacity*
popup-position popup.txt /*popup-position*
popup-props popup.txt /*popup-props*
popup-scrollbar popup.txt /*popup-scrollbar*
diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt
index d3362f6c8..09a3c8c16 100644
--- a/runtime/doc/version9.txt
+++ b/runtime/doc/version9.txt
@@ -52582,6 +52582,10 @@ VERSION 9.3 *version-9.3* *version9.3* *vim-9.3*
This section is about improvements made between version 9.2 and 9.3 and is
work in progress.
+Popups ~
+------
+- Support for transparency, see |popup-opacity|.
+
*changed-9.3*
Changed~
-------
diff --git a/src/globals.h b/src/globals.h
index 5e96304cc..4b40873a9 100644
--- a/src/globals.h
+++ b/src/globals.h
@@ -120,6 +120,9 @@ EXTERN tabpage_T *popup_mask_tab INIT(= NULL);
// Zindex in for screen_char(): if lower than the value in "popup_mask"
// drawing the character is skipped.
EXTERN int screen_zindex INIT(= 0);
+
+// Currently drawing popup with opacity window, or NULL.
+EXTERN win_T *screen_opacity_popup INIT(= NULL);
#endif
EXTERN int screen_Rows INIT(= 0); // actual size of ScreenLines[]
diff --git a/src/highlight.c b/src/highlight.c
index 9afe69c4d..5df56b100 100644
--- a/src/highlight.c
+++ b/src/highlight.c
@@ -3081,6 +3081,201 @@ hl_combine_attr(int char_attr, int prim_attr)
return get_attr_entry(&term_attr_table, &new_en);
}
+#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
+/*
+ * Blend two RGB colors based on blend value (0-100).
+ * blend: 0=use popup color, 100=use background color
+ * If bg_color is INVALCOLOR, high blend means more visible (return INVALCOLOR).
+ */
+ static guicolor_T
+blend_colors(guicolor_T popup_color, guicolor_T bg_color, int blend_val)
+{
+ int r1, g1, b1, r2, g2, b2, r, g, b;
+
+ if (popup_color == INVALCOLOR)
+ return INVALCOLOR;
+
+ r1 = (popup_color >> 16) & 0xFF;
+ g1 = (popup_color >> 8) & 0xFF;
+ b1 = popup_color & 0xFF;
+
+ if (bg_color == INVALCOLOR)
+ {
+ // Background color unknown: fade popup color to black as blend increases
+ // This makes background text more visible at high blend values
+ r = r1 * (100 - blend_val) / 100;
+ g = g1 * (100 - blend_val) / 100;
+ b = b1 * (100 - blend_val) / 100;
+ return (r << 16) | (g << 8) | b;
+ }
+
+ r2 = (bg_color >> 16) & 0xFF;
+ g2 = (bg_color >> 8) & 0xFF;
+ b2 = bg_color & 0xFF;
+
+ r = r1 + (r2 - r1) * blend_val / 100;
+ g = g1 + (g2 - g1) * blend_val / 100;
+ b = b1 + (b2 - b1) * blend_val / 100;
+
+ return (r << 16) | (g << 8) | b;
+}
+#endif
+
+/*
+ * Blend attributes for popup windows with opacity.
+ * Blends foreground and/or background colors based on blend value (0-100).
+ * blend: 0 = opaque (use popup colors), 100 = transparent (use background colors)
+ * blend_fg: TRUE to blend foreground color, FALSE to keep popup foreground
+ */
+ int
+hl_blend_attr(int char_attr, int popup_attr, int blend, int blend_fg UNUSED)
+{
+ attrentry_T *char_aep = NULL;
+ attrentry_T *popup_aep;
+ attrentry_T new_en;
+
+ // If both attrs are 0, return 0
+ if (char_attr == 0 && popup_attr == 0)
+ return 0;
+ if (blend >= 100)
+ return char_attr; // Fully transparent, show background only
+
+#ifdef FEAT_GUI
+ if (gui.in_use)
+ {
+ if (char_attr > HL_ALL)
+ char_aep = syn_gui_attr2entry(char_attr);
+ if (char_aep != NULL)
+ new_en = *char_aep;
+ else
+ {
+ CLEAR_FIELD(new_en);
+ new_en.ae_u.gui.fg_color = INVALCOLOR;
+ new_en.ae_u.gui.bg_color = INVALCOLOR;
+ new_en.ae_u.gui.sp_color = INVALCOLOR;
+ if (char_attr <= HL_ALL)
+ new_en.ae_attr = char_attr;
+ }
+
+ if (popup_attr > HL_ALL)
+ {
+ popup_aep = syn_gui_attr2entry(popup_attr);
+ if (popup_aep != NULL)
+ {
+ 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
+ if (popup_aep->ae_u.gui.bg_color != INVALCOLOR)
+ {
+ new_en.ae_u.gui.fg_color = blend_colors(
+ popup_aep->ae_u.gui.bg_color, 0xFFFFFF, blend);
+ }
+ }
+ else if (popup_aep->ae_u.gui.fg_color != INVALCOLOR)
+ {
+ // blend_fg=FALSE: use popup foreground
+ new_en.ae_u.gui.fg_color = popup_aep->ae_u.gui.fg_color;
+ }
+ // Blend background color
+ if (popup_aep->ae_u.gui.bg_color != INVALCOLOR)
+ {
+ // Always use popup background, fade to black based on blend
+ int r = ((popup_aep->ae_u.gui.bg_color >> 16) & 0xFF) * (100 - blend) / 100;
+ int g = ((popup_aep->ae_u.gui.bg_color >> 8) & 0xFF) * (100 - blend) / 100;
+ int b = (popup_aep->ae_u.gui.bg_color & 0xFF) * (100 - blend) / 100;
+ new_en.ae_u.gui.bg_color = (r << 16) | (g << 8) | b;
+ }
+ }
+ }
+ return get_attr_entry(&gui_attr_table, &new_en);
+ }
+#endif
+
+ if (IS_CTERM)
+ {
+ if (char_attr > HL_ALL)
+ char_aep = syn_cterm_attr2entry(char_attr);
+ if (char_aep != NULL)
+ new_en = *char_aep;
+ else
+ {
+ CLEAR_FIELD(new_en);
+#ifdef FEAT_TERMGUICOLORS
+ new_en.ae_u.cterm.bg_rgb = INVALCOLOR;
+ new_en.ae_u.cterm.fg_rgb = INVALCOLOR;
+ new_en.ae_u.cterm.ul_rgb = INVALCOLOR;
+#endif
+ if (char_attr <= HL_ALL)
+ new_en.ae_attr = char_attr;
+ }
+
+ if (popup_attr > HL_ALL)
+ {
+ popup_aep = syn_cterm_attr2entry(popup_attr);
+ if (popup_aep != NULL)
+ {
+ // Blend foreground color
+ if (popup_aep->ae_u.cterm.fg_color > 0)
+ {
+ if (new_en.ae_u.cterm.fg_color > 0)
+ new_en.ae_u.cterm.fg_color = popup_aep->ae_u.cterm.fg_color;
+ else
+ new_en.ae_u.cterm.fg_color = popup_aep->ae_u.cterm.fg_color;
+ }
+ // Use popup background color (cterm colors don't support blending)
+ if (popup_aep->ae_u.cterm.bg_color > 0)
+ {
+ new_en.ae_u.cterm.bg_color = popup_aep->ae_u.cterm.bg_color;
+ }
+#ifdef FEAT_TERMGUICOLORS
+ // 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
+ if (popup_aep->ae_u.cterm.bg_rgb != INVALCOLOR)
+ {
+ new_en.ae_u.cterm.fg_rgb = blend_colors(
+ popup_aep->ae_u.cterm.bg_rgb, 0xFFFFFF, blend);
+ }
+ }
+ else if (popup_aep->ae_u.cterm.fg_rgb != INVALCOLOR)
+ {
+ // blend_fg=FALSE: use popup foreground
+ new_en.ae_u.cterm.fg_rgb = popup_aep->ae_u.cterm.fg_rgb;
+ }
+ if (popup_aep->ae_u.cterm.bg_rgb != INVALCOLOR)
+ {
+ // Always use popup background, fade to black based on blend
+ int r = ((popup_aep->ae_u.cterm.bg_rgb >> 16) & 0xFF) * (100 - blend) / 100;
+ int g = ((popup_aep->ae_u.cterm.bg_rgb >> 8) & 0xFF) * (100 - blend) / 100;
+ int b = (popup_aep->ae_u.cterm.bg_rgb & 0xFF) * (100 - blend) / 100;
+ new_en.ae_u.cterm.bg_rgb = (r << 16) | (g << 8) | b;
+ }
+#endif
+ }
+ }
+ return get_attr_entry(&cterm_attr_table, &new_en);
+ }
+
+ if (char_attr > HL_ALL)
+ char_aep = syn_term_attr2entry(char_attr);
+ if (char_aep != NULL)
+ new_en = *char_aep;
+ else
+ {
+ CLEAR_FIELD(new_en);
+ if (char_attr <= HL_ALL)
+ new_en.ae_attr = char_attr;
+ }
+
+ // For term mode, no separate background color handling.
+ return get_attr_entry(&term_attr_table, &new_en);
+}
+
#ifdef FEAT_GUI
attrentry_T *
syn_gui_attr2entry(int attr)
diff --git a/src/popupwin.c b/src/popupwin.c
index a126822d0..380b884be 100644
--- a/src/popupwin.c
+++ b/src/popupwin.c
@@ -781,6 +781,24 @@ apply_general_options(win_T *wp, dict_T *dict)
wp->w_popup_flags &= ~POPF_RESIZE;
}
+ di = dict_find(dict, (char_u *)"opacity", -1);
+ if (di != NULL)
+ {
+ nr = dict_get_number(dict, "opacity");
+ if (nr > 0 && nr <= 100)
+ {
+ // opacity: 0-100, where 0=transparent, 100=opaque
+ // Convert to blend (0=opaque, 100=transparent)
+ wp->w_popup_flags |= POPF_OPACITY;
+ wp->w_popup_blend = 100 - nr;
+ }
+ else
+ {
+ wp->w_popup_flags &= ~POPF_OPACITY;
+ wp->w_popup_blend = 0;
+ }
+ }
+
di = dict_find(dict, (char_u *)"close", -1);
if (di != NULL)
{
@@ -3249,6 +3267,9 @@ f_popup_setoptions(typval_T *argvars, typval_T *rettv UNUSED)
int id;
win_T *wp;
linenr_T old_firstline;
+#ifdef FEAT_PROP_POPUP
+ int old_blend;
+#endif
if (in_vim9script()
&& (check_for_number_arg(argvars, 0) == FAIL
@@ -3264,11 +3285,23 @@ f_popup_setoptions(typval_T *argvars, typval_T *rettv UNUSED)
return;
dict = argvars[1].vval.v_dict;
old_firstline = wp->w_firstline;
+#ifdef FEAT_PROP_POPUP
+ old_blend = wp->w_popup_blend;
+#endif
(void)apply_options(wp, dict, FALSE);
if (old_firstline != wp->w_firstline)
redraw_win_later(wp, UPD_NOT_VALID);
+#ifdef FEAT_PROP_POPUP
+ // Force redraw if opacity value changed
+ if (old_blend != wp->w_popup_blend)
+ {
+ redraw_win_later(wp, UPD_NOT_VALID);
+ // Also redraw windows below the popup
+ redraw_all_later(UPD_NOT_VALID);
+ }
+#endif
popup_adjust_position(wp);
}
@@ -3518,6 +3551,9 @@ f_popup_getoptions(typval_T *argvars, typval_T *rettv)
dict_add_number(dict, "resize", (wp->w_popup_flags & POPF_RESIZE) != 0);
dict_add_number(dict, "posinvert",
(wp->w_popup_flags & POPF_POSINVERT) != 0);
+ // Return opacity (0-100) by converting from internal blend value
+ dict_add_number(dict, "opacity",
+ (wp->w_popup_flags & POPF_OPACITY) ? 100 - wp->w_popup_blend : 100);
dict_add_number(dict, "cursorline",
(wp->w_popup_flags & POPF_CURSORLINE) != 0);
dict_add_string(dict, "highlight", wp->w_p_wcr);
@@ -4088,6 +4124,12 @@ may_update_popup_mask(int type)
width = popup_width(wp);
height = popup_height(wp);
popup_update_mask(wp, width, height);
+
+ // Popup with opacitys do not block lower layers from drawing,
+ // so they don't participate in the popup_mask.
+ if (wp->w_popup_flags & POPF_OPACITY)
+ continue;
+
for (line = wp->w_winrow;
line < wp->w_winrow + height && line < screen_Rows; ++line)
for (col = wp->w_wincol;
@@ -4195,14 +4237,62 @@ may_update_popup_position(void)
}
/*
- * Return a string of "len" spaces in IObuff.
+ * Draw a single padding cell with opacity blending.
+ * Restores background from saved data and blends with popup attribute.
*/
- static char_u *
-get_spaces(int len)
+ static void
+draw_opacity_padding_cell(
+ int row,
+ int col,
+ schar_T *saved_screenlines,
+ int *saved_screenattrs,
+ u8char_T *saved_screenlinesuc,
+ int save_start_row,
+ int save_start_col,
+ int save_rows,
+ int save_cols)
+{
+ int off = LineOffset[row] + col;
+ int r = row - save_start_row;
+ int c = col - save_start_col;
+
+ if (r >= 0 && r < save_rows && c >= 0 && c < save_cols)
+ {
+ int save_off = r * save_cols + c;
+ ScreenLines[off] = saved_screenlines[save_off];
+ ScreenAttrs[off] = saved_screenattrs[save_off];
+ if (enc_utf8 && saved_screenlinesuc != NULL)
+ 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[off] = hl_blend_attr(ScreenAttrs[off],
+ popup_attr_val, blend, TRUE);
+ screen_char(off, row, col);
+ }
+}
+
+/*
+ * Fill a rectangular padding area with opacity blending.
+ */
+ static void
+fill_opacity_padding(
+ int start_row,
+ int end_row,
+ int start_col,
+ int end_col,
+ schar_T *saved_screenlines,
+ int *saved_screenattrs,
+ u8char_T *saved_screenlinesuc,
+ int save_start_row,
+ int save_start_col,
+ int save_rows,
+ int save_cols)
{
- vim_memset(IObuff, ' ', (size_t)len);
- IObuff[len] = NUL;
- return IObuff;
+ for (int pad_row = start_row; pad_row < end_row; pad_row++)
+ 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);
}
/*
@@ -4249,6 +4339,64 @@ update_popups(void (*win_update)(win_T *wp))
// zindex is on top of the character.
screen_zindex = wp->w_zindex;
+ // Set popup with opacity context for screen drawing.
+ if (wp->w_popup_flags & POPF_OPACITY)
+ screen_opacity_popup = wp;
+ else
+ screen_opacity_popup = NULL;
+
+ // Save background ScreenLines for padding opacity.
+ // We need to save it before win_update() overwrites it.
+ schar_T *saved_screenlines = NULL;
+ int *saved_screenattrs = NULL;
+ u8char_T *saved_screenlinesuc = NULL;
+ int save_start_row = 0;
+ int save_start_col = 0;
+ int save_rows = 0;
+ int save_cols = 0;
+
+ if (screen_opacity_popup != NULL
+ && (wp->w_popup_padding[0] > 0 || wp->w_popup_padding[1] > 0
+ || wp->w_popup_padding[2] > 0 || wp->w_popup_padding[3] > 0))
+ {
+ // Calculate the area to save (all padding regions including top/bottom)
+ save_start_row = wp->w_winrow + wp->w_popup_border[0];
+ save_start_col = wp->w_wincol + wp->w_popup_border[3];
+ 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];
+
+ // Allocate buffers
+ saved_screenlines = ALLOC_MULT(schar_T, save_rows * save_cols);
+ saved_screenattrs = ALLOC_MULT(int, save_rows * save_cols);
+ if (enc_utf8)
+ saved_screenlinesuc = ALLOC_MULT(u8char_T, save_rows * save_cols);
+
+ // Save the background
+ if (saved_screenlines != NULL && saved_screenattrs != NULL)
+ {
+ for (int r = 0; r < save_rows; r++)
+ {
+ int screen_row = save_start_row + r;
+ if (screen_row >= 0 && screen_row < screen_Rows)
+ {
+ for (int c = 0; c < save_cols; c++)
+ {
+ int screen_col = save_start_col + c;
+ if (screen_col >= 0 && screen_col < screen_Columns)
+ {
+ int off = LineOffset[screen_row] + screen_col;
+ int save_off = r * save_cols + c;
+ saved_screenlines[save_off] = ScreenLines[off];
+ saved_screenattrs[save_off] = ScreenAttrs[off];
+ if (enc_utf8 && saved_screenlinesuc != NULL)
+ saved_screenlinesuc[save_off] = ScreenLinesUC[off];
+ }
+ }
+ }
+ }
+ }
+ }
+
// Set flags in popup_transparent[] for masked cells.
update_popup_transparent(wp, 1);
@@ -4334,8 +4482,18 @@ update_popups(void (*win_update)(win_T *wp))
border_attr[i] = popup_attr;
if (wp->w_border_highlight[i] != NULL)
border_attr[i] = syn_name2attr(wp->w_border_highlight[i]);
+
+ // Apply blend to border attributes for popup with opacitys
+ if ((wp->w_popup_flags & POPF_OPACITY) && wp->w_popup_blend > 0)
+ border_attr[i] = hl_blend_attr(0, border_attr[i],
+ wp->w_popup_blend, FALSE);
}
+ // Apply blend to popup_attr for padding areas
+ if ((wp->w_popup_flags & POPF_OPACITY) && wp->w_popup_blend > 0)
+ popup_attr = hl_blend_attr(0, popup_attr, wp->w_popup_blend, FALSE);
+
+
// Title goes on top of border or padding.
title_wincol = wp->w_wincol + 1;
if (wp->w_popup_title != NULL)
@@ -4416,15 +4574,43 @@ update_popups(void (*win_update)(win_T *wp))
if (title_len > 0 && row == wp->w_winrow)
{
// top padding and no border; do not draw over the title
- screen_fill(row, row + 1, padcol, title_wincol,
- ' ', ' ', popup_attr);
- screen_fill(row, row + 1, title_wincol + title_len,
- padendcol, ' ', ' ', popup_attr);
+ if (screen_opacity_popup != NULL && saved_screenlines != NULL)
+ {
+ // Left of title
+ fill_opacity_padding(row, row + 1, padcol, title_wincol,
+ saved_screenlines, saved_screenattrs,
+ saved_screenlinesuc, save_start_row, save_start_col,
+ save_rows, save_cols);
+ // Right of title
+ fill_opacity_padding(row, row + 1,
+ title_wincol + title_len, padendcol,
+ saved_screenlines, saved_screenattrs,
+ saved_screenlinesuc, save_start_row, save_start_col,
+ save_rows, save_cols);
+ }
+ else
+ {
+ screen_fill(row, row + 1, padcol, title_wincol,
+ ' ', ' ', popup_attr);
+ screen_fill(row, row + 1, title_wincol + title_len,
+ padendcol, ' ', ' ', popup_attr);
+ }
row += 1;
top_padding -= 1;
}
- screen_fill(row, row + top_padding, padcol, padendcol,
- ' ', ' ', popup_attr);
+ // Draw remaining top padding rows
+ if (screen_opacity_popup != NULL && saved_screenlines != NULL)
+ {
+ fill_opacity_padding(row, row + top_padding, padcol, padendcol,
+ saved_screenlines, saved_screenattrs,
+ saved_screenlinesuc, save_start_row, save_start_col,
+ save_rows, save_cols);
+ }
+ else
+ {
+ screen_fill(row, row + top_padding, padcol, padendcol,
+ ' ', ' ', popup_attr);
+ }
}
// Compute scrollbar thumb position and size.
@@ -4495,7 +4681,8 @@ update_popups(void (*win_update)(win_T *wp))
col = 0;
}
if (pad_left > 0)
- screen_puts(get_spaces(pad_left), row, col, popup_attr);
+ screen_fill(row, row + 1, col, col + pad_left,
+ ' ', ' ', popup_attr);
}
// scrollbar
if (wp->w_has_scrollbar)
@@ -4520,10 +4707,14 @@ update_popups(void (*win_update)(win_T *wp))
}
// right padding
if (do_padding && wp->w_popup_padding[1] > 0)
- screen_puts(get_spaces(wp->w_popup_padding[1]), row,
- wincol + wp->w_popup_border[3]
- + wp->w_popup_padding[3] + wp->w_width + wp->w_leftcol,
- popup_attr);
+ {
+ int pad_col_start = wincol + wp->w_popup_border[3]
+ + 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,
+ ' ', ' ', popup_attr);
+ }
}
// right shadow
@@ -4543,8 +4734,14 @@ update_popups(void (*win_update)(win_T *wp))
// bottom padding
row = wp->w_winrow + wp->w_popup_border[0]
+ wp->w_popup_padding[0] + wp->w_height;
- screen_fill(row, row + wp->w_popup_padding[2],
- padcol, padendcol, ' ', ' ', popup_attr);
+ if (screen_opacity_popup != NULL && saved_screenlines != NULL)
+ fill_opacity_padding(row, row + wp->w_popup_padding[2],
+ padcol, padendcol, saved_screenlines, saved_screenattrs,
+ saved_screenlinesuc, save_start_row, save_start_col,
+ save_rows, save_cols);
+ else
+ screen_fill(row, row + wp->w_popup_padding[2],
+ padcol, padendcol, ' ', ' ', popup_attr);
}
if (wp->w_popup_border[2] > 0)
@@ -4583,6 +4780,17 @@ update_popups(void (*win_update)(win_T *wp))
update_popup_transparent(wp, 0);
+ // Free saved background data
+ if (saved_screenlines != NULL)
+ vim_free(saved_screenlines);
+ if (saved_screenattrs != NULL)
+ vim_free(saved_screenattrs);
+ if (saved_screenlinesuc != NULL)
+ vim_free(saved_screenlinesuc);
+
+ // Clear popup with opacity context.
+ screen_opacity_popup = NULL;
+
// Back to the normal zindex.
screen_zindex = 0;
diff --git a/src/proto/
highlight.pro b/src/proto/
highlight.pro
index 760e8fa12..908765c21 100644
--- a/src/proto/
highlight.pro
+++ b/src/proto/
highlight.pro
@@ -20,6 +20,7 @@ int get_tgc_attr_idx(int attr, guicolor_T fg, guicolor_T bg);
int get_gui_attr_idx(int attr, guicolor_T fg, guicolor_T bg);
void clear_hl_tables(void);
int hl_combine_attr(int char_attr, int prim_attr);
+int hl_blend_attr(int char_attr, int popup_attr, int blend, int blend_fg);
attrentry_T *syn_gui_attr2entry(int attr);
int syn_attr2attr(int attr);
attrentry_T *syn_term_attr2entry(int attr);
diff --git a/src/screen.c b/src/screen.c
index b92e2779f..74e4a40e3 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -583,6 +583,7 @@ screen_line(
redraw_this = TRUE;
}
#endif
+ // skip the second cell for double-width characters.
if (redraw_this && char_cells == 2 && skip_for_popup(row, col + coloff + 1))
redraw_this = FALSE;
// For transparent popup cells, update the background character
@@ -613,6 +614,71 @@ screen_line(
else
screen_char(off_to, row, col + coloff);
}
+
+#ifdef FEAT_PROP_POPUP
+ // For popup with opacity windows: if drawing a space, show the
+ // underlying character with the popup's attributes blended in.
+ int opacity_blank = FALSE;
+ // Check if the popup is drawing a space and the background is the
+ // second cell of a wide character. Skip drawing to preserve the
+ // wide character that was drawn in the previous cell.
+ if (screen_opacity_popup != NULL
+ && (flags & SLF_POPUP)
+ && ScreenLines[off_from] == ' '
+ && (!enc_utf8 || ScreenLinesUC[off_from] == 0)
+ && ScreenLines[off_to] == 0
+ && off_to > 0
+ && enc_utf8 && ScreenLinesUC[off_to - 1] != 0
+ && utf_char2cells(ScreenLinesUC[off_to - 1]) == 2)
+ {
+ // This is the second cell of a wide character. Don't overwrite it.
+ opacity_blank = TRUE;
+ redraw_this = FALSE;
+ }
+ else if (screen_opacity_popup != NULL
+ && (flags & SLF_POPUP)
+ && ScreenLines[off_from] == ' '
+ && (!enc_utf8 || ScreenLinesUC[off_from] == 0)
+ && ScreenLines[off_to] != 0)
+ {
+ int bg_char_cells = 1;
+ if (enc_utf8 && ScreenLinesUC[off_to] != 0)
+ bg_char_cells = utf_char2cells(ScreenLinesUC[off_to]);
+
+ // For wide background character, check if the next popup cell
+ // is also a space. If not, the wide char would be partially
+ // covered by a popup character, so don't show it.
+ if (bg_char_cells == 2)
+ {
+ 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.
+ goto skip_opacity;
+ int next_off_from = off_from + 1;
+ if (!(ScreenLines[next_off_from] == ' '
+ && (!enc_utf8 || ScreenLinesUC[next_off_from] == 0)))
+ {
+ // Next cell is not a space, don't show the wide char.
+ goto skip_opacity;
+ }
+ }
+
+ opacity_blank = TRUE;
+ // Keep the underlying character and blend its foreground color
+ // from popup background color to original color.
+ int popup_attr = get_wcr_attr(screen_opacity_popup);
+ int blend = screen_opacity_popup->w_popup_blend;
+ ScreenAttrs[off_to] = hl_blend_attr(ScreenAttrs[off_to],
+ popup_attr, blend, TRUE);
+ screen_char(off_to, row, col + coloff);
+ // For wide background character, also update the second cell.
+ if (bg_char_cells == 2)
+ ScreenAttrs[off_to + 1] = ScreenAttrs[off_to];
+ redraw_this = FALSE;
+ }
+skip_opacity:
+#endif
+
if (redraw_this)
{
/*
@@ -746,11 +812,24 @@ screen_line(
redraw_next = TRUE;
#endif
ScreenAttrs[off_to] = ScreenAttrs[off_from];
+#ifdef FEAT_PROP_POPUP
+ // For popup with opacity text: blend background with default (0)
+ if (screen_opacity_popup != NULL
+ && (flags & SLF_POPUP)
+ && screen_opacity_popup->w_popup_blend > 0)
+ {
+ int popup_attr = get_wcr_attr(screen_opacity_popup);
+ int blend = screen_opacity_popup->w_popup_blend;
+ // Blend popup attr with default background (0)
+ // FALSE = keep popup foreground color, blend background only
+ ScreenAttrs[off_to] = hl_blend_attr(0, popup_attr, blend, FALSE);
+ }
+#endif
// For simplicity set the attributes of second half of a
// double-wide character equal to the first half.
if (char_cells == 2)
- ScreenAttrs[off_to + 1] = ScreenAttrs[off_from];
+ ScreenAttrs[off_to + 1] = ScreenAttrs[off_to];
if (enc_dbcs != 0 && char_cells == 2)
screen_char_2(off_to, row, col + coloff);
@@ -775,9 +854,14 @@ screen_line(
screen_stop_highlight();
}
- ScreenCols[off_to] = ScreenCols[off_from];
- if (char_cells == 2)
- ScreenCols[off_to + 1] = ScreenCols[off_from + 1];
+#ifdef FEAT_PROP_POPUP
+ if (!opacity_blank)
+#endif
+ {
+ ScreenCols[off_to] = ScreenCols[off_from];
+ if (char_cells == 2)
+ ScreenCols[off_to + 1] = ScreenCols[off_from + 1];
+ }
off_to += char_cells;
off_from += char_cells;
@@ -1725,6 +1809,9 @@ screen_start_highlight(int attr)
{
if (aep->ae_u.cterm.fg_rgb != INVALCOLOR)
term_fg_rgb_color(aep->ae_u.cterm.fg_rgb);
+ else
+ // Reset to default foreground color (SGR 39)
+ out_str((char_u *)" [39m");
}
else
#endif
@@ -2310,6 +2397,51 @@ screen_fill(
// Skip if under a(nother) popup.
&& !skip_for_popup(row, col))
{
+#ifdef FEAT_PROP_POPUP
+ // For popup with opacity: show underlying character with
+ // popup's background color applied.
+ if (screen_opacity_popup != NULL && c == ' ')
+ {
+ // Skip if background is the second cell of a wide character.
+ // Check if previous cell is a wide character.
+ if (ScreenLines[off] == 0
+ && off > 0
+ && enc_utf8 && ScreenLinesUC[off - 1] != 0
+ && utf_char2cells(ScreenLinesUC[off - 1]) == 2)
+ goto next_col;
+ }
+ if (screen_opacity_popup != NULL && c == ' '
+ && ScreenLines[off] != 0)
+ {
+ int bg_char_cells = 1;
+ if (enc_utf8 && ScreenLinesUC[off] != 0)
+ bg_char_cells = utf_char2cells(ScreenLinesUC[off]);
+
+ // For wide background character, check if the next cell
+ // is also being filled with space. If not, the wide char
+ // would be partially covered, so don't show it.
+ if (bg_char_cells == 2)
+ {
+ if (col + 1 >= end_col)
+ // At the edge, skip wide char.
+ goto skip_opacity_fill;
+ // In screen_fill, we're filling with 'c' which is ' '.
+ // The next cell will also be filled with c2 (usually ' ').
+ // If c2 is not space, skip the wide char.
+ if (c2 != ' ')
+ goto skip_opacity_fill;
+ }
+
+ int popup_attr = get_wcr_attr(screen_opacity_popup);
+ int blend = screen_opacity_popup->w_popup_blend;
+ // Blend both foreground and background for padding area
+ ScreenAttrs[off] = hl_blend_attr(ScreenAttrs[off],
+ popup_attr, blend, TRUE);
+ screen_char(off, row, col);
+ goto next_col;
+ }
+skip_opacity_fill:
+#endif
#if defined(FEAT_GUI) || defined(UNIX)
// The bold trick may make a single row of pixels appear in
// the next character. When a bold character is removed, the
@@ -2350,6 +2482,9 @@ screen_fill(
if (!did_delete || c != ' ')
screen_char(off, row, col);
}
+#ifdef FEAT_PROP_POPUP
+next_col:
+#endif
ScreenCols[off] = -1;
++off;
if (col == start_col)
diff --git a/src/structs.h b/src/structs.h
index df296b35d..ed112e064 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -4059,6 +4059,7 @@ struct window_S
#ifdef FEAT_PROP_POPUP
int w_popup_flags; // POPF_ values
+ int w_popup_blend; // 0-100: transparency level for popup with opacitys
int w_popup_handled; // POPUP_HANDLE[0-9] flags
char_u *w_popup_title;
poppos_T w_popup_pos;
diff --git a/src/version.c b/src/version.c
index d6cc249c1..fd53cbfb6 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 */
+/**/
+ 17,
/**/
16,
/**/
diff --git a/src/vim.h b/src/vim.h
index bd8b4a5d6..3d375f099 100644
--- a/src/vim.h
+++ b/src/vim.h
@@ -705,6 +705,7 @@ extern int (*dyn_libintl_wputenv)(const wchar_t *envstring);
#define POPF_INFO 0x200 // used for info of popup menu
#define POPF_INFO_MENU 0x400 // align info popup with popup menu
#define POPF_POSINVERT 0x800 // vertical position can be inverted
+#define POPF_OPACITY 0x1000 // popup has opacity/transparency setting
// flags used in w_popup_handled
#define POPUP_HANDLED_1 0x01 // used by mouse_find_win()