最近在修改 notify.nvim 插件源码的时候有这么一段:
local win_config = {
-- .....
}
if not vim.api.nvim_buf_is_valid(buf) then
buf = vim.api.nvim_create_buf(false, true)
end
if not vim.api.nvim_win_is_valid(win) then
win_config.noautocmd = true
win = vim.api.nvim_open_win(buf, false, win_config)
else
vim.api.nvim_win_set_config(win, win_config)
end
我把 win_config 内容省略了,主要想表达的意思是,
本来我想通过一个固定的窗口配置来打开或者设置窗口的参数。
但是 Neovim 的 nvim_open_win 和 nvim_win_set_config
两个函数所接受的 win_opt 选项是有区别的,已存在的窗口使用后者设置时不能够传入 noautocmd 选项(neovim#36409)。
'noautocmd' cannot be used with existing windows
于是只能分开写,在调用 nvim_open_win 时传入 noautocmd 参数。
那么这个 noautocmd 到底禁用了哪些事件,以及禁用的时机时什么呢?
以下为测试脚本:
local buf = vim.api.nvim_create_buf(true, false)
local log = require('logger').derive('t_no')
local aug = vim.api.nvim_create_augroup('test_noautocmd', { clear = true })
vim.api.nvim_create_autocmd(
{ 'WinEnter', 'BufWinEnter', 'BufEnter', 'WinLeave', 'TextChangedI' },
{
pattern = { '*' },
group = aug,
callback = function(ev)
log.info(ev.event)
end,
}
)
vim.api.nvim_open_win(buf, true, { split = 'above', noautocmd = true })
-- [ 20:43:20:664 ] [ Info ] [ t_no ] TextChangedI
-- [ 20:43:23:092 ] [ Info ] [ t_no ] WinLeave
-- [ 20:43:23:093 ] [ Info ] [ t_no ] WinEnter
-- [ 20:43:23:094 ] [ Info ] [ t_no ] BufEnter
local buf = vim.api.nvim_create_buf(true, false)
local log = require('logger').derive('t_no')
local aug = vim.api.nvim_create_augroup('test_noautocmd', { clear = true })
vim.api.nvim_create_autocmd(
{ 'WinEnter', 'BufWinEnter', 'BufEnter', 'WinLeave', 'TextChangedI' },
{
pattern = { '*' },
group = aug,
callback = function(ev)
log.info(ev.event)
end,
}
)
vim.api.nvim_open_win(buf, true, { split = 'above', noautocmd = false })
-- [ 20:44:50:454 ] [ Info ] [ t_no ] WinLeave
-- [ 20:44:50:455 ] [ Info ] [ t_no ] WinEnter
-- [ 20:44:50:456 ] [ Info ] [ t_no ] BufEnter
-- [ 20:44:50:456 ] [ Info ] [ t_no ] BufWinEnter
-- [ 20:44:51:279 ] [ Info ] [ t_no ] TextChangedI
-- [ 20:44:52:045 ] [ Info ] [ t_no ] WinLeave
-- [ 20:44:52:046 ] [ Info ] [ t_no ] WinEnter
-- [ 20:44:52:048 ] [ Info ] [ t_no ] BufEnter
如果去看 API 的源码,neovim 这段 nvim_open_win api 的源码。
Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Error *err)
FUNC_API_SINCE(6) FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
#define HAS_KEY_X(d, key) HAS_KEY(d, win_config, key)
buf_T *buf = find_buffer_by_handle(buffer, err);
if (!buf) {
return 0;
}
if ((cmdwin_type != 0 && enter) || buf == cmdwin_buf) {
api_set_error(err, kErrorTypeException, "%s", e_cmdwin);
return 0;
}
WinConfig fconfig = WIN_CONFIG_INIT;
if (!parse_win_config(NULL, config, &fconfig, false, err)) {
return 0;
}
bool is_split = HAS_KEY_X(config, split) || HAS_KEY_X(config, vertical);
Window rv = 0;
if (fconfig.noautocmd) {
block_autocmds();
}
win_T *wp = NULL;
tabpage_T *tp = curtab;
assert(curwin != NULL);
win_T *parent = config->win == 0 ? curwin : NULL;
if (config->win > 0) {
parent = find_window_by_handle(fconfig.window, err);
if (!parent) {
// find_window_by_handle has already set the error
goto cleanup;
} else if (is_split && parent->w_floating) {
api_set_error(err, kErrorTypeException, "Cannot split a floating window");
goto cleanup;
}
tp = win_find_tabpage(parent);
}
if (is_split) {
if (!check_split_disallowed_err(parent ? parent : curwin, err)) {
goto cleanup; // error already set
}
if (HAS_KEY_X(config, vertical) && !HAS_KEY_X(config, split)) {
if (config->vertical) {
fconfig.split = p_spr ? kWinSplitRight : kWinSplitLeft;
} else {
fconfig.split = p_sb ? kWinSplitBelow : kWinSplitAbove;
}
}
int flags = win_split_flags(fconfig.split, parent == NULL) | WSP_NOENTER;
int size = (flags & WSP_VERT) ? fconfig.width : fconfig.height;
TRY_WRAP(err, {
if (parent == NULL || parent == curwin) {
wp = win_split_ins(size, flags, NULL, 0, NULL);
} else {
switchwin_T switchwin;
// `parent` is valid in `tp`, so switch_win should not fail.
const int result = switch_win(&switchwin, parent, tp, true);
assert(result == OK);
(void)result;
wp = win_split_ins(size, flags, NULL, 0, NULL);
restore_win(&switchwin, true);
}
});
if (wp) {
wp->w_config = fconfig;
if (size > 0) {
// Without room for the requested size, window sizes may have been equalized instead.
// If the size differs from what was requested, try to set it again now.
if ((flags & WSP_VERT) && wp->w_width != size) {
win_setwidth_win(size, wp);
} else if (!(flags & WSP_VERT) && wp->w_height != size) {
win_setheight_win(size, wp);
}
}
}
} else {
if (!check_split_disallowed_err(curwin, err)) {
goto cleanup; // error already set
}
wp = win_new_float(NULL, false, fconfig, err);
}
if (!wp) {
if (!ERROR_SET(err)) {
api_set_error(err, kErrorTypeException, "Failed to create window");
}
goto cleanup;
}
if (fconfig._cmdline_offset < INT_MAX) {
cmdline_win = wp;
}
// Autocommands may close `wp` or move it to another tabpage, so update and check `tp` after each
// event. In each case, `wp` should already be valid in `tp`, so switch_win should not fail.
// Also, autocommands may free the `buf` to switch to, so store a bufref to check.
bufref_T bufref;
set_bufref(&bufref, buf);
if (!fconfig.noautocmd) {
switchwin_T switchwin;
const int result = switch_win_noblock(&switchwin, wp, tp, true);
assert(result == OK);
(void)result;
if (apply_autocmds(EVENT_WINNEW, NULL, NULL, false, curbuf)) {
tp = win_find_tabpage(wp);
}
restore_win_noblock(&switchwin, true);
}
if (tp && enter) {
goto_tabpage_win(tp, wp);
tp = win_find_tabpage(wp);
}
if (tp && bufref_valid(&bufref) && buf != wp->w_buffer) {
// win_set_buf temporarily makes `wp` the curwin to set the buffer.
// If not entering `wp`, block Enter and Leave events. (cringe)
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, err);
if (!fconfig.noautocmd) {
tp = win_find_tabpage(wp);
}
if (au_no_enter_leave) {
autocmd_no_enter--;
autocmd_no_leave--;
}
}
if (!tp) {
api_set_error(err, kErrorTypeException, "Window was closed immediately");
goto cleanup;
}
if (fconfig.style == kWinStyleMinimal) {
win_set_minimal_style(wp);
didset_window_options(wp, true);
}
rv = wp->handle;
cleanup:
if (fconfig.noautocmd) {
unblock_autocmds();
}
return rv;
#undef HAS_KEY_X
}
从源码中不难看出,这个 noautocmd 选项只是在 nvim_open_win 这个函数调用内起作用,在最后的时候使用 unblock_autocmds 又恢复的事件的响应。