前面再阅读一些插件源码时,发现一个问题,很多插件的使用了 ftplugin 这个目录,其内的脚本文件中直接使用了 setlocal xx=xx 这样的语法。
在早期的 Neovim 或者 Vim 版本中这样确实没有问题,但是随着 Neovim 功能特性增加。这样写就会容易出错。
实际上,直到目前为止 Neovim 和 Vim 的官方文档 :h ftplugin 内的示例还是:
" Only do this when not done yet for this buffer
if exists("b:did_ftplugin")
finish
endif
let b:did_ftplugin = 1
setlocal textwidth=70
Neovim 插件的 ftplugin 目录是一个特殊的文件夹,其内的文件会在 FileType 事件触发是被载入。
看一下 Neovim 的源码,ftplugin 目录下的文件是如何被载入的。
augroup filetypeplugin
au FileType * call s:LoadFTPlugin()
func! s:LoadFTPlugin()
if exists("b:undo_ftplugin")
exe b:undo_ftplugin
unlet! b:undo_ftplugin b:did_ftplugin
endif
let s = expand("<amatch>")
if s != ""
if &cpo =~# "S" && exists("b:did_ftplugin")
" In compatible mode options are reset to the global values, need to
" set the local values also when a plugin was already used.
unlet b:did_ftplugin
endif
" When there is a dot it is used to separate filetype names. Thus for
" "aaa.bbb" load "aaa" and then "bbb".
for name in split(s, '\.')
" Load Lua ftplugins after Vim ftplugins _per directory_
" TODO(clason): use nvim__get_runtime when supports globs and modeline
" XXX: "[.]" in the first pattern makes it a wildcard on Windows
exe $'runtime! ftplugin/{name}[.] ftplugin/{name}_*. ftplugin/{name}/*.'
endfor
endif
endfunc
augroup END
以上内容不难看出,Neovim 实际上是监听了 FileType 这个事件,然后根据 expand('<amatch>') 的值来执行 :runtime 命令。
但是,随着 Neovim 和 Vim 增加了设置非当前 buffer 的 option 这一功能后。就会出现这样问题,当 FileType 事件触发时,触发的 buffer 并非是当前 buffer。
那么在 ftplugin 内如果使用了 setlocal 这样的命令,有可能会设置错了缓冲区。
test_ft.lua
local log = require("logger").derive("ft")
log.info("nvim_get_current_buf() is " .. vim.api.nvim_get_current_buf())
log.info("nvim_get_current_win() is " .. vim.api.nvim_get_current_win())
log.info("-----------------------------------------------------")
local real_current_win = vim.api.nvim_get_current_win()
local newbuf = vim.api.nvim_create_buf(true, false)
local events = {}
for _, v in ipairs(vim.fn.getcompletion("", "event")) do
if not vim.endswith(v, "Cmd") then
table.insert(events, v)
end
end
local id = vim.api.nvim_create_autocmd(events, {
group = vim.api.nvim_create_augroup("test_ft", { clear = true }),
pattern = { "*" },
callback = function(ev)
log.info("-----------------------------------------------------")
log.info("event is " .. ev.event)
log.info("ev.buf is " .. ev.buf)
log.info("nvim_get_current_buf() is " .. vim.api.nvim_get_current_buf())
log.info("nvim_get_current_win() is " .. vim.api.nvim_get_current_win())
log.info("real_current_win's buf is" .. vim.api.nvim_win_get_buf(real_current_win))
end,
})
vim.api.nvim_open_win(newbuf, false, { split = "above" })
vim.api.nvim_set_option_value("filetype", "test123", { buf = newbuf })
vim.api.nvim_del_autocmd(id)
log.info("-----------------------------------------------------")
log.info("nvim_get_current_buf() is " .. vim.api.nvim_get_current_buf())
log.info("nvim_get_current_win() is " .. vim.api.nvim_get_current_win())
[ 23:50:19:932 ] [ Info ] [ ft ] nvim_get_current_buf() is 7
[ 23:50:19:932 ] [ Info ] [ ft ] nvim_get_current_win() is 1000
[ 23:50:19:932 ] [ Info ] [ ft ] -----------------------------------------------------
[ 23:50:19:933 ] [ Info ] [ ft ] -----------------------------------------------------
[ 23:50:19:933 ] [ Info ] [ ft ] event is WinNew
[ 23:50:19:933 ] [ Info ] [ ft ] ev.buf is 7
[ 23:50:19:933 ] [ Info ] [ ft ] nvim_get_current_buf() is 7
[ 23:50:19:933 ] [ Info ] [ ft ] nvim_get_current_win() is 1008
[ 23:50:19:933 ] [ Info ] [ ft ] real_current_win's buf is7
[ 23:50:19:934 ] [ Info ] [ ft ] -----------------------------------------------------
[ 23:50:19:934 ] [ Info ] [ ft ] event is BufWinEnter
[ 23:50:19:934 ] [ Info ] [ ft ] ev.buf is 9
[ 23:50:19:934 ] [ Info ] [ ft ] nvim_get_current_buf() is 9
[ 23:50:19:934 ] [ Info ] [ ft ] nvim_get_current_win() is 1008
[ 23:50:19:934 ] [ Info ] [ ft ] real_current_win's buf is7
[ 23:50:19:953 ] [ Info ] [ ft ] -----------------------------------------------------
[ 23:50:19:953 ] [ Info ] [ ft ] event is Syntax
[ 23:50:19:953 ] [ Info ] [ ft ] ev.buf is 9
[ 23:50:19:953 ] [ Info ] [ ft ] nvim_get_current_buf() is 9
[ 23:50:19:953 ] [ Info ] [ ft ] nvim_get_current_win() is 1008
[ 23:50:19:953 ] [ Info ] [ ft ] real_current_win's buf is7
[ 23:50:19:954 ] [ Info ] [ ft ] -----------------------------------------------------
[ 23:50:19:954 ] [ Info ] [ ft ] event is FileType
[ 23:50:19:954 ] [ Info ] [ ft ] ev.buf is 9
[ 23:50:19:954 ] [ Info ] [ ft ] nvim_get_current_buf() is 9
[ 23:50:19:954 ] [ Info ] [ ft ] nvim_get_current_win() is 1008
[ 23:50:19:954 ] [ Info ] [ ft ] real_current_win's buf is7
[ 23:50:19:954 ] [ Info ] [ ft ] -----------------------------------------------------
[ 23:50:19:954 ] [ Info ] [ ft ] event is OptionSet
[ 23:50:19:954 ] [ Info ] [ ft ] ev.buf is 0
[ 23:50:19:954 ] [ Info ] [ ft ] nvim_get_current_buf() is 9
[ 23:50:19:954 ] [ Info ] [ ft ] nvim_get_current_win() is 1008
[ 23:50:19:954 ] [ Info ] [ ft ] real_current_win's buf is7
[ 23:50:19:954 ] [ Info ] [ ft ] -----------------------------------------------------
[ 23:50:19:954 ] [ Info ] [ ft ] nvim_get_current_buf() is 7
[ 23:50:19:954 ] [ Info ] [ ft ] nvim_get_current_win() is 1000
可以看到,在 event 触发 callback 函数内 nvim_get_current_win 和 nvim_get_current_buf 都临时被修改了。
测试一下,不开窗口效果呢?
local log = require("logger").derive("ft")
log.info("nvim_get_current_buf() is " .. vim.api.nvim_get_current_buf())
log.info("nvim_get_current_win() is " .. vim.api.nvim_get_current_win())
log.info("-----------------------------------------------------")
local real_current_win = vim.api.nvim_get_current_win()
local newbuf = vim.api.nvim_create_buf(true, false)
local events = {}
for _, v in ipairs(vim.fn.getcompletion("", "event")) do
if not vim.endswith(v, "Cmd") then
table.insert(events, v)
end
end
local id = vim.api.nvim_create_autocmd(events, {
group = vim.api.nvim_create_augroup("test_ft", { clear = true }),
pattern = { "*" },
callback = function(ev)
log.info("-----------------------------------------------------")
log.info("event is " .. ev.event)
log.info("ev.buf is " .. ev.buf)
log.info("nvim_get_current_buf() is " .. vim.api.nvim_get_current_buf())
log.info("nvim_get_current_win() is " .. vim.api.nvim_get_current_win())
log.info("real_current_win's buf is" .. vim.api.nvim_win_get_buf(real_current_win))
end,
})
-- vim.api.nvim_open_win(newbuf, false, { split = "above" })
vim.api.nvim_set_option_value("filetype", "test123", { buf = newbuf })
vim.api.nvim_del_autocmd(id)
log.info("-----------------------------------------------------")
log.info("nvim_get_current_buf() is " .. vim.api.nvim_get_current_buf())
log.info("nvim_get_current_win() is " .. vim.api.nvim_get_current_win())
[ 23:53:49:058 ] [ Info ] [ ft ] nvim_get_current_buf() is 10
[ 23:53:49:058 ] [ Info ] [ ft ] nvim_get_current_win() is 1000
[ 23:53:49:058 ] [ Info ] [ ft ] -----------------------------------------------------
[ 23:53:49:078 ] [ Info ] [ ft ] -----------------------------------------------------
[ 23:53:49:078 ] [ Info ] [ ft ] event is Syntax
[ 23:53:49:078 ] [ Info ] [ ft ] ev.buf is 12
[ 23:53:49:078 ] [ Info ] [ ft ] nvim_get_current_buf() is 12
[ 23:53:49:078 ] [ Info ] [ ft ] nvim_get_current_win() is 1001
[ 23:53:49:078 ] [ Info ] [ ft ] real_current_win's buf is10
[ 23:53:49:079 ] [ Info ] [ ft ] -----------------------------------------------------
[ 23:53:49:079 ] [ Info ] [ ft ] event is FileType
[ 23:53:49:079 ] [ Info ] [ ft ] ev.buf is 12
[ 23:53:49:079 ] [ Info ] [ ft ] nvim_get_current_buf() is 12
[ 23:53:49:079 ] [ Info ] [ ft ] nvim_get_current_win() is 1001
[ 23:53:49:079 ] [ Info ] [ ft ] real_current_win's buf is10
[ 23:53:49:079 ] [ Info ] [ ft ] -----------------------------------------------------
[ 23:53:49:079 ] [ Info ] [ ft ] event is OptionSet
[ 23:53:49:079 ] [ Info ] [ ft ] ev.buf is 0
[ 23:53:49:079 ] [ Info ] [ ft ] nvim_get_current_buf() is 12
[ 23:53:49:079 ] [ Info ] [ ft ] nvim_get_current_win() is 1001
[ 23:53:49:079 ] [ Info ] [ ft ] real_current_win's buf is10
[ 23:53:49:079 ] [ Info ] [ ft ] -----------------------------------------------------
[ 23:53:49:079 ] [ Info ] [ ft ] nvim_get_current_buf() is 10
[ 23:53:49:079 ] [ Info ] [ ft ] nvim_get_current_win() is 1000
这窗口 1001 是什么鬼?临时隐藏窗口?
local log = require("logger").derive("ft")
log.info("nvim_get_current_buf() is " .. vim.api.nvim_get_current_buf())
log.info("nvim_get_current_win() is " .. vim.api.nvim_get_current_win())
log.info("-----------------------------------------------------")
local real_current_win = vim.api.nvim_get_current_win()
local newbuf = vim.api.nvim_create_buf(true, false)
local events = {}
for _, v in ipairs(vim.fn.getcompletion("", "event")) do
if not vim.endswith(v, "Cmd") then
table.insert(events, v)
end
end
local id = vim.api.nvim_create_autocmd(events, {
group = vim.api.nvim_create_augroup("test_ft", { clear = true }),
pattern = { "*" },
callback = function(ev)
log.info("-----------------------------------------------------")
log.info("event is " .. ev.event)
log.info("ev.buf is " .. ev.buf)
log.info("nvim_get_current_buf() is " .. vim.api.nvim_get_current_buf())
log.info("nvim_get_current_win() is " .. vim.api.nvim_get_current_win())
log.info('win count is ' .. vim.fn.winnr('$'))
log.info('winconfig is ' .. vim.inspect(vim.api.nvim_win_get_config(vim.api.nvim_get_current_win())))
log.info("real_current_win's buf is" .. vim.api.nvim_win_get_buf(real_current_win))
end,
})
-- vim.api.nvim_open_win(newbuf, false, { split = "above" })
vim.api.nvim_set_option_value("filetype", "test123", { buf = newbuf })
vim.api.nvim_del_autocmd(id)
log.info("-----------------------------------------------------")
log.info("nvim_get_current_buf() is " .. vim.api.nvim_get_current_buf())
log.info("nvim_get_current_win() is " .. vim.api.nvim_get_current_win())
[ 23:57:49:249 ] [ Info ] [ ft ] nvim_get_current_buf() is 9
[ 23:57:49:249 ] [ Info ] [ ft ] nvim_get_current_win() is 1000
[ 23:57:49:249 ] [ Info ] [ ft ] -----------------------------------------------------
[ 23:57:49:268 ] [ Info ] [ ft ] -----------------------------------------------------
[ 23:57:49:268 ] [ Info ] [ ft ] event is Syntax
[ 23:57:49:268 ] [ Info ] [ ft ] ev.buf is 13
[ 23:57:49:268 ] [ Info ] [ ft ] nvim_get_current_buf() is 13
[ 23:57:49:268 ] [ Info ] [ ft ] nvim_get_current_win() is 1001
[ 23:57:49:268 ] [ Info ] [ ft ] win count is 2
[ 23:57:49:268 ] [ Info ] [ ft ] winconfig is {
anchor = "NW",
col = 0,
external = false,
focusable = false,
height = 5,
hide = false,
mouse = false,
relative = "editor",
row = 0,
width = 168,
zindex = 50
}
[ 23:57:49:268 ] [ Info ] [ ft ] real_current_win's buf is9
[ 23:57:49:269 ] [ Info ] [ ft ] -----------------------------------------------------
[ 23:57:49:269 ] [ Info ] [ ft ] event is FileType
[ 23:57:49:269 ] [ Info ] [ ft ] ev.buf is 13
[ 23:57:49:269 ] [ Info ] [ ft ] nvim_get_current_buf() is 13
[ 23:57:49:269 ] [ Info ] [ ft ] nvim_get_current_win() is 1001
[ 23:57:49:269 ] [ Info ] [ ft ] win count is 2
[ 23:57:49:270 ] [ Info ] [ ft ] winconfig is {
anchor = "NW",
col = 0,
external = false,
focusable = false,
height = 5,
hide = false,
mouse = false,
relative = "editor",
row = 0,
width = 168,
zindex = 50
}
[ 23:57:49:270 ] [ Info ] [ ft ] real_current_win's buf is9
[ 23:57:49:270 ] [ Info ] [ ft ] -----------------------------------------------------
[ 23:57:49:270 ] [ Info ] [ ft ] event is OptionSet
[ 23:57:49:270 ] [ Info ] [ ft ] ev.buf is 0
[ 23:57:49:270 ] [ Info ] [ ft ] nvim_get_current_buf() is 13
[ 23:57:49:270 ] [ Info ] [ ft ] nvim_get_current_win() is 1001
[ 23:57:49:270 ] [ Info ] [ ft ] win count is 2
[ 23:57:49:270 ] [ Info ] [ ft ] winconfig is {
anchor = "NW",
col = 0,
external = false,
focusable = false,
height = 5,
hide = false,
mouse = false,
relative = "editor",
row = 0,
width = 168,
zindex = 50
}
[ 23:57:49:270 ] [ Info ] [ ft ] real_current_win's buf is9
[ 23:57:49:270 ] [ Info ] [ ft ] -----------------------------------------------------
[ 23:57:49:270 ] [ Info ] [ ft ] nvim_get_current_buf() is 9
[ 23:57:49:270 ] [ Info ] [ ft ] nvim_get_current_win() is 1000