事情起因
时隔十年,再次被 Windows 系统的路劲大小写问题坑了一把。记得上一次被坑是因为写 Vim Script 的 autoload 脚本时出现的问题。 最近使用 Lua 重新写了 ChineseLinter.vim 这个插件,最开始的文件结构:
文件:plugins/chineselinter.lua
return {
'wsdjeg/ChineseLinter.nvim',
dev = true,
opts = {
ignored_errors = { 'E015', 'E013', 'E020', 'E021' },
},
cmds = { 'CheckChinese' },
desc = 'Chinese Document Language Standards Checking Tool',
}
按照以上配置,无论如何 ignored_errors 配置都无法起效。
寻找原因
上述插件在载入时没有报错,说明被成功载入并且执行了 setup 函数。我试着用单独的脚本来测试,并且打入一些日志:
vim.opt.runtimepath:append("D:/wsdjeg/job.nvim")
vim.opt.runtimepath:append("D:/wsdjeg/logger.nvim")
vim.opt.runtimepath:append("D:/wsdjeg/nvim-plug")
require('plug').setup({
bundle_dir = 'D:/bundle_dir',
raw_plugin_dir = 'D:/bundle_dir/raw_plugin',
-- ui = 'notify',
http_proxy = 'http://127.0.0.1:7890',
https_proxy = 'http://127.0.0.1:7890',
enable_priority = false,
enable_luarocks = true,
max_processes = 16,
dev_path = 'D:/wsdjeg',
})
require("plug").add({
{
"wsdjeg/ChineseLinter.nvim",
dev = true,
opts = {
ignored_errors = { "E015", "E013", "E020", "E021" },
},
cmds = { "CheckChinese" },
desc = "Chinese Document Language Standards Checking Tool",
},
})
日志结果如下:
[ 23:35:32:449 ] [ Info ] [ cnlint ] module is loaded
[ 23:35:32:450 ] [ Info ] [ cnlint ] setup function is called
[ 23:35:32:450 ] [ Info ] [ plug ] load plug: ChineseLinter.nvim in 4.3624ms
[ 23:35:32:451 ] [ Info ] [ cnlint ] module is loaded
[ 23:35:32:451 ] [ Info ] [ cnlint ] check function is called
不难看出 ChineseLinter 模块被载入了两次,第一次载入及setup函数是 nvim-plug 在执行,执行后计算的载入时间,第二次是执行 CheckChinese 命令时,
而这一命令是在 plugin/ChineseLinter.lua 内定义的:
vim.api.nvim_create_user_command("CheckChinese", function(opt)
require("ChineseLinter").check()
end, { nargs = "*" })
问题就在这里,这个命令内 require('ChineseLinter') 不应该再次载入模块文件,因为前面 nvim-plug 已经执行过一次了,正常情况下 package.loaded 内会缓存模块。
看一下 nvim-plug 载入 Lua 插件的逻辑,它会给 plugSpec 自动设置一个模块名称,
以便于自动执行 require(plugSpec.module).setup(plugSpec.opts)。
问题就在于这个 module 名称生成函数原先是:
local function get_default_module(name)
return name
:lower()
:gsub('[%.%-]lua$', '')
:gsub('^n?vim-', '')
:gsub('[%.%-]n?vim', '')
end
也就是说,按照上述载入插件方式,nvim-plug 执行的是 require('chineselinter'),这在 Windows 系统下,
因为文件 lua/ChineseLinter/init.lua 已存在,那么上述 require 函数就会读取这个模块。
而 :CheckChinese 命令实际上调用的模块是 require('ChineseLinter')。
因为 Lua 的模块名称实际上是大小写敏感的,就会再次去寻找模块文件以载入。
如何修复?
我查阅了几个插件管理器,他们的获取模块名称的函数基本上逻辑类似,都使用了 lower() 函数:
---@param name string
---@return string
function M.normname(name)
local ret = name:lower():gsub("^n?vim%-", ""):gsub("%.n?vim$", ""):gsub("[%.%-]lua", ""):gsub("[^a-z]+", "")
return ret
end
实际上,最好是不要自动去将模块的名字全部小写,按照仓库的名称来最合适,去除掉前后缀,修改 nvim-plug 如下:
diff --git a/lua/plug/loader.lua b/lua/plug/loader.lua
index d0fc7b6..957fcb7 100644
--- a/lua/plug/loader.lua
+++ b/lua/plug/loader.lua
@@ -68,8 +68,7 @@ end
--- @param name string
--- @return string
local function get_default_module(name)
- return name:lower()
- :gsub('[%.%-]lua$', '')
+ return name:gsub('[%.%-]lua$', '')
:gsub('^n?vim-', '')
:gsub('[%.%-]n?vim', '')
end
@@ -94,6 +93,13 @@ function M.parser(plugSpec)
plugSpec.name = check_name(plugSpec)
if not plugSpec.module then
plugSpec.module = get_default_module(plugSpec.name)
+ log.info(
+ string.format(
+ 'set %s default module name to %s',
+ plugSpec.name,
+ plugSpec.module
+ )
+ )
end
if #plugSpec.name == 0 then
plugSpec.enabled = false
考虑到 Windows 系统的大小写敏感,以及 Shift 键这么难按,我将插件的名称以及其内模块的名称都改成了小写,修改后插件的安装方式:
return {
'wsdjeg/chineselinter.nvim',
dev = true,
opts = {
ignored_errors = { 'E015', 'E013', 'E020', 'E021' },
},
cmds = { 'CheckChinese' },
desc = 'Chinese Document Language Standards Checking Tool',
}
总结
上述核心问题在于 Lua 的 require() 函数读取模块缓存时判断的是 package.load[key],这里的 key 是大小写敏感的。
而发现缓存不存在时,依照 key 去载入文件时,在 Windows 系统下路劲又是不敏感的,
会导致同一个模块被不同的大小写模块名称多次载入。