Skip to content

lua 脚本

neovim 中引入了 lua 解释器能够直接使用 lua 作为脚本文件。由于 lua 是一个完整的编程语言要比 vimscript 更加的完善因此 neovim 推荐使用 lua 脚本来配置以及 lua 编写插件。

简介

Neovim 内置了一个始终可用的 lua 引擎,并且围绕 lua 提供了一系列的编程接口。为了方便 Neovim 将他们放置在不同的命名空间中:

  • vim builtin-functionsEx-commands 被封装到 vim.fn 命名空间下,或者使用 vim.cmd() 来使用
  • 一些用 C 语言编写的用于远程插件和图形用户界面的Nvim API,他们被封装到 vim.api 命名空间下
  • 使用 Lua 编写的用于 Neovim 的lua-stdlib,他们直接被绑定在 vim 下

Note

vim.optvim.keymap 这些都属于 lua-stdlib。这里主要讲解的也只是 lua-stdlib。而 vim.fn 通常被认为是隶属于 vim 的

在 nvim 中使用 lua

要在 neovim 中使用 lua 有几种方式:

  1. 使用 :lua <lua-code> 直接在命令行上运行 lua 代码
  2. 使用 :source luafile.lua 来运行 lua 脚本

Note

在命令行中使用 lua 命令具有自己的执行环境,多次命令之间无法互通(例如定义变量)

启动是加载 Lua 文件

Neovim 支持使用 init.lua 文件作为配置文件,他会在启动时自动加载他,他通常位于:

Bash
# Linux
~/.config/nvim/init.vim|init.lua

# windows
~/AppData/Local/nvim/init.vim|init.lua

而如果想要加载插件和其他 lua 文件,可以放置到 runtimepath 的 lua/ 目录中,基于此配置目录可能如下:

Bash
~/.config/nvim
|-- after/
|-- ftplugin/    # filetype 插件,特定 filetype 加载
|--   java.lua
|-- lua/
|   |-- myluamodule.lua
|   |-- other_modules/
|       |-- anothermodule.lua
|       |-- init.lua
|-- plugin/ # 插件
|-- syntax/ # 语法高亮
|-- init.lua

在 lua 中可以使用 require 来加载其他 lua 模块:

Lua
-- 注意不要有 .lua 扩展名
require('other_modules') -- loads other_modules/init.lua
-- loads other_modules/anothermodule.lua
require('other_modules/anothermodule')
-- or
require('other_modules.anothermodule')

如果模块不存在会导致引入错误,因此有一个比较健壮的写法就是:

Lua
local ok, mymod = pcall(require, 'module_with_error')
if not ok then
  print("Module had an error")
else
  mymod.function()
end

在 Lua 中使用 vimscript

所有的 Vim 命令和函数都能够在 Lua 中访问。

vim.cmd

使用 vim.cmd() 来运行任意 Vim 命令(主要是 Ex Command):

Lua
-- 运行 Vim Command
vim.cmd("colorscheme habamax")

-- 多行命令可以使用 [[ ]] 来运行
vim.cmd([[
  highlight Error guibg=red
  highlight link Warning Error
]])

如果想要你编程的方式构建 vim 命令,可以使用 vim.cmd.{fn} 的形式:

Lua
vim.cmd.colorscheme("habamax")
vim.cmd.highlight({ "Error", "guibg=red" })
vim.cmd.highlight({ "link", "Warning", "Error" })

vim.fn

所有 Vimscript function 函数(包含builtin-functionsuser-functions被绑定在 vim.fn 命令空间下:

Lua
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 函数必须:

Lua
vim.fn['my#autoload#function']()

变量(Variables)

可以使用以下的包装器来设置和读取变量:

lua vimscript 说明
vim.g g: 全局变量
vim.b b: 当前缓冲区变量
vim.w w: 当前窗口变量
vim.t t: 当前标签页变量
vim.v v: 预定义变量
vim.env - 当前会话中定义的环境变量

还可以定位特定缓冲区(通过数字)、窗口(通过 window-id)和标签页:

Lua
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

Tip

要删除一个变量,只需要将其设置为 nil 即可: vim.g.myvar = nil

更改表中字段值

这个非常特殊,你不能直接修改表中字段的只:

Lua
vim.g.some_global_variable.key2 = 400
vim.print(vim.g.some_global_variable)
--> { key1 = "value", key2 = 300 }

如果想要更改必须创建一个中间 lua 表,然后修改后在赋值:

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 }

选项(Options)

Neovim 同样为选项提供了包装器:

lua vim 说明
vim.opt :set 设置选项
vim.opt_global :setglobal 设置全局选项
vim.opt_local :setlocal 设置本地选项

对于布尔选项:

Lua
-- set smarttab
vim.opt.smarttab = true
-- set nosmarttab
vim.opt.smarttab = false

对于列表选项:

Lua
-- set wildignore=*.o,*.a,__pycache__
vim.opt.wildignore = { '*.o', '*.a', '__pycache__' }
-- set listchars=space:_,tab:>~
vim.opt.listchars = { space = '_', tab = '>~' }
-- set formatoptions=njt
vim.opt.formatoptions = { n = true, j = true, t = true }

读取选项

不能直接访问选项的值,必须使用 vim.opt:get():

Lua
print(vim.opt.smarttab)
--> {...} (big table)
print(vim.opt.smarttab:get())
--> false
vim.print(vim.opt.listchars:get())
--> { space = '_', tab = '>~' }

vim.o

还有一套更为简单的选项读取接口:

lua vim 说明
vim.o :set 设置选项
vim.go :setglobal 设置全局选项
vim.bo - 设置缓冲区选项
vim.wo - 设置窗口范围的选项

他们能够直接读写:

Lua
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

和变量一样,他同样可以通过缓冲区编号、window-id 来针对特定缓冲区:

Lua
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

映射(Mappings)

使用 vim.keymap.set(mode, lhs, rhs, opts) 来创建命令,他接受三个参数分别表示:

  • mode: 一个字符串或 table,其中包含映射生效的模式前缀
  • lhs: 一个字符串,要触发命令的按键
  • rhs: 一个带有 vim 命令的字符串或者是 lua 函数,如果是空表示禁用键
Lua
-- 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)

第四个参数包含一些映射行为,他是一个具有四个参数的表:

  • buffer: 指定缓冲区映射, 0 或 true 表示当前缓冲区
  • silent: 如果设置为 true 则禁止输出错误信息
  • expr: 如果设置为 true 不执行 rhs 而是使用输入作为返回值
  • desc: 映射描述
  • remap: 默认是非递归映射,可以设置为 true 来启用递归映射

自动命令(autocommand)

用户命令(usercommand)

Note

要区别用户命令和用户函数,前者可以通过 :user_command 调用,后者用于 vimscript/lua 脚本中

参考

Note

Lua 5.1 是 Neovim Lua 的永久接口