# HG changeset patch # User Bram Moolenaar # Date 1560003304 -7200 # Node ID 03f3a9ca277077e673ce8610b9d12f37c3de1364 # Parent 914898e347fe5ce24c8ee5680021c5118e222112 patch 8.1.1493: redrawing with popups is slow and causes flicker commit https://github.com/vim/vim/commit/33796b39b9f00b42ca57fa00dbbb52316d9d38ff Author: Bram Moolenaar Date: Sat Jun 8 16:01:13 2019 +0200 patch 8.1.1493: redrawing with popups is slow and causes flicker Problem: Redrawing with popups is slow and causes flicker. Solution: Avoid clearing and redrawing using a zindex mask. diff --git a/src/globals.h b/src/globals.h --- a/src/globals.h +++ b/src/globals.h @@ -70,6 +70,21 @@ EXTERN schar_T *ScreenLines2 INIT(= NULL */ EXTERN short *TabPageIdxs INIT(= NULL); +#ifdef FEAT_TEXT_PROP +// Array with size Rows x Columns containing zindex of popups. +EXTERN short *popup_mask INIT(= NULL); + +// Flag set to TRUE when popup_mask needs to be updated. +EXTERN int popup_mask_refresh INIT(= TRUE); + +// Tab that was used to fill popup_mask. +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); +#endif + EXTERN int screen_Rows INIT(= 0); /* actual size of ScreenLines[] */ EXTERN int screen_Columns INIT(= 0); /* actual size of ScreenLines[] */ diff --git a/src/popupmnu.c b/src/popupmnu.c --- a/src/popupmnu.c +++ b/src/popupmnu.c @@ -431,6 +431,12 @@ pum_redraw(void) / (pum_size - pum_height); } +#ifdef FEAT_TEXT_PROP + // The popup menu is drawn over popup menus with zindex under + // POPUPMENU_ZINDEX. + screen_zindex = POPUPMENU_ZINDEX; +#endif + for (i = 0; i < pum_height; ++i) { idx = i + pum_first; @@ -611,6 +617,10 @@ pum_redraw(void) ++row; } + +#ifdef FEAT_TEXT_PROP + screen_zindex = 0; +#endif } /* diff --git a/src/popupwin.c b/src/popupwin.c --- a/src/popupwin.c +++ b/src/popupwin.c @@ -185,8 +185,12 @@ apply_options(win_T *wp, buf_T *buf UNUS get_pos_options(wp, dict); wp->w_zindex = dict_get_number(dict, (char_u *)"zindex"); + if (wp->w_zindex < 1) + wp->w_zindex = POPUPWIN_DEFAULT_ZINDEX; + if (wp->w_zindex > 32000) + wp->w_zindex = 32000; -#if defined(FEAT_TIMERS) +# if defined(FEAT_TIMERS) // Add timer to close the popup after some time. nr = dict_get_number(dict, (char_u *)"time"); if (nr > 0) @@ -204,7 +208,7 @@ apply_options(win_T *wp, buf_T *buf UNUS clear_tv(&tv); } } -#endif +# endif // Option values resulting in setting an option. str = dict_get_string(dict, (char_u *)"highlight", FALSE); @@ -330,6 +334,8 @@ apply_options(win_T *wp, buf_T *buf UNUS else semsg(_(e_invarg2), tv_get_string(&di->di_tv)); } + + popup_mask_refresh = TRUE; } /* @@ -435,6 +441,10 @@ popup_adjust_position(win_T *wp) int left_extra = wp->w_popup_border[3] + wp->w_popup_padding[3]; int extra_height = top_extra + bot_extra; int extra_width = left_extra + right_extra; + int org_winrow = wp->w_winrow; + int org_wincol = wp->w_wincol; + int org_width = wp->w_width; + int org_height = wp->w_height; wp->w_winrow = 0; wp->w_wincol = 0; @@ -554,6 +564,16 @@ popup_adjust_position(win_T *wp) } wp->w_popup_last_changedtick = CHANGEDTICK(wp->w_buffer); + + // Need to update popup_mask if the position or size changed. + if (org_winrow != wp->w_winrow + || org_wincol != wp->w_wincol + || org_width != wp->w_width + || org_height != wp->w_height) + { + redraw_all_later(NOT_VALID); + popup_mask_refresh = TRUE; + } } typedef enum @@ -565,7 +585,7 @@ typedef enum /* * popup_create({text}, {options}) * popup_atcursor({text}, {options}) - * When called from f_popup_atcursor() "atcursor" is TRUE. + * When called from f_popup_atcursor() "type" is TYPE_ATCURSOR. */ static void popup_create(typval_T *argvars, typval_T *rettv, create_type_T type) @@ -675,18 +695,18 @@ popup_create(typval_T *argvars, typval_T set_moved_columns(wp, FIND_STRING); } + // set default values + wp->w_zindex = POPUPWIN_DEFAULT_ZINDEX; + // Deal with options. apply_options(wp, buf, argvars[1].vval.v_dict); - // set default values - if (wp->w_zindex == 0) - wp->w_zindex = 50; - popup_adjust_position(wp); wp->w_vsep_width = 0; redraw_all_later(NOT_VALID); + popup_mask_refresh = TRUE; } /* @@ -815,6 +835,7 @@ f_popup_hide(typval_T *argvars, typval_T wp->w_popup_flags |= POPF_HIDDEN; --wp->w_buffer->b_nwindows; redraw_all_later(NOT_VALID); + popup_mask_refresh = TRUE; } } @@ -832,6 +853,7 @@ f_popup_show(typval_T *argvars, typval_T wp->w_popup_flags &= ~POPF_HIDDEN; ++wp->w_buffer->b_nwindows; redraw_all_later(NOT_VALID); + popup_mask_refresh = TRUE; } } @@ -843,6 +865,7 @@ popup_free(win_T *wp) clear_cmdline = TRUE; win_free_popup(wp); redraw_all_later(NOT_VALID); + popup_mask_refresh = TRUE; } /* @@ -944,7 +967,6 @@ f_popup_move(typval_T *argvars, typval_T if (wp->w_winrow + wp->w_height >= cmdline_row) clear_cmdline = TRUE; popup_adjust_position(wp); - redraw_all_later(NOT_VALID); } /* @@ -984,7 +1006,7 @@ f_popup_getpos(typval_T *argvars, typval } /* - * f_popup_getoptions({id}) + * popup_getoptions({id}) */ void f_popup_getoptions(typval_T *argvars, typval_T *rettv) diff --git a/src/proto/screen.pro b/src/proto/screen.pro --- a/src/proto/screen.pro +++ b/src/proto/screen.pro @@ -16,6 +16,7 @@ int update_screen(int type_arg); int conceal_cursor_line(win_T *wp); void conceal_check_cursor_line(void); void update_debug_sign(buf_T *buf, linenr_T lnum); +int may_update_popup_mask(int type_arg); void updateWindow(win_T *wp); int screen_get_current_line_off(void); void screen_line(int row, int coloff, int endcol, int clear_width, int flags); diff --git a/src/screen.c b/src/screen.c --- a/src/screen.c +++ b/src/screen.c @@ -610,15 +610,10 @@ update_screen(int type_arg) curwin->w_lines_valid = 0; /* don't use w_lines[].wl_size now */ return FAIL; } + #ifdef FEAT_TEXT_PROP - // TODO: avoid redrawing everything when there is a popup window. - if (popup_any_visible()) - { - if (type < NOT_VALID) - type = NOT_VALID; - FOR_ALL_WINDOWS(wp) - wp->w_redr_type = NOT_VALID; - } + // Update popup_mask if needed. + type = may_update_popup_mask(type); #endif updating_screen = TRUE; @@ -880,6 +875,10 @@ update_prepare(void) #ifdef FEAT_SEARCH_EXTRA start_search_hl(); #endif +#ifdef FEAT_TEXT_PROP + // Update popup_mask if needed. + may_update_popup_mask(0); +#endif } /* @@ -992,11 +991,6 @@ update_debug_sign(buf_T *buf, linenr_T l win_redr_status(wp, FALSE); } -#ifdef FEAT_TEXT_PROP - // Display popup windows on top of the others. - update_popups(); -#endif - update_finish(); } #endif @@ -1021,6 +1015,82 @@ get_wcr_attr(win_T *wp) #ifdef FEAT_TEXT_PROP /* + * Update "popup_mask" if needed. + * Also recomputes the popup size and positions. + * Also updates "popup_visible". + * If more redrawing is needed than "type_arg" a higher value is returned. + */ + int +may_update_popup_mask(int type_arg) +{ + int type = type_arg; + win_T *wp; + + if (popup_mask_tab != curtab) + popup_mask_refresh = TRUE; + if (!popup_mask_refresh) + { + // Check if any buffer has changed. + for (wp = first_popupwin; wp != NULL; wp = wp->w_next) + if (wp->w_popup_last_changedtick != CHANGEDTICK(wp->w_buffer)) + popup_mask_refresh = TRUE; + for (wp = curtab->tp_first_popupwin; wp != NULL; wp = wp->w_next) + if (wp->w_popup_last_changedtick != CHANGEDTICK(wp->w_buffer)) + popup_mask_refresh = TRUE; + if (!popup_mask_refresh) + return type; + } + + popup_mask_refresh = FALSE; + popup_mask_tab = curtab; + + popup_visible = FALSE; + vim_memset(popup_mask, 0, screen_Rows * screen_Columns * sizeof(short)); + + // Find the window with the lowest zindex that hasn't been handled yet, + // so that the window with a higher zindex overwrites the value in + // popup_mask. + popup_reset_handled(); + while ((wp = find_next_popup(TRUE)) != NULL) + { + int top_off, bot_off; + int left_off, right_off; + short *p; + int line, col; + + popup_visible = TRUE; + + // Recompute the position if the text changed. + if (wp->w_popup_last_changedtick != CHANGEDTICK(wp->w_buffer)) + popup_adjust_position(wp); + + // the position and size are for the inside, add the padding and + // border + top_off = wp->w_popup_padding[0] + wp->w_popup_border[0]; + bot_off = wp->w_popup_padding[2] + wp->w_popup_border[2]; + left_off = wp->w_popup_padding[3] + wp->w_popup_border[3]; + right_off = wp->w_popup_padding[1] + wp->w_popup_border[1]; + + for (line = wp->w_winrow + top_off; + line < wp->w_winrow + wp->w_height + bot_off + && line < screen_Rows; ++line) + for (col = wp->w_wincol + left_off; + col < wp->w_wincol + wp->w_width + right_off + && col < screen_Columns; ++col) + { + p = popup_mask + line * screen_Columns + col; + if (*p != wp->w_zindex) + { + *p = wp->w_zindex; + type = NOT_VALID; + } + } + } + + return type; +} + +/* * Return a string of "len" spaces in IObuff. */ static char_u * @@ -1049,14 +1119,13 @@ update_popups(void) // Find the window with the lowest zindex that hasn't been updated yet, // so that the window with a higher zindex is drawn later, thus goes on // top. - // TODO: don't redraw every popup every time. - popup_visible = FALSE; popup_reset_handled(); while ((wp = find_next_popup(TRUE)) != NULL) { - // Recompute the position if the text changed. - if (wp->w_popup_last_changedtick != CHANGEDTICK(wp->w_buffer)) - popup_adjust_position(wp); + // This drawing uses the zindex of the popup window, so that it's on + // top of the text but doesn't draw when another popup with higher + // zindex is on top of the character. + screen_zindex = wp->w_zindex; // adjust w_winrow and w_wincol for border and padding, since // win_update() doesn't handle them. @@ -1067,7 +1136,6 @@ update_popups(void) // Draw the popup text. win_update(wp); - popup_visible = TRUE; wp->w_winrow -= top_off; wp->w_wincol -= left_off; @@ -1190,6 +1258,9 @@ update_popups(void) wp->w_wincol + total_width - 1, border_attr[2]); } } + + // Back to the normal zindex. + screen_zindex = 0; } } #endif @@ -6477,6 +6548,11 @@ screen_line( redraw_this = TRUE; } #endif +#ifdef FEAT_TEXT_PROP + // Skip if under a(nother) popup. + if (popup_mask[row * screen_Columns + col + coloff] > screen_zindex) + redraw_this = FALSE; +#endif if (redraw_this) { @@ -6721,6 +6797,7 @@ screen_line( if (clear_width > 0 #ifdef FEAT_TEXT_PROP && !(flags & SLF_POPUP) // no separator for popup window + && popup_mask[row * screen_Columns + col + coloff] <= screen_zindex #endif ) { @@ -6821,11 +6898,6 @@ redraw_statuslines(void) win_redr_status(wp, FALSE); if (redraw_tabline) draw_tabline(); - -#ifdef FEAT_TEXT_PROP - // Display popup windows on top of the status lines. - update_popups(); -#endif } #if defined(FEAT_WILDMENU) || defined(PROTO) @@ -8577,9 +8649,21 @@ screen_char(unsigned off, int row, int c return; #ifdef FEAT_INS_EXPAND - if (pum_under_menu(row, col)) + // Skip if under the popup menu. + // Popup windows with zindex higher than POPUPMENU_ZINDEX go on top. + if (pum_under_menu(row, col) +# ifdef FEAT_TEXT_PROP + && screen_zindex <= POPUPMENU_ZINDEX +# endif + ) return; #endif +#ifdef FEAT_TEXT_PROP + // Skip if under a(nother) popup. + if (popup_mask[row * screen_Columns + col] > screen_zindex) + return; +#endif + /* Outputting a character in the last cell on the screen may scroll the * screen up. Only do it when the "xn" termcap property is set, otherwise * mark the character invalid (update it when scrolled up). */ @@ -8869,7 +8953,7 @@ screen_fill( c = c1; for (col = start_col; col < end_col; ++col) { - if (ScreenLines[off] != c + if ((ScreenLines[off] != c || (enc_utf8 && (int)ScreenLinesUC[off] != (c >= 0x80 ? c : 0)) || ScreenAttrs[off] != attr @@ -8877,6 +8961,11 @@ screen_fill( || force_next #endif ) +#ifdef FEAT_TEXT_PROP + // Skip if under a(nother) popup. + && popup_mask[row * screen_Columns + col] <= screen_zindex +#endif + ) { #if defined(FEAT_GUI) || defined(UNIX) /* The bold trick may make a single row of pixels appear in @@ -9013,6 +9102,9 @@ screenalloc(int doclear) unsigned *new_LineOffset; char_u *new_LineWraps; short *new_TabPageIdxs; +#ifdef FEAT_TEXT_PROP + short *new_popup_mask; +#endif tabpage_T *tp; static int entered = FALSE; /* avoid recursiveness */ static int done_outofmem_msg = FALSE; /* did outofmem message */ @@ -9094,6 +9186,9 @@ retry: new_LineOffset = LALLOC_MULT(unsigned, Rows); new_LineWraps = LALLOC_MULT(char_u, Rows); new_TabPageIdxs = LALLOC_MULT(short, Columns); +#ifdef FEAT_TEXT_PROP + new_popup_mask = LALLOC_MULT(short, Rows * Columns); +#endif FOR_ALL_TAB_WINDOWS(tp, wp) { @@ -9136,6 +9231,9 @@ give_up: || new_LineOffset == NULL || new_LineWraps == NULL || new_TabPageIdxs == NULL +#ifdef FEAT_TEXT_PROP + || new_popup_mask == NULL +#endif || outofmem) { if (ScreenLines != NULL || !done_outofmem_msg) @@ -9156,6 +9254,9 @@ give_up: VIM_CLEAR(new_LineOffset); VIM_CLEAR(new_LineWraps); VIM_CLEAR(new_TabPageIdxs); +#ifdef FEAT_TEXT_PROP + VIM_CLEAR(new_popup_mask); +#endif } else { @@ -9242,6 +9343,11 @@ give_up: LineOffset = new_LineOffset; LineWraps = new_LineWraps; TabPageIdxs = new_TabPageIdxs; +#ifdef FEAT_TEXT_PROP + popup_mask = new_popup_mask; + vim_memset(popup_mask, 0, screen_Rows * screen_Columns * sizeof(short)); + popup_mask_refresh = TRUE; +#endif /* It's important that screen_Rows and screen_Columns reflect the actual * size of ScreenLines[]. Set them before calling anything. */ @@ -9296,15 +9402,18 @@ free_screenlines(void) { int i; - vim_free(ScreenLinesUC); + VIM_CLEAR(ScreenLinesUC); for (i = 0; i < Screen_mco; ++i) - vim_free(ScreenLinesC[i]); - vim_free(ScreenLines2); - vim_free(ScreenLines); - vim_free(ScreenAttrs); - vim_free(LineOffset); - vim_free(LineWraps); - vim_free(TabPageIdxs); + VIM_CLEAR(ScreenLinesC[i]); + VIM_CLEAR(ScreenLines2); + VIM_CLEAR(ScreenLines); + VIM_CLEAR(ScreenAttrs); + VIM_CLEAR(LineOffset); + VIM_CLEAR(LineWraps); + VIM_CLEAR(TabPageIdxs); +#ifdef FEAT_TEXT_PROP + VIM_CLEAR(popup_mask); +#endif } void @@ -9429,6 +9538,7 @@ linecopy(int to, int from, win_T *wp) /* * Return TRUE if clearing with term string "p" would work. * It can't work when the string is empty or it won't set the right background. + * Don't clear to end-of-line when there are popups, it may cause flicker. */ int can_clear(char_u *p) @@ -9443,7 +9553,11 @@ can_clear(char_u *p) #else || cterm_normal_bg_color == 0 #endif - || *T_UT != NUL)); + || *T_UT != NUL) +#ifdef FEAT_TEXT_PROP + && !(p == T_CE && popup_visible) +#endif + ); } /* @@ -9891,22 +10005,26 @@ win_do_lines( if (!redrawing() || line_count <= 0) return FAIL; - /* When inserting lines would result in loss of command output, just redraw - * the lines. */ + // When inserting lines would result in loss of command output, just redraw + // the lines. if (no_win_do_lines_ins && !del) return FAIL; - /* only a few lines left: redraw is faster */ + // only a few lines left: redraw is faster if (mayclear && Rows - line_count < 5 && wp->w_width == Columns) { if (!no_win_do_lines_ins) - screenclear(); /* will set wp->w_lines_valid to 0 */ + screenclear(); // will set wp->w_lines_valid to 0 return FAIL; } - /* - * Delete all remaining lines - */ +#ifdef FEAT_TEXT_PROP + // this doesn't work when tere are popups visible + if (popup_visible) + return FAIL; +#endif + + // Delete all remaining lines if (row + line_count >= wp->w_height) { screen_fill(W_WINROW(wp) + row, W_WINROW(wp) + wp->w_height, @@ -10024,12 +10142,17 @@ screen_ins_lines( * - the line count is less than one * - the line count is more than 'ttyscroll' * - redrawing for a callback and there is a modeless selection + * - there is a popup window */ - if (!screen_valid(TRUE) || line_count <= 0 || line_count > p_ttyscroll + if (!screen_valid(TRUE) + || line_count <= 0 || line_count > p_ttyscroll #ifdef FEAT_CLIPBOARD || (clip_star.state != SELECT_CLEARED && redrawing_for_callback > 0) #endif +#ifdef FEAT_TEXT_PROP + || popup_visible +#endif ) return FAIL; @@ -11136,11 +11259,6 @@ showruler(int always) /* Redraw the tab pages line if needed. */ if (redraw_tabline) draw_tabline(); - -#ifdef FEAT_TEXT_PROP - // Display popup windows on top of everything. - update_popups(); -#endif } #ifdef FEAT_CMDL_INFO diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -768,6 +768,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1493, +/**/ 1492, /**/ 1491,