Bram,
I am maintaining a eye-candy statusline plugin, that can also create a
nice tabline. I recently made a change, trying to improve the
performance of the plugin by caching certain values
(
https://github.com/vim-airline/vim-airline/commit/b2f301f73c168104cf2202ac5f2e2b7c078c7aa1)
The issue can be reproduced with this little vim snippet:
,----
| function! TabTitle(n)
| let title = gettabvar(a:n, 'title')
| if empty(title)
| " call an expensive function to get the title
| " try to avoid the expensive call, by caching the title
| let title='foobar'
| endif
| "if a:n == tabpagenr()
| " let t:title = title
| "endif
| call settabvar(a:n, 'title', title)
| return title
| endfunction
|
| function! DrawTabline()
| let tabline = ''
| for i in range(1, tabpagenr('$'))
| let tabline .= "%(Tab: ".i.' "%{TabTitle('.i.')}"%)'
| endfor
| return tabline
| endfunction
| set tabline=%!DrawTabline()
`----
However, it turns out, that this causes flicker on movement and so makes
Vim redraw a lot more often than actually needed. I looked into the
source, trying to find out, why this happens and I think it happens
because settabvar does internally switch tabpages (e.g. calling
enter_tabpage) which unconditionally set must_redraw=CLEAR.
However this happens on a call to draw_tabline() which is done when we
already updating the screen (e.g. in a callchain coming from
update_scree()). So I think we can prevent this by only setting
must_redraw, if we are not updating the screen currently, which is what
this patch does:
diff --git a/src/window.c b/src/window.c
index 8078e011d..785d240e8 100644
--- a/src/window.c
+++ b/src/window.c
@@ -3925,7 +3925,8 @@ enter_tabpage(
last_status(FALSE); /* status line may appear or disappear */
(void)win_comp_pos(); /* recompute w_winrow for all windows */
- must_redraw = CLEAR; /* need to redraw everything */
+ if (!updating_screen)
+ must_redraw = CLEAR; /* need to redraw everything */
#ifdef FEAT_DIFF
diff_need_scrollbind = TRUE;
#endif
On a second thought, it might be better not to set must_redraw for
settabvar() at all, since after we are finished, we switch back the
tabpage, and thus theoretically a redraw should not be necessary. So
perhaps this patch is preferable:
diff --git a/src/evalfunc.c b/src/evalfunc.c
index 2d796f7e1..4b8f95e50 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -10385,6 +10385,7 @@ f_settabvar(typval_T *argvars, typval_T *rettv)
#endif
char_u *varname, *tabvarname;
typval_T *varp;
+ int redraw;
rettv->vval.v_number = 0;
@@ -10404,6 +10405,7 @@ f_settabvar(typval_T *argvars, typval_T *rettv)
)
{
#ifdef FEAT_WINDOWS
+ redraw = must_redraw;
save_curtab = curtab;
goto_tabpage_tp(tp, FALSE, FALSE);
#endif
@@ -10421,6 +10423,7 @@ f_settabvar(typval_T *argvars, typval_T *rettv)
/* Restore current tabpage */
if (valid_tabpage(save_curtab))
goto_tabpage_tp(save_curtab, FALSE, FALSE);
+ must_redraw = redraw; /* avoid an unnecessary redraw */
#endif
}
}
And finally, I made another observation, that looks suspicious as well.
When update_screen() is called, it will eventually call this code, if
type==CLEAR:
,----
| if (type == CLEAR) /* first clear screen */
| {
| screenclear(); /* will reset clear_cmdline */
| type = NOT_VALID;
| }
`----
screenclear() will then call screenclear2() which will call win_rest_invalid(firstwin)
which will then call
,----
| redraw_win_later(wp, NOT_VALID);
`----
So although we are in the process of updating the current screen, we
will force another redraw after the next main_loop. I think we can save
that call here, and only call redraw_win_later if updating_screen is not
true.
Best,
Christian
--
Moral ist eine Wichtigtuerei des Menschen vor der Natur.
-- Friedrich Wilhelm Nietzsche