mirror of
https://github.com/neovim/neovim.git
synced 2026-07-10 03:19:44 +00:00
fix(autocmd): never draw aucmd_prepbuf temp win #40379
Problem: An autocommand that redraws may do so while curwin is
temporarily set for the autocommand scope. This can result in
flickering or unexpected state with UI components (statusline,
winbar, decor providers...) that depend on the current window.
Current workaround for statusline and winbar specifically
delays the redraw, which can itself be unexpected for the
autocommand.
Solution: If redrawing happens with a temporary autocmd current window,
temporarily restore the current window while redrawing.
This commit is contained in:
@@ -1347,15 +1347,16 @@ void aucmd_prepbuf(aco_save_T *aco, buf_T *buf)
|
||||
curbuf = buf;
|
||||
aco->new_curwin_handle = curwin->handle;
|
||||
set_bufref(&aco->new_curbuf, curbuf);
|
||||
if (aco->new_curwin_handle != aco->save_curwin_handle) {
|
||||
aucmd_prepbuf_depth++;
|
||||
}
|
||||
|
||||
aco->save_VIsual_active = VIsual_active;
|
||||
if (!same_buffer) {
|
||||
// disable the Visual area, position may be invalid in another buffer
|
||||
VIsual_active = false;
|
||||
}
|
||||
if (autocmd_save.save_aucmd == NULL) {
|
||||
autocmd_save = *aco;
|
||||
autocmd_save.save_aucmd = aco;
|
||||
}
|
||||
}
|
||||
|
||||
/// Cleanup after executing autocommands for a (hidden) buffer.
|
||||
@@ -1378,6 +1379,9 @@ void aucmd_restbuf(aco_save_T *aco)
|
||||
if (aco->new_curbuf.br_buf == NULL) {
|
||||
return;
|
||||
}
|
||||
if (autocmd_save.save_aucmd == aco) {
|
||||
autocmd_save.save_aucmd = NULL;
|
||||
}
|
||||
|
||||
if (aco->use_aucmd_win_idx >= 0) {
|
||||
win_T *awp = aucmd_win[aco->use_aucmd_win_idx].auc_win;
|
||||
@@ -1468,7 +1472,6 @@ win_found:
|
||||
curbuf->b_nwindows++;
|
||||
}
|
||||
|
||||
curwin->w_redr_status = true;
|
||||
curwin = save_curwin;
|
||||
curbuf = curwin->w_buffer;
|
||||
prevwin = win_find_by_handle(aco->save_prevwin_handle);
|
||||
@@ -1485,10 +1488,6 @@ win_found:
|
||||
if (VIsual_active) {
|
||||
check_pos(curbuf, &VIsual);
|
||||
}
|
||||
if (aco->new_curwin_handle != aco->save_curwin_handle) {
|
||||
assert(aucmd_prepbuf_depth > 0);
|
||||
aucmd_prepbuf_depth--;
|
||||
}
|
||||
}
|
||||
|
||||
/// Schedules an autocommand event, to be executed at the next event-loop tick.
|
||||
|
||||
@@ -22,7 +22,6 @@ EXTERN win_T *last_cursormoved_win INIT( = NULL);
|
||||
EXTERN pos_T last_cursormoved INIT( = { 0, 0, 0 });
|
||||
|
||||
EXTERN bool autocmd_busy INIT( = false); ///< Is apply_autocmds() busy?
|
||||
EXTERN int aucmd_prepbuf_depth INIT( = 0); ///< Nested aucmd_prepbuf() window switches
|
||||
EXTERN int autocmd_no_enter INIT( = false); ///< Buf/WinEnter autocmds disabled
|
||||
EXTERN int autocmd_no_leave INIT( = false); ///< Buf/WinLeave autocmds disabled
|
||||
|
||||
@@ -42,6 +41,8 @@ EXTERN bool autocmd_fname_full INIT( = false); ///< autocmd_fname is full path
|
||||
EXTERN int autocmd_bufnr INIT( = 0); ///< fnum for <abuf> on cmdline
|
||||
EXTERN char *autocmd_match INIT( = NULL); ///< name for <amatch> on cmdline
|
||||
EXTERN bool did_cursorhold INIT( = true); ///< set when CursorHold t'gerd
|
||||
// Used to restore the actual current buffer/window when redrawing.
|
||||
EXTERN aco_save_T autocmd_save INIT( = { 0 });
|
||||
|
||||
typedef struct {
|
||||
win_T *auc_win; ///< Window used in aucmd_prepbuf(). When not NULL the
|
||||
|
||||
@@ -9,9 +9,10 @@
|
||||
|
||||
#include "auevents_enum.generated.h"
|
||||
|
||||
typedef struct aco_save_S aco_save_T;
|
||||
/// Struct to save values in before executing autocommands for a buffer that is
|
||||
/// not the current buffer.
|
||||
typedef struct {
|
||||
struct aco_save_S {
|
||||
int use_aucmd_win_idx; ///< index in aucmd_win[] if >= 0
|
||||
handle_T save_curwin_handle; ///< ID of saved curwin
|
||||
handle_T new_curwin_handle; ///< ID of new curwin
|
||||
@@ -21,7 +22,8 @@ typedef struct {
|
||||
char *globaldir; ///< saved value of globaldir
|
||||
bool save_VIsual_active; ///< saved VIsual_active
|
||||
int save_prompt_insert; ///< saved b_prompt_insert
|
||||
} aco_save_T;
|
||||
aco_save_T *save_aucmd;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
size_t refcount; ///< Reference count (freed when reaches zero)
|
||||
|
||||
@@ -465,6 +465,11 @@ int update_screen(void)
|
||||
return FAIL;
|
||||
}
|
||||
|
||||
// Restore actual curwin before redrawing.
|
||||
if (autocmd_save.save_aucmd != NULL && curwin->handle != autocmd_save.save_curwin_handle) {
|
||||
aucmd_restbuf(&autocmd_save);
|
||||
}
|
||||
|
||||
int type = must_redraw;
|
||||
|
||||
// must_redraw is reset here, so that when we run into some weird
|
||||
@@ -740,6 +745,12 @@ int update_screen(void)
|
||||
if (!ui_has(kUICmdline)) {
|
||||
cmdline_was_last_drawn = false;
|
||||
}
|
||||
|
||||
// Restore temporary autocmd curwin.
|
||||
if (autocmd_save.save_aucmd != NULL && curwin->handle != autocmd_save.new_curwin_handle) {
|
||||
aucmd_prepbuf(&autocmd_save, autocmd_save.new_curbuf.br_buf);
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -63,18 +63,6 @@ typedef enum {
|
||||
kNumBaseHexadecimal = 16,
|
||||
} NumberBase;
|
||||
|
||||
static bool stl_defer_redraw(win_T *wp)
|
||||
{
|
||||
if (aucmd_prepbuf_depth == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// aucmd_prepbuf() temporarily changes curwin/curbuf. Statusline and winbar
|
||||
// expressions can observe that context, so evaluate them after aucmd_restbuf().
|
||||
wp->w_redr_status = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Redraw the status line of window `wp`.
|
||||
///
|
||||
/// If inversion is possible we use it. Else '=' characters are used.
|
||||
@@ -90,9 +78,7 @@ void win_redr_status(win_T *wp)
|
||||
|| (wild_menu_showing != 0 && !ui_has(kUIWildmenu))) {
|
||||
return;
|
||||
}
|
||||
if (stl_defer_redraw(wp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
busy = true;
|
||||
wp->w_redr_status = false;
|
||||
if (wp->w_status_height == 0 && !(is_stl_global && wp == curwin)) {
|
||||
@@ -260,6 +246,11 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler, bool u
|
||||
}
|
||||
entered = true;
|
||||
|
||||
// Restore actual curwin before redrawing.
|
||||
if (autocmd_save.save_aucmd != NULL && curwin->handle != autocmd_save.save_curwin_handle) {
|
||||
aucmd_restbuf(&autocmd_save);
|
||||
}
|
||||
|
||||
// setup environment for the task at hand
|
||||
if (wp == NULL) {
|
||||
// Use 'tabline'. Always at the first line of the screen.
|
||||
@@ -437,6 +428,11 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler, bool u
|
||||
|
||||
theend:
|
||||
entered = false;
|
||||
|
||||
// Restore temporary autocmd curwin.
|
||||
if (autocmd_save.save_aucmd != NULL && curwin->handle == autocmd_save.new_curwin_handle) {
|
||||
aucmd_prepbuf(&autocmd_save, autocmd_save.new_curbuf.br_buf);
|
||||
}
|
||||
}
|
||||
|
||||
void win_redr_winbar(win_T *wp)
|
||||
@@ -448,9 +444,6 @@ void win_redr_winbar(win_T *wp)
|
||||
if (entered) {
|
||||
return;
|
||||
}
|
||||
if (stl_defer_redraw(wp)) {
|
||||
return;
|
||||
}
|
||||
entered = true;
|
||||
|
||||
if (wp->w_winbar_height == 0 || !redrawing()) {
|
||||
|
||||
@@ -1207,4 +1207,21 @@ describe('statuscolumn', function()
|
||||
|
|
||||
]])
|
||||
end)
|
||||
|
||||
it('redrawn during nvim_exec_autocmds({buf})', function()
|
||||
command([[let &statuscolumn='%{g:actual_curwin == win_getid() ? "CUR" : "NC"}']])
|
||||
local buf = api.nvim_create_buf(true, false)
|
||||
api.nvim_open_win(buf, false, { split = 'right' })
|
||||
api.nvim_create_autocmd('User', { command = 'redraw!' })
|
||||
screen:expect([[
|
||||
{8:CUR}aaaaa │{8:NC} |
|
||||
{8:CUR}aaaaa │{1:~ }|*3
|
||||
{8:CUR}^aaaaa │{1:~ }|
|
||||
{8:CUR}aaaaa │{1:~ }|*7
|
||||
{3:[No Name] [+] }{2:[No Name] }|
|
||||
|
|
||||
]])
|
||||
api.nvim_exec_autocmds('User', { buf = buf })
|
||||
screen:expect_unchanged()
|
||||
end)
|
||||
end)
|
||||
|
||||
@@ -959,15 +959,13 @@ describe('statusline', function()
|
||||
it('no cmdline ruler for autocmd window #39938', function()
|
||||
command('set ruler laststatus=2')
|
||||
api.nvim_create_autocmd('BufDelete', { command = 'redrawstatus' })
|
||||
local expected = [[
|
||||
screen:expect([[
|
||||
^ |
|
||||
{1:~ }|*5
|
||||
{3:[No Name] 0,0-1 All}|
|
||||
|
|
||||
]]
|
||||
screen:expect(expected)
|
||||
]])
|
||||
api.nvim_exec_autocmds('BufDelete', { buf = api.nvim_create_buf(true, true) })
|
||||
-- The first matching frame can arrive before the deferred redraw settles.
|
||||
screen:expect_unchanged(true)
|
||||
end)
|
||||
|
||||
@@ -1029,7 +1027,7 @@ describe('statusline', function()
|
||||
)
|
||||
end)
|
||||
|
||||
it('defers statusline evaluation during nvim_exec_autocmds({buf}) #40153', function()
|
||||
it('statusline evaluation during nvim_exec_autocmds({buf}) #40153', function()
|
||||
command('set laststatus=2')
|
||||
local caller_win = api.nvim_get_current_win()
|
||||
command('split')
|
||||
@@ -1071,25 +1069,12 @@ describe('statusline', function()
|
||||
-- Ignore statusline evaluations from setup; only the autocmd redraw matters.
|
||||
exec_lua('_G.statusline_contexts = {}')
|
||||
api.nvim_exec_autocmds('User', { buf = target })
|
||||
-- No different frame should be flushed while the deferred redraw settles.
|
||||
screen:expect({ unchanged = true })
|
||||
local contexts = exec_lua(function()
|
||||
return _G.statusline_contexts
|
||||
end)
|
||||
eq(true, #contexts > 0)
|
||||
eq(caller_win, contexts[#contexts].actual)
|
||||
-- The statusline must be evaluated only after the autocmd context is restored.
|
||||
eq(
|
||||
{},
|
||||
exec_lua(function()
|
||||
return vim
|
||||
.iter(_G.statusline_contexts)
|
||||
:filter(function(context)
|
||||
return context.in_aucmd
|
||||
end)
|
||||
:totable()
|
||||
end)
|
||||
)
|
||||
end)
|
||||
end)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user