mirror of
https://github.com/neovim/neovim
synced 2025-07-16 01:01:49 +00:00
fix(api): make open_win block only enter/leave events if !enter && !noautocmd
Problem: nvim_open_win blocking all win_set_buf autocommands when !enter && !noautocmd is too aggressive. Solution: temporarily block WinEnter/Leave and BufEnter/Leave events when setting the buffer. Delegate the firing of BufWinEnter back to win_set_buf, which also has the advantage of keeping the timing consistent (e.g: before the epilogue in enter_buffer, which also handles restoring the cursor position if autocommands didn't change it, among other things). Reword the documentation for noautocmd a bit. I pondered modifying do_buffer and callees to allow for BufEnter/Leave being conditionally disabled, but it seems too invasive (and potentially error-prone, especially if new code paths to BufEnter/Leave are added in the future). Unfortunately, doing this has the drawback of blocking ALL such events for the duration, which also means blocking unrelated such events; like if window switching occurs in a ++nested autocmd fired by win_set_buf. If this turns out to be a problem in practice, a different solution specialized for nvim_open_win could be considered. :-)
This commit is contained in:
@ -3265,9 +3265,9 @@ nvim_open_win({buffer}, {enter}, {config}) *nvim_open_win()*
|
|||||||
• footer_pos: Footer position. Must be set with `footer`
|
• footer_pos: Footer position. Must be set with `footer`
|
||||||
option. Value can be one of "left", "center", or "right".
|
option. Value can be one of "left", "center", or "right".
|
||||||
Default is `"left"`.
|
Default is `"left"`.
|
||||||
• noautocmd: If true then no buffer-related autocommand
|
• noautocmd: If true then autocommands triggered from
|
||||||
events such as |BufEnter|, |BufLeave| or |BufWinEnter| may
|
setting the `buffer` to display are blocked (e.g:
|
||||||
fire from calling this function.
|
|BufEnter|, |BufLeave|, |BufWinEnter|).
|
||||||
• fixed: If true when anchor is NW or SW, the float window
|
• fixed: If true when anchor is NW or SW, the float window
|
||||||
would be kept fixed even if the window would be truncated.
|
would be kept fixed even if the window would be truncated.
|
||||||
• hide: If true the floating window will be hidden.
|
• hide: If true the floating window will be hidden.
|
||||||
|
6
runtime/lua/vim/_meta/api.lua
generated
6
runtime/lua/vim/_meta/api.lua
generated
@ -1719,9 +1719,9 @@ function vim.api.nvim_open_term(buffer, opts) end
|
|||||||
--- • footer_pos: Footer position. Must be set with `footer`
|
--- • footer_pos: Footer position. Must be set with `footer`
|
||||||
--- option. Value can be one of "left", "center", or "right".
|
--- option. Value can be one of "left", "center", or "right".
|
||||||
--- Default is `"left"`.
|
--- Default is `"left"`.
|
||||||
--- • noautocmd: If true then no buffer-related autocommand
|
--- • noautocmd: If true then autocommands triggered from
|
||||||
--- events such as `BufEnter`, `BufLeave` or `BufWinEnter` may
|
--- setting the `buffer` to display are blocked (e.g:
|
||||||
--- fire from calling this function.
|
--- `BufEnter`, `BufLeave`, `BufWinEnter`).
|
||||||
--- • fixed: If true when anchor is NW or SW, the float window
|
--- • fixed: If true when anchor is NW or SW, the float window
|
||||||
--- would be kept fixed even if the window would be truncated.
|
--- would be kept fixed even if the window would be truncated.
|
||||||
--- • hide: If true the floating window will be hidden.
|
--- • hide: If true the floating window will be hidden.
|
||||||
|
@ -199,9 +199,9 @@
|
|||||||
/// - footer_pos: Footer position. Must be set with `footer` option.
|
/// - footer_pos: Footer position. Must be set with `footer` option.
|
||||||
/// Value can be one of "left", "center", or "right".
|
/// Value can be one of "left", "center", or "right".
|
||||||
/// Default is `"left"`.
|
/// Default is `"left"`.
|
||||||
/// - noautocmd: If true then no buffer-related autocommand events such as
|
/// - noautocmd: If true then autocommands triggered from setting the
|
||||||
/// |BufEnter|, |BufLeave| or |BufWinEnter| may fire from
|
/// `buffer` to display are blocked (e.g: |BufEnter|, |BufLeave|,
|
||||||
/// calling this function.
|
/// |BufWinEnter|).
|
||||||
/// - fixed: If true when anchor is NW or SW, the float window
|
/// - fixed: If true when anchor is NW or SW, the float window
|
||||||
/// would be kept fixed even if the window would be truncated.
|
/// would be kept fixed even if the window would be truncated.
|
||||||
/// - hide: If true the floating window will be hidden.
|
/// - hide: If true the floating window will be hidden.
|
||||||
@ -302,20 +302,20 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Err
|
|||||||
tp = win_find_tabpage(wp);
|
tp = win_find_tabpage(wp);
|
||||||
}
|
}
|
||||||
if (tp && bufref_valid(&bufref) && buf != wp->w_buffer) {
|
if (tp && bufref_valid(&bufref) && buf != wp->w_buffer) {
|
||||||
const bool noautocmd = curwin != wp || fconfig.noautocmd;
|
// win_set_buf temporarily makes `wp` the curwin to set the buffer.
|
||||||
win_set_buf(wp, buf, noautocmd, err);
|
// If not entering `wp`, block Enter and Leave events. (cringe)
|
||||||
if (!noautocmd) {
|
const bool au_no_enter_leave = curwin != wp && !fconfig.noautocmd;
|
||||||
|
if (au_no_enter_leave) {
|
||||||
|
autocmd_no_enter++;
|
||||||
|
autocmd_no_leave++;
|
||||||
|
}
|
||||||
|
win_set_buf(wp, buf, fconfig.noautocmd, err);
|
||||||
|
if (!fconfig.noautocmd) {
|
||||||
tp = win_find_tabpage(wp);
|
tp = win_find_tabpage(wp);
|
||||||
}
|
}
|
||||||
// win_set_buf autocommands were blocked if we didn't enter, but we still want BufWinEnter.
|
if (au_no_enter_leave) {
|
||||||
if (noautocmd && !fconfig.noautocmd && wp->w_buffer == buf) {
|
autocmd_no_enter--;
|
||||||
const int result = switch_win_noblock(&switchwin, wp, tp, true);
|
autocmd_no_leave--;
|
||||||
assert(result == OK);
|
|
||||||
(void)result;
|
|
||||||
if (apply_autocmds(EVENT_BUFWINENTER, NULL, NULL, false, buf)) {
|
|
||||||
tp = win_find_tabpage(wp);
|
|
||||||
}
|
|
||||||
restore_win_noblock(&switchwin, true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!tp) {
|
if (!tp) {
|
||||||
|
@ -1620,6 +1620,40 @@ describe('API/win', function()
|
|||||||
)
|
)
|
||||||
command('new | quit')
|
command('new | quit')
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it('restores last known cursor position if BufWinEnter did not move it', function()
|
||||||
|
-- This test mostly exists to ensure BufWinEnter is executed before enter_buffer's epilogue.
|
||||||
|
local buf = api.nvim_get_current_buf()
|
||||||
|
insert([[
|
||||||
|
foo
|
||||||
|
bar baz .etc
|
||||||
|
i love autocommand bugs!
|
||||||
|
supercalifragilisticexpialidocious
|
||||||
|
marvim is actually a human
|
||||||
|
llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch
|
||||||
|
]])
|
||||||
|
api.nvim_win_set_cursor(0, { 5, 2 })
|
||||||
|
command('set nostartofline | enew')
|
||||||
|
local new_win = api.nvim_open_win(buf, false, { split = 'left' })
|
||||||
|
eq({ 5, 2 }, api.nvim_win_get_cursor(new_win))
|
||||||
|
|
||||||
|
exec([[
|
||||||
|
only!
|
||||||
|
autocmd BufWinEnter * ++once normal! j6l
|
||||||
|
]])
|
||||||
|
new_win = api.nvim_open_win(buf, false, { split = 'left' })
|
||||||
|
eq({ 2, 6 }, api.nvim_win_get_cursor(new_win))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('does not block all win_set_buf autocommands if !enter and !noautocmd', function()
|
||||||
|
local new_buf = fn.bufadd('foobarbaz')
|
||||||
|
exec([[
|
||||||
|
let triggered = ""
|
||||||
|
autocmd BufReadCmd * ++once let triggered = bufname()
|
||||||
|
]])
|
||||||
|
api.nvim_open_win(new_buf, false, { split = 'left' })
|
||||||
|
eq('foobarbaz', eval('triggered'))
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
describe('set_config', function()
|
describe('set_config', function()
|
||||||
|
Reference in New Issue
Block a user