From: Yasuhiro Matsumoto Date: Mon, 9 Mar 2026 18:47:10 +0000 (+0000) Subject: patch 9.2.0129: popup: wrong handling of wide-chars and opacity:0 X-Git-Tag: v9.2.0129^0 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=433bcf3becac2e4278f6f2cdeaa932a18e62d15f;p=thirdparty%2Fvim.git patch 9.2.0129: popup: wrong handling of wide-chars and opacity:0 Problem: popup: wrong handling of wide-chars and opacity:0 Solution: Correctly handle opacity 0, correctly handle wide chars (Yasuhiro Matsumoto) closes: #19606 Signed-off-by: Yasuhiro Matsumoto Signed-off-by: Christian Brabandt --- diff --git a/src/highlight.c b/src/highlight.c index 0bd5c6e4ca..df34f03075 100644 --- a/src/highlight.c +++ b/src/highlight.c @@ -3120,6 +3120,10 @@ blend_colors(guicolor_T popup_color, guicolor_T bg_color, int blend_val) if (popup_color == INVALCOLOR) return INVALCOLOR; + // Fully transparent: use underlying color as-is. + if (blend_val >= 100) + return bg_color; + r1 = (popup_color >> 16) & 0xFF; g1 = (popup_color >> 8) & 0xFF; b1 = popup_color & 0xFF; @@ -3162,8 +3166,8 @@ hl_blend_attr(int char_attr, int popup_attr, int blend, int blend_fg UNUSED) // 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 + if (blend >= 100 && blend_fg) + return char_attr; // Fully transparent for both fg and bg #ifdef FEAT_GUI if (gui.in_use) @@ -3205,14 +3209,15 @@ hl_blend_attr(int char_attr, int popup_attr, int blend, int blend_fg UNUSED) // blend_fg=FALSE: use popup foreground new_en.ae_u.gui.fg_color = popup_aep->ae_u.gui.fg_color; } - // Blend background color + // Blend background color: blend popup bg toward underlying bg 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; + guicolor_T underlying_bg = INVALCOLOR; + if (char_aep != NULL) + underlying_bg = char_aep->ae_u.gui.bg_color; + new_en.ae_u.gui.bg_color = blend_colors( + popup_aep->ae_u.gui.bg_color, + underlying_bg, blend); } } } @@ -3269,11 +3274,13 @@ hl_blend_attr(int char_attr, int popup_attr, int blend, int blend_fg UNUSED) 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; + // Blend popup bg toward underlying bg + guicolor_T underlying_bg = INVALCOLOR; + if (char_aep != NULL) + underlying_bg = char_aep->ae_u.cterm.bg_rgb; + new_en.ae_u.cterm.bg_rgb = blend_colors( + popup_aep->ae_u.cterm.bg_rgb, + underlying_bg, blend); } #endif } diff --git a/src/popupwin.c b/src/popupwin.c index a9325e8377..63f9fe3f3b 100644 --- a/src/popupwin.c +++ b/src/popupwin.c @@ -785,7 +785,13 @@ apply_general_options(win_T *wp, dict_T *dict) if (di != NULL) { nr = dict_get_number(dict, "opacity"); - if (nr > 0 && nr < 100) + if (nr == 0) + { + // opacity: 0, fully transparent + wp->w_popup_flags |= POPF_OPACITY; + wp->w_popup_blend = 100; + } + else if (nr > 0 && nr < 100) { // opacity: 1-99, partially transparent // Convert to blend (0=opaque, 100=transparent) @@ -4244,6 +4250,38 @@ popup_need_position_adjust(win_T *wp) return wp->w_cursor.lnum != wp->w_popup_last_curline; } +/* + * Force background windows to redraw rows under an opacity popup. + */ + static void +redraw_win_under_opacity_popup(win_T *wp) +{ + int height; + int r; + + if (!(wp->w_popup_flags & POPF_OPACITY) || wp->w_popup_blend <= 0 + || (wp->w_popup_flags & POPF_HIDDEN)) + return; + + height = popup_height(wp); + for (r = wp->w_winrow; + r < wp->w_winrow + height && r < screen_Rows; ++r) + { + int line_cp = r; + int col_cp = wp->w_wincol; + win_T *twp; + + twp = mouse_find_win(&line_cp, &col_cp, IGNORE_POPUP); + if (twp != NULL && line_cp < twp->w_height) + { + linenr_T lnum; + + (void)mouse_comp_pos(twp, &line_cp, &col_cp, &lnum, NULL); + redrawWinline(twp, lnum); + } + } +} + /* * Update "popup_mask" if needed. * Also recomputes the popup size and positions. @@ -4281,6 +4319,16 @@ may_update_popup_mask(int type) else if (popup_need_position_adjust(wp)) popup_mask_refresh = TRUE; + // Force background windows to redraw rows under opacity popups. + // Opacity popups don't participate in popup_mask, so their area + // wouldn't normally be redrawn. Without this, ScreenAttrs retains + // blended values from the previous cycle, causing blend accumulation. + // This must run every cycle, not just when popup_mask_refresh is set. + FOR_ALL_POPUPWINS(wp) + redraw_win_under_opacity_popup(wp); + FOR_ALL_POPUPWINS_IN_TAB(curtab, wp) + redraw_win_under_opacity_popup(wp); + if (!popup_mask_refresh) return; @@ -5310,21 +5358,9 @@ update_popups(void (*win_update)(win_T *wp)) } #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; - } + VIM_CLEAR(base_screenlines); + VIM_CLEAR(base_screenattrs); + VIM_CLEAR(base_screenlinesuc); base_screen_rows = 0; base_screen_cols = 0; #endif diff --git a/src/screen.c b/src/screen.c index 01f9118d5d..fc99ac3726 100644 --- a/src/screen.c +++ b/src/screen.c @@ -452,6 +452,48 @@ skip_for_popup(int row, int col) return FALSE; } +#ifdef FEAT_PROP_POPUP +/* + * For a double-wide character at a popup boundary with opacity:0 + * (blend==100), the two cells may have different underlying attrs. + * Pick the one without a background color to prevent color leaking. + */ + static void +resolve_wide_char_opacity_attrs( + int row, int col1, int col2, + sattr_T *attr1, sattr_T *attr2) +{ + int bg1, bg2; + int base1 = 0; + int base2 = 0; + attrentry_T *ae; + + if (*attr1 == *attr2) + return; + + popup_get_base_screen_cell(row, col1, NULL, &base1, NULL); + ae = syn_cterm_attr2entry(base1); +# ifdef FEAT_TERMGUICOLORS + bg1 = (ae != NULL && !COLOR_INVALID(ae->ae_u.cterm.bg_rgb)); +# else + bg1 = (ae != NULL && ae->ae_u.cterm.bg_color != 0); +# endif + + popup_get_base_screen_cell(row, col2, NULL, &base2, NULL); + ae = syn_cterm_attr2entry(base2); +# ifdef FEAT_TERMGUICOLORS + bg2 = (ae != NULL && !COLOR_INVALID(ae->ae_u.cterm.bg_rgb)); +# else + bg2 = (ae != NULL && ae->ae_u.cterm.bg_color != 0); +# endif + + if (bg1 && !bg2) + *attr1 = *attr2; + else if (!bg1 && bg2) + *attr2 = *attr1; +} +#endif + /* * Move one "cooked" screen line to the screen, but only the characters that * have actually changed. Handle insert/delete character. @@ -605,40 +647,7 @@ screen_line( redraw_this = FALSE; // Check if the character is occluded by a popup. if (redraw_this && skip_for_popup(row, col + coloff)) - { -#ifdef FEAT_PROP_POPUP - // For transparent popup cells, update the background character - // so it shows through the popup. - if (screen_opacity_popup && screen_opacity_popup->w_popup_blend > 0) - { - ScreenLines[off_to] = ScreenLines[off_from]; - ScreenAttrs[off_to] = ScreenAttrs[off_from]; - if (enc_utf8) - { - ScreenLinesUC[off_to] = ScreenLinesUC[off_from]; - if (ScreenLinesUC[off_from] != 0) - { - for (int i = 0; i < Screen_mco; ++i) - ScreenLinesC[i][off_to] = ScreenLinesC[i][off_from]; - } - } - if (char_cells == 2) - { - ScreenLines[off_to + 1] = ScreenLines[off_from + 1]; - ScreenAttrs[off_to + 1] = ScreenAttrs[off_from]; - } - if (enc_dbcs == DBCS_JPNU) - ScreenLines2[off_to] = ScreenLines2[off_from]; - - if (enc_dbcs != 0 && char_cells == 2) - screen_char_2(off_to, row, col + coloff); - else - screen_char(off_to, row, col + coloff); - } - else -#endif - redraw_this = FALSE; - } + redraw_this = FALSE; #ifdef FEAT_PROP_POPUP // For popup with opacity windows: if drawing a space, show the @@ -689,7 +698,10 @@ screen_line( ScreenLines[off_to] = ' '; if (enc_utf8) ScreenLinesUC[off_to] = 0; - ScreenAttrs[off_to] = hl_blend_attr(ScreenAttrs[off_to], + int base_attr = ScreenAttrs[off_to]; + popup_get_base_screen_cell(row, col + coloff, + NULL, &base_attr, NULL); + ScreenAttrs[off_to] = hl_blend_attr(base_attr, combined, blend, TRUE); screen_char(off_to, row, col + coloff); opacity_blank = TRUE; @@ -715,12 +727,26 @@ screen_line( int popup_attr = get_win_attr(screen_opacity_popup); int combined = hl_combine_attr(popup_attr, char_attr); int blend = screen_opacity_popup->w_popup_blend; - ScreenAttrs[off_to] = hl_blend_attr(ScreenAttrs[off_to], - combined, blend, TRUE); + int base_attr = ScreenAttrs[off_to]; + popup_get_base_screen_cell(row, col + coloff, + NULL, &base_attr, NULL); + ScreenAttrs[off_to] = hl_blend_attr(base_attr, + combined, blend, TRUE); screen_char(off_to, row, col + coloff); - // For wide background character, also update the second cell. + // For wide background character, also update the second cell + // with its own base attr (it may be outside the popup area). if (bg_char_cells == 2) - ScreenAttrs[off_to + 1] = ScreenAttrs[off_to]; + { + int base_attr2 = ScreenAttrs[off_to + 1]; + popup_get_base_screen_cell(row, col + coloff + 1, + NULL, &base_attr2, NULL); + ScreenAttrs[off_to + 1] = hl_blend_attr(base_attr2, + combined, blend, TRUE); + if (blend == 100) + resolve_wide_char_opacity_attrs(row, + col + coloff, col + coloff + 1, + &ScreenAttrs[off_to], &ScreenAttrs[off_to + 1]); + } redraw_this = FALSE; } // When a popup space overlaps the second half of a destroyed wide @@ -741,8 +767,11 @@ screen_line( 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); + int base_attr = ScreenAttrs[off_to]; + popup_get_base_screen_cell(row, col + coloff, + NULL, &base_attr, NULL); + ScreenAttrs[off_to] = hl_blend_attr(base_attr, + combined, blend, TRUE); screen_char(off_to, row, col + coloff); opacity_blank = TRUE; redraw_this = FALSE; @@ -886,9 +915,10 @@ skip_opacity: if (gui.in_use && changed_this) redraw_next = TRUE; #endif + ScreenAttrs[off_to] = ScreenAttrs[off_from]; #ifdef FEAT_PROP_POPUP - // For popup with opacity text: blend background with default (0) + // For popup with opacity text: blend background with underlying. if (screen_opacity_popup != NULL && (flags & SLF_POPUP) && screen_opacity_popup->w_popup_blend > 0) @@ -896,14 +926,35 @@ skip_opacity: int char_attr = ScreenAttrs[off_from]; int popup_attr = get_win_attr(screen_opacity_popup); int blend = screen_opacity_popup->w_popup_blend; - // Combine popup window color with the character's own - // attribute (e.g. syntax highlighting) so that the - // character's foreground color is preserved. int combined = hl_combine_attr(popup_attr, char_attr); - ScreenAttrs[off_to] = hl_blend_attr(0, combined, blend, FALSE); + int underlying_attr = 0; + + popup_get_base_screen_cell(row, col + coloff, + NULL, &underlying_attr, NULL); + ScreenAttrs[off_to] = hl_blend_attr(underlying_attr, + combined, blend, FALSE); + + // For double-wide characters, the second cell may have a + // different underlying attr (e.g. at popup boundary), + // so blend it independently. + if (char_cells == 2) + { + int underlying_attr2 = 0; + + popup_get_base_screen_cell(row, col + coloff + 1, + NULL, &underlying_attr2, NULL); + ScreenAttrs[off_to + 1] = hl_blend_attr( + underlying_attr2, combined, blend, + FALSE); + if (blend == 100) + resolve_wide_char_opacity_attrs(row, + col + coloff, col + coloff + 1, + &ScreenAttrs[off_to], + &ScreenAttrs[off_to + 1]); + } } + else #endif - // For simplicity set the attributes of second half of a // double-wide character equal to the first half. if (char_cells == 2) diff --git a/src/testdir/dumps/Test_popupwin_opacity_zero_01.dump b/src/testdir/dumps/Test_popupwin_opacity_zero_01.dump new file mode 100644 index 0000000000..17aa447f86 --- /dev/null +++ b/src/testdir/dumps/Test_popupwin_opacity_zero_01.dump @@ -0,0 +1,10 @@ +>b+0&#ffffff0|a|c|k|g|r|o|u|n|d| |t|e|x|t| |h|e|r|e| @54 +|b|a|c|k|g|r|o|u|n|d| |t|e|x|t| |h|e|r|e| @54 +|b|a|c|k|b|l|u|e|n|p|o|p|u|p|t| |h|e|r|e| @54 +|b|a|c|k|g|r|o|r|e|d| |p|o|p|u|p|h|e|r|e| @54 +|b|a|c|k|g|r|o|u|n|d| |t|e|x|t| |h|e|r|e| @54 +|b|a|c|k|g|r|o|u|n|d| |t|e|x|t| |h|e|r|e| @54 +|b|a|c|k|g|r|o|u|n|d| |t|e|x|t| |h|e|r|e| @54 +|b|a|c|k|g|r|o|u|n|d| |t|e|x|t| |h|e|r|e| @54 +|b|a|c|k|g|r|o|u|n|d| |t|e|x|t| |h|e|r|e| @54 +@57|1|,|1| @10|T|o|p| diff --git a/src/testdir/dumps/Test_popupwin_opacity_zero_02.dump b/src/testdir/dumps/Test_popupwin_opacity_zero_02.dump new file mode 100644 index 0000000000..03b9f4a59f --- /dev/null +++ b/src/testdir/dumps/Test_popupwin_opacity_zero_02.dump @@ -0,0 +1,10 @@ +>b+0&#ffffff0|a|c|k|g|r|o|u|n|d| |t|e|x|t| |h|e|r|e| @54 +|b|a|c|k|g|r|o|u|n|d| |t|e|x|t| |h|e|r|e| @54 +|b|a|c|k|b|l|u|e|n|p|o|p|u|p|t| |h|e|r|e| @54 +|b|a|c|k|g|r|o|r|e|d| |p|o|p|u|p|h|e|r|e| @54 +|b|a|c|k|g|r|o|u|n|d| |t|e|x|t| |h|e|r|e| @54 +|b|a|c|k|g|r|o|u|n|d| |t|e|x|t| |h|e|r|e| @54 +|b|a|c|k|g|r|o|u|n|d| |t|e|x|t| |h|e|r|e| @54 +|b|a|c|k|g|r|o|u|n|d| |t|e|x|t| |h|e|r|e| @54 +|b|a|c|k|g|r|o|u|n|d| |t|e|x|t| |h|e|r|e| @54 +|0| @55|1|,|1| @10|T|o|p| diff --git a/src/testdir/test_popupwin.vim b/src/testdir/test_popupwin.vim index a2626a637d..41cd6f0d0f 100644 --- a/src/testdir/test_popupwin.vim +++ b/src/testdir/test_popupwin.vim @@ -4835,6 +4835,42 @@ func Test_popup_opacity_100_blocks_background() call StopVimInTerminal(buf) endfunc +func Test_popup_opacity_zero() + CheckScreendump + + let lines =<< trim END + call setline(1, repeat(['background text here'], 10)) + hi BluePopup guibg=darkblue guifg=white + hi RedPopup guibg=darkred guifg=white + + " Blue popup with opacity=50 (partially transparent) + call popup_create('blue popup', { + \ 'line': 3, 'col': 5, + \ 'highlight': 'BluePopup', + \ 'opacity': 50, + \ 'zindex': 1, + \ }) + + " Red popup with opacity=0 (fully transparent), overlapping the blue one + let g:pop_id = popup_create('red popup', { + \ 'line': 4, 'col': 8, + \ 'highlight': 'RedPopup', + \ 'opacity': 0, + \ 'zindex': 2, + \ }) + END + + call writefile(lines, 'XtestPopupOpacityZero', 'D') + let buf = RunVimInTerminal('-S XtestPopupOpacityZero', #{rows: 10}) + call VerifyScreenDump(buf, 'Test_popupwin_opacity_zero_01', {}) + + call TermWait(buf, 50) + call term_sendkeys(buf, ":echo popup_getoptions(g:pop_id)['opacity']\") + call VerifyScreenDump(buf, 'Test_popupwin_opacity_zero_02', {}) + + call StopVimInTerminal(buf) +endfunc + func Test_popup_getwininfo_tabnr() tab split let winid1 = popup_create('sup', #{tabpage: 1}) diff --git a/src/version.c b/src/version.c index 1526bc451e..29f46f381a 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 */ +/**/ + 129, /**/ 128, /**/