From: Yasuhiro Matsumoto Date: Sat, 28 Feb 2026 17:28:25 +0000 (+0000) Subject: patch 9.2.0080: popup: a few redrawing problems X-Git-Tag: v9.2.0080^0 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=cded5e22058e4f0bb54077a65c130164e3528283;p=thirdparty%2Fvim.git 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 Signed-off-by: Christian Brabandt --- diff --git a/src/drawscreen.c b/src/drawscreen.c index 1124840816..e5de9abf11 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 0c4fc05600..f4d6867aa5 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 267ffb6ff6..e49a863488 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 e92d5eb214..ee8efbb142 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 fa686d2b4f..905f18499a 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 d09726fe79..805d9ada3f 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 be2feddbf7..9462c23977 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 0000000000..9ec6d5ba63 --- /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 0000000000..50ca50354a --- /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 4c41bf75d6..5f87f34e15 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})\") + call TermWait(buf) + call term_sendkeys(buf, ":\") + 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 51b853a2b9..5e72c78424 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, /**/