Lua 指南

Nvim 的 :help 页面,从 生成源代码,使用 tree-sitter-vimdoc 解析器。


在 Nvim 中使用 Lua 的指南

介绍

本指南将介绍在 Nvim 中使用 Lua 的基础知识。它并不旨在成为所有可用功能的完整百科全书,也不会详细介绍所有复杂之处。把它想象成一个生存工具包——使用 Lua 在 Nvim 中舒适地入门所需的最低限度知识。
需要注意的是,这不是 Lua 语言本身的指南。相反,这是一份关于如何通过 Lua 语言以及我们提供的用于帮助配置和修改 Nvim 的函数来配置和修改 Nvim 的指南。如果你想了解更多关于 Lua 本身的信息,请查看 luareflua-concepts。同样,本指南假设您熟悉 Nvim 的基本知识(命令、选项、映射、自动命令),这些知识在 用户手册 中有介绍。

关于 API 的一些话语 lua-guide-api

本指南的目的是介绍通过 Lua 与 Nvim 交互的不同方式(“API”)。此 API 由三个不同的层组成
1. 从 Vim 继承的“Vim API”:Ex 命令内置函数 以及 Vimscript 中的 用户函数。这些可以通过 vim.cmd()vim.fn 访问,分别在下面 lua-guide-vimscript 中讨论。
2. 用 C 编写的“Nvim API”,用于远程插件和 GUI;参见 api。这些函数可以通过 vim.api 访问。
3. 用 Lua 编写并专门为 Lua 设计的“Lua API”。这些是任何其他可通过 vim.* 访问的函数,这些函数尚未提及;参见 lua-stdlib
这种区分很重要,因为 API 函数继承了来自其原始层的行为:例如,Nvim API 函数始终需要指定所有参数,即使 Lua 本身允许省略参数(这些参数将作为 nil 传递);而 Vim API 函数可以使用基于 0 的索引,即使 Lua 数组默认情况下是基于 1 的。
通过这种方式,任何可能的交互都可以通过 Lua 完成,而无需从头开始编写一个全新的 API。为此,函数通常不会在层之间重复,除非在功能或性能方面存在显着优势(例如,你可以通过 nvim_create_autocmd() 直接映射 Lua 函数,但不能通过 :autocmd)。如果有几种方法可以实现相同的结果,本指南只会涵盖最方便从 Lua 使用的方法。
要从 Nvim 命令行运行 Lua 代码,请使用 :lua 命令
:lua print("Hello!")
注意:每个 :lua 命令都有自己的范围,使用 local 关键字声明的变量在命令外部不可访问。这将不起作用
:lua local foo = 1
:lua print(foo)
" prints "nil" instead of "1"
你也可以使用 :lua=,它等同于 :lua vim.print(...),方便地检查变量或表的的值
:lua =package
要在外部文件中运行 Lua 脚本,可以使用 :source 命令,与 Vimscript 文件完全相同
:source ~/programs/baz/myluafile.lua
最后,你可以在 Vimscript 文件中包含 Lua 代码,方法是将其放在 :lua-heredoc 块中
lua << EOF
  local tbl = {1, 2, 3}
  for k, v in ipairs(tbl) do
    print(v)
  end
EOF

在启动时使用 Lua 文件 lua-guide-config

Nvim 支持使用 init.viminit.lua 作为配置文件,但不能同时使用两者。这应该放在你的 配置 目录中,通常是 Linux、BSD 或 macOS 的 ~/.config/nvim,以及 Windows 的 ~/AppData/Local/nvim/。请注意,你可以在 init.vim 中使用 Lua,在 init.lua 中使用 Vimscript,这将在下面介绍。
如果你想在 启动 时自动运行任何其他 Lua 脚本,那么你只需将其放在 'runtimepath' 中的 plugin/ 目录中。
如果你想按需加载 Lua 文件,你可以将它们放在 'runtimepath' 下的 lua/ 目录中,并使用 require 加载它们。(这相当于 Vimscript 的 autoload 机制。)
假设你具有以下目录结构
~/.config/nvim
|-- after/
|-- ftplugin/
|-- lua/
|   |-- myluamodule.lua
|   |-- other_modules/
|       |-- anothermodule.lua
|       |-- init.lua
|-- plugin/
|-- syntax/
|-- init.vim
那么以下 Lua 代码将加载 myluamodule.lua
require("myluamodule")
注意缺少 .lua 扩展名。
同样,加载 other_modules/anothermodule.lua 通过以下方式完成
require('other_modules/anothermodule')
-- or
require('other_modules.anothermodule')
请注意,“子模块”仅仅是子目录;. 等同于路径分隔符 /(即使在 Windows 上)。
包含 init.lua 文件的文件夹可以直接被 require,而无需指定文件名
require('other_modules') -- loads other_modules/init.lua
require 一个不存在的模块或包含语法错误的模块将中止当前正在执行的脚本。pcall() 可用于捕获此类错误。以下示例尝试加载 module_with_error,只有在成功加载时才调用其函数之一,否则打印错误消息
local ok, mymod = pcall(require, 'module_with_error')
if not ok then
  print("Module had an error")
else
  mymod.function()
end
:source 相反,require() 不仅搜索 'runtimepath' 下所有 lua/ 目录,它还会在第一次使用时缓存模块。因此,第二次调用 require() _不会_再次执行脚本,而是返回缓存的文件。要重新运行该文件,你需要先手动从缓存中删除它
package.loaded['myluamodule'] = nil
require('myluamodule')    -- read and execute the module again from disk

另请参阅

从 Lua 中使用 Vim 命令和函数 lua-guide-vimscript

所有 Vim 命令和函数都可以从 Lua 访问。
要从 Lua 运行任意的 Vim 命令,请将其作为字符串传递给 vim.cmd()
vim.cmd("colorscheme habamax")
请注意,特殊字符需要使用反斜杠转义
vim.cmd("%s/\\Vfoo/bar/g")
另一种方法是使用由双括号 [[ ]] 括起来的字面量字符串(参见 lua-literal),如
vim.cmd([[%s/\Vfoo/bar/g]])
使用字面量字符串的另一个好处是它们可以是多行的;这允许你将多个命令传递给 vim.cmd() 的单个调用
vim.cmd([[
  highlight Error guibg=red
  highlight link Warning Error
]])
这是 :lua-heredoc 的反面,允许你在 init.lua 中包含 Vimscript 代码。
如果你想以编程方式构建 Vim 命令,以下形式可能有用(这些都等同于上面对应的行)
vim.cmd.colorscheme("habamax")
vim.cmd.highlight({ "Error", "guibg=red" })
vim.cmd.highlight({ "link", "Warning", "Error" })

Vimscript 函数 lua-guide-vim-functions

使用 vim.fn 从 Lua 调用 Vimscript 函数。Lua 和 Vimscript 之间的数据类型将自动转换
print(vim.fn.printf('Hello from %s', 'Lua'))
local reversed_list = vim.fn.reverse({ 'a', 'b', 'c' })
vim.print(reversed_list) -- { "c", "b", "a" }
local function print_stdout(chan_id, data, name)
  print(data[1])
end
vim.fn.jobstart('ls', { on_stdout = print_stdout })
这适用于 内置函数用户函数
请注意,在 Lua 中,井号(#)不是标识符的有效字符,因此,例如,autoload 函数必须使用以下语法调用
vim.fn['my#autoload#function']()

另请参阅

内置函数:所有 Vimscript 函数的字母顺序列表
function-list:按主题分组的所有 Vimscript 函数列表
:runtime:运行 'runtimepath' 中所有与模式匹配的 Lua 脚本
package.pathrequire() 搜索的所有路径的列表
可以使用以下包装器设置和读取变量,这些包装器直接对应于它们的 变量范围
vim.g:全局变量 (g:)
vim.b:当前缓冲区的变量 (b:)
vim.w:当前窗口的变量 (w:)
vim.t:当前标签页的变量 (t:)
vim.v:预定义的 Vim 变量 (v:)
vim.env:在编辑器会话中定义的环境变量
数据类型将自动转换。例如
vim.g.some_global_variable = {
  key1 = "value",
  key2 = 300
}
vim.print(vim.g.some_global_variable)
--> { key1 = "value", key2 = 300 }
你可以通过索引包装器来定位特定缓冲区(通过编号)、窗口(通过 窗口 ID)或标签页
vim.b[2].myvar = 1               -- set myvar for buffer number 2
vim.w[1005].myothervar = true    -- set myothervar for window ID 1005
某些变量名可能包含在 Lua 中无法用作标识符的字符。你仍然可以通过以下语法操作这些变量
vim.g['my#variable'] = 1
请注意,你无法直接更改数组变量的字段。这将不起作用
vim.g.some_global_variable.key2 = 400
vim.print(vim.g.some_global_variable)
--> { key1 = "value", key2 = 300 }
相反,你需要创建一个中间 Lua 表格并更改它
local temp_table = vim.g.some_global_variable
temp_table.key2 = 400
vim.g.some_global_variable = temp_table
vim.print(vim.g.some_global_variable)
--> { key1 = "value", key2 = 400 }
要删除变量,只需将其设置为 nil
vim.g.myvar = nil

另请参阅

有两种互补的方法可以通过 Lua 设置 选项

vim.opt

设置全局和局部选项最方便的方式(例如,在 init.lua 中)是通过 vim.opt 及其朋友
vim.opt:行为类似于 :set
vim.opt_global:行为类似于 :setglobal
vim.opt_local:行为类似于 :setlocal
例如,Vimscript 命令
set smarttab
set nosmarttab
等同于
vim.opt.smarttab = true
vim.opt.smarttab = false
特别是,它们允许通过 Lua 表格轻松地处理类似列表、类似映射和类似集合的选项:而不是
set wildignore=*.o,*.a,__pycache__
set listchars=space:_,tab:>~
set formatoptions=njt
你可以使用
vim.opt.wildignore = { '*.o', '*.a', '__pycache__' }
vim.opt.listchars = { space = '_', tab = '>~' }
vim.opt.formatoptions = { n = true, j = true, t = true }
这些包装器还附带与 Vimscript 中的 :set+=:set^=:set-= 对等项类似的方法
vim.opt.shortmess:append({ I = true })
vim.opt.wildignore:prepend('*.o')
vim.opt.whichwrap:remove({ 'b', 's' })
需要付出的代价是你无法直接访问选项值,而必须使用 vim.opt:get()
print(vim.opt.smarttab)
--> {...} (big table)
print(vim.opt.smarttab:get())
--> false
vim.print(vim.opt.listchars:get())
--> { space = '_', tab = '>~' }

vim.o

为此,存在一种更直接的变量式访问方法,使用 vim.o 及其朋友,类似于你如何通过 :echo &number:let &listchars='space:_,tab:>~' 获取和设置选项
vim.o:行为类似于 :set
vim.go:行为类似于 :setglobal
vim.bo:用于缓冲区范围的选项
vim.wo:用于窗口范围的选项(可以双索引)
例如
vim.o.smarttab = false -- :set nosmarttab
print(vim.o.smarttab)
--> false
vim.o.listchars = 'space:_,tab:>~' -- :set listchars='space:_,tab:>~'
print(vim.o.listchars)
--> 'space:_,tab:>~'
vim.o.isfname = vim.o.isfname .. ',@-@' -- :set isfname+=@-@
print(vim.o.isfname)
--> '@,48-57,/,.,-,_,+,,,#,$,%,~,=,@-@'
vim.bo.shiftwidth = 4 -- :setlocal shiftwidth=4
print(vim.bo.shiftwidth)
--> 4
就像变量一样,你可以分别为缓冲区和窗口选项指定缓冲区编号或 窗口 ID。如果没有给出编号,则使用当前缓冲区或窗口
vim.bo[4].expandtab = true -- sets expandtab to true in buffer 4
vim.wo.number = true       -- sets number to true in current window
vim.wo[0].number = true    -- same as above
vim.wo[0][0].number = true -- sets number to true in current buffer
                           -- in current window only
print(vim.wo[0].number)    --> true

另请参阅

你可以将 Vim 命令或 Lua 函数映射到键序列。
可以使用 vim.keymap.set() 创建映射。此函数接受三个必填参数
{mode} 是一个字符串或一个字符串表,包含映射生效的模式前缀。前缀是在 :map-modes 中列出的那些前缀,或者对于 :map! 为“!”,或者对于 :map 为空字符串。
{lhs} 是一个字符串,包含应该触发映射的键序列。
{rhs} 可以是一个包含 Vim 命令的字符串,也可以是一个 Lua 函数,当输入 {lhs} 时,该函数会被执行。空字符串等同于 <Nop>,它会禁用一个键。
示例
-- Normal mode mapping for Vim command
vim.keymap.set('n', '<Leader>ex1', '<cmd>echo "Example 1"<cr>')
-- Normal and Command-line mode mapping for Vim command
vim.keymap.set({'n', 'c'}, '<Leader>ex2', '<cmd>echo "Example 2"<cr>')
-- Normal mode mapping for Lua function
vim.keymap.set('n', '<Leader>ex3', vim.treesitter.start)
-- Normal mode mapping for Lua function with arguments
vim.keymap.set('n', '<Leader>ex4', function() print('Example 4') end)
您可以通过以下方式映射 Lua 模块中的函数
vim.keymap.set('n', '<Leader>pl1', require('plugin').action)
请注意,这会在定义映射时加载插件。如果您希望在执行映射时才加载(如 autoload 函数),请将其封装在 function() end 中。
vim.keymap.set('n', '<Leader>pl2', function() require('plugin').action() end)
第四个可选参数是一个包含用于修改映射行为的键的表格,例如来自 :map-arguments 的键。以下是最有用的选项:
buffer:如果给出,则仅为指定编号的缓冲区设置映射;0true 表示当前缓冲区。
-- set mapping for the current buffer
vim.keymap.set('n', '<Leader>pl1', require('plugin').action, { buffer = true })
-- set mapping for the buffer number 4
vim.keymap.set('n', '<Leader>pl1', require('plugin').action, { buffer = 4 })
silent:如果设置为 true,则抑制输出,如错误消息。
vim.keymap.set('n', '<Leader>pl1', require('plugin').action, { silent = true })
expr:如果设置为 true,则不执行 {rhs},而是使用返回值作为输入。特殊 键码 会被自动转换。例如,以下映射只在弹出菜单中将 <down> 替换为 <c-n>
vim.keymap.set('c', '<down>', function()
  if vim.fn.pumvisible() == 1 then return '<c-n>' end
  return '<down>'
end, { expr = true })
desc:一个字符串,在使用例如 :map 列出映射时显示。这很有用,因为 Lua 函数作为 {rhs} 只能列为 Lua: <number> <source file>:<line>。因此,插件应该始终为此类映射使用此属性。
vim.keymap.set('n', '<Leader>pl1', require('plugin').action,
  { desc = 'Execute action from plugin' })
remap:默认情况下,所有映射都是非递归的(即 vim.keymap.set() 的行为类似于 :noremap)。如果 {rhs} 本身是一个要执行的映射,则设置 remap = true
vim.keymap.set('n', '<Leader>ex1', '<cmd>echo "Example 1"<cr>')
-- add a shorter mapping
vim.keymap.set('n', 'e', '<Leader>ex1', { remap = true })
注意: <Plug> 映射始终会被扩展,即使默认情况下 remap = false
vim.keymap.set('n', '[%', '<Plug>(MatchitNormalMultiBackward)')
可以使用 vim.keymap.del() 删除特定映射。
vim.keymap.del('n', '<Leader>ex1')
vim.keymap.del({'n', 'c'}, '<Leader>ex2', {buffer = true})

另请参阅

vim.api.nvim_get_keymap():返回所有全局映射。
vim.api.nvim_buf_get_keymap():返回缓冲区的所有映射。
一个 自动命令 是一个 Vim 命令或一个 Lua 函数,每当一个或多个 事件 被触发时,它就会被自动执行,例如,当一个文件被读取或写入时,或者当一个窗口被创建时。这些可以通过 Nvim API 从 Lua 访问。

创建自动命令 lua-guide-autocommand-create

自动命令是使用 vim.api.nvim_create_autocmd() 创建的,它接受两个必需的参数:
{event}:一个字符串或包含应该触发命令或函数的事件的字符串表格。
{opts}:一个包含控制事件被触发时发生什么的键的表格。
最重要的选项是:
pattern:一个字符串或包含 autocmd-pattern 的字符串表格。注意: $HOME~ 等环境变量不会自动扩展;您需要显式使用 vim.fn.expand() 来进行扩展。
command:一个包含 Vim 命令的字符串。
callback:一个 Lua 函数。
您必须指定 commandcallback 中的一个且只有一个。如果 pattern 被省略,则默认为 pattern = '*'。示例:
vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, {
  pattern = {"*.c", "*.h"},
  command = "echo 'Entering a C or C++ file'",
})
-- Same autocommand written with a Lua function instead
vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, {
  pattern = {"*.c", "*.h"},
  callback = function() print("Entering a C or C++ file") end,
})
-- User event triggered by MyPlugin
vim.api.nvim_create_autocmd("User", {
  pattern = "MyPlugin",
  callback = function() print("My Plugin Works!") end,
})
Nvim 始终会使用包含有关触发自动命令的信息的单个表格调用 Lua 函数。最有用的键是:
match:与 pattern 匹配的字符串(参见 <amatch>)。
buf:触发事件的缓冲区的编号(参见 <abuf>)。
file:触发事件的缓冲区的文件名(参见 <afile>)。
data:一个包含与某些事件相关联的其他相关数据的表格。
例如,这允许您为某些文件类型设置缓冲区局部映射。
vim.api.nvim_create_autocmd("FileType", {
  pattern = "lua",
  callback = function(args)
    vim.keymap.set('n', 'K', vim.lsp.buf.hover, { buffer = args.buf })
  end
})
这意味着,如果您的回调本身接受一个(即使是可选的)参数,您必须将其封装在 function() end 中以避免错误。
vim.api.nvim_create_autocmd('TextYankPost', {
  callback = function() vim.hl.on_yank() end
})
(由于 Lua 函数定义中可以省略未使用的参数,因此这等同于 function(args) ... end。)
您可以使用 buffer 创建一个缓冲区局部自动命令(参见 autocmd-buflocal),而不是使用模式;在这种情况下,无法使用 pattern
-- set autocommand for current buffer
vim.api.nvim_create_autocmd("CursorHold", {
  buffer = 0,
  callback = function() print("hold") end,
})
-- set autocommand for buffer number 33
vim.api.nvim_create_autocmd("CursorHold", {
  buffer = 33,
  callback = function() print("hold") end,
})
与映射类似,您可以(也应该)使用 desc 添加描述。
vim.api.nvim_create_autocmd('TextYankPost', {
  callback = function() vim.hl.on_yank() end,
  desc = "Briefly highlight yanked text"
})
最后,您可以使用 group 键对自动命令进行分组;这将在下一节中详细介绍。

分组自动命令 lua-guide-autocommands-group

自动命令组可用于将相关自动命令分组在一起;参见 autocmd-groups。这对于组织自动命令非常有用,尤其对于防止自动命令被多次设置非常有用。
组可以使用 vim.api.nvim_create_augroup() 创建。此函数接受两个必需参数:一个包含组名称的字符串,以及一个确定如果组已存在,是否应该清除该组(即删除所有分组的自动命令)的表格。该函数返回一个表示组的内部标识符的数字。组可以使用此标识符或名称来指定(但前提是该组已经创建)。
例如,一个常见的 Vimscript 模式用于在可能被重新加载的文件中定义的自动命令是:
augroup vimrc
  " Remove all vimrc autocommands
  autocmd!
  au BufNewFile,BufRead *.html set shiftwidth=4
  au BufNewFile,BufRead *.html set expandtab
augroup END
这等同于以下 Lua 代码:
local mygroup = vim.api.nvim_create_augroup('vimrc', { clear = true })
vim.api.nvim_create_autocmd({ 'BufNewFile', 'BufRead' }, {
  pattern = '*.html',
  group = mygroup,
  command = 'set shiftwidth=4',
})
vim.api.nvim_create_autocmd({ 'BufNewFile', 'BufRead' }, {
  pattern = '*.html',
  group = 'vimrc',  -- equivalent to group=mygroup
  command = 'set expandtab',
})
自动命令组对于给定名称是唯一的,因此您可以重复使用它们,例如在另一个文件中:
local mygroup = vim.api.nvim_create_augroup('vimrc', { clear = false })
vim.api.nvim_create_autocmd({ 'BufNewFile', 'BufRead' }, {
  pattern = '*.c',
  group = mygroup,
  command = 'set noexpandtab',
})
您可以使用 vim.api.nvim_clear_autocmds() 删除自动命令。此函数接受一个包含描述要删除的自动命令的键的表格的单一必需参数。
-- Delete all BufEnter and InsertLeave autocommands
vim.api.nvim_clear_autocmds({event = {"BufEnter", "InsertLeave"}})
-- Delete all autocommands that uses "*.py" pattern
vim.api.nvim_clear_autocmds({pattern = "*.py"})
-- Delete all autocommands in group "scala"
vim.api.nvim_clear_autocmds({group = "scala"})
-- Delete all ColorScheme autocommands in current buffer
vim.api.nvim_clear_autocmds({event = "ColorScheme", buffer = 0 })
注意: 即使其他选项与之匹配,组中的自动命令也只会删除,前提是指定了 group 键。

另请参阅

nvim_get_autocmds():返回所有匹配的自动命令。
nvim_exec_autocmds():执行所有匹配的自动命令。

用户命令 lua-guide-commands

用户命令 是自定义的 Vim 命令,它们调用 Vimscript 或 Lua 函数。就像内置命令一样,它们可以有参数、作用于范围或具有自定义的参数完成。由于这些命令对插件最有用,因此我们只介绍这个高级主题的基础知识。

创建用户命令 lua-guide-commands-create

用户命令可以通过 nvim_create_user_command() 创建。此函数接受三个必需参数:
一个字符串,表示命令的名称(该名称必须以大写字母开头,以将其与内置命令区分开来);
一个包含 Vim 命令或一个 Lua 函数的字符串,该函数在调用命令时执行;
一个包含 command-attributes 的表格;此外,它还可以包含 desc(描述命令的字符串)、force(设置为 false 以避免用相同名称的现有命令替换)、preview(用于 :command-preview 的 Lua 函数)等键。
示例
vim.api.nvim_create_user_command('Test', 'echo "It works!"', {})
vim.cmd.Test()
--> It works!
(请注意,即使没有给出任何属性,第三个参数也是必需的。)
Lua 函数使用包含参数和修饰符的单个表格参数调用。最重要的参数是:
name:包含命令名称的字符串。
fargs:包含命令参数的表格,按空格分隔(参见 <f-args>)。
bang:如果命令使用 ! 修饰符执行,则为 true(参见 <bang>)。
line1:命令范围的起始行号(参见 <line1>)。
line2:命令范围的结束行号(参见 <line2>)。
range:命令范围中的项目数量:0、1 或 2(参见 <range>)。
count:提供的任何计数(参见 <count>)。
smods:包含命令修饰符的表格(参见 <mods>)。
例如
vim.api.nvim_create_user_command('Upper',
  function(opts)
    print(string.upper(opts.fargs[1]))
  end,
  { nargs = 1 })
vim.cmd.Upper('foo')
--> FOO
complete 属性除了 :command-complete 中列出的属性外,还可以使用 Lua 函数。
vim.api.nvim_create_user_command('Upper',
  function(opts)
    print(string.upper(opts.fargs[1]))
  end,
  { nargs = 1,
    complete = function(ArgLead, CmdLine, CursorPos)
      -- return completion candidates as a list-like table
      return { "foo", "bar", "baz" }
    end,
})
缓冲区局部用户命令是使用 vim.api.nvim_buf_create_user_command() 创建的。这里第一个参数是缓冲区编号(0 表示当前缓冲区);其余参数与 nvim_create_user_command() 相同。
vim.api.nvim_buf_create_user_command(0, 'Upper',
  function(opts)
    print(string.upper(opts.fargs[1]))
  end,
  { nargs = 1 })

删除用户命令 lua-guide-commands-delete

用户命令可以使用 vim.api.nvim_del_user_command() 删除。唯一的参数是命令的名称。
vim.api.nvim_del_user_command('Upper')
要删除缓冲区局部用户命令,请使用 vim.api.nvim_buf_del_user_command()。这里第一个参数是缓冲区编号(0 表示当前缓冲区),第二个参数是命令名称。
vim.api.nvim_buf_del_user_command(4, 'Upper')

致谢 lua-guide-credits

本指南主要来自 nanotee 的 Lua 指南:https://github.com/nanotee/nvim-lua-guide
感谢 @nanotee!
主目录
命令索引
快速参考