在 neovim 中使用 Lua

2023-05-16

原文:https://github.com/glepnir/nvim-lua-guide-zh

nvim-lua-guide 中文版简易教程
译者: Neovim Core Developer

文章目录

  • 在 neovim 中使用 Lua
    • 简介
      • 学习 Lua
      • 现有的一些在 Neovim 中使用 Lua 的教程
      • 相关插件
    • Lua 文件位置
        • 警告
        • 提示
        • 包说明
    • 在 Vimscript 中使用 Lua
      • :lua
        • 警告
      • :luado
      • :luafile
        • luafile 对比 require():
      • luaeval()
      • v:lua
        • Caveats
    • Vim 命名空间
        • Tips
    • 在 Lua 中使用 Vimscript
      • vim.api.nvim_eval()
        • Caveats
      • vim.api.nvim_exec()
      • vim.api.nvim_command()
        • Tips
    • 管理 vim 的设置选项
      • 使用 api 函数
      • 使用元访问器
        • Caveats
    • 管理 vim 的内部变量
      • 使用 api 函数
      • 使用元访问器
        • Caveats
    • 调用 Vimscript 函数
      • vim.call()
      • vim.fn.{function}()
        • Tips
        • Caveats
    • 定义映射
    • 定义用户命令
    • 定义自动命令
    • 定义语法高亮
    • General tips and recommendations
    • Miscellaneous
      • vim.loop
      • vim.lsp
      • vim.treesitter
      • Transpilers

在 neovim 中使用 Lua

nvim-lua-guide 中文版简易教程

译者: Neovim Core Developer

简介

Lua 作为 Neovim 中的一等语言的集成正在成为它的杀手级特性之一。然而,学习如何用 Lua 编写插件的教程数量并不像用 Vimscript 编写插件那样多。这是一种尝试,试图提供一些基本信息,让人们可以使用 Lua 编写 Neovim 插件。

本指南假定您使用的是最新的 Neovim Nighly build。由于 Neovim 的 0.5 版本是开发版本,请记住,正在积极开发的一些 API 并不十分稳定,在发布之前可能会发生变化。

学习 Lua

不同于原版教程,以下资源适用于国内用户:

  • 在 Y 分钟内学习 X 关于 Lua 的页面
  • Lua 菜鸟教程
  • Lua 用户维基
  • Lua 的官方参考手册

Lua 是一种非常干净和简单的语言。它很容易学习,特别是如果你有其他编程语言基础的例如 TypeScript / JavaScript 等,会更加容易上手 Lua。注意:Neovim 嵌入的 Lua 版本是 LuaJIT 2.1.0,它与 Lua 5.1 保持兼容(带有几个 5.2 扩展)

现有的一些在 Neovim 中使用 Lua 的教程

已经编写了一些教程来帮助人们用 Lua 编写插件。他们中的一些人在写这本指南时提供了不少的帮助。非常感谢它们的作者。

  • teukka.tech - 从 init.vim 转到 init.lua
  • 2n.pl - 如何使用 Lua 编写 neovim 插件
  • 2n.pl - 如何使用 Lua 制作 neovim UI
  • ms-jpq - NeoVim 异步教程

相关插件

  • Vimpeccable - Plugin to help write your .vimrc in Lua
  • plenary.nvim - All the lua functions I don’t want to write twice
  • popup.nvim - An implementation of the Popup API from vim in Neovim
  • nvim_utils
  • nvim-luadev - REPL/debug console for nvim lua plugins
  • nvim-luapad - Interactive real time neovim scratchpad for embedded lua engine
  • nlua.nvim - Lua Development for Neovim
  • galaxyline.nvim - neovim statusline plugin written in lua
  • BetterLua.vim - Better Lua syntax highlighting in Vim/NeoVim

Lua 文件位置

Lua 文件通常位于您的 runtimepath 中的 lua/ 文件夹中(对于大多数用户来说,在 *nix 系统上为 ~/.config/nvim/lua,在 Windows 系统上为 ~/appdata/Local/nvim/lua)。Package.pathPackage.cpath 全局变量会自动调整为包含该文件夹下的 Lua 文件。这意味着您可以 require() 这些文件作为 Lua 模块

我们以下面的文件夹结构为例:

📂 ~/.config/nvim
├── 📁 after
├── 📁 ftplugin
├── 📂 lua
│  ├── 🌑 myluamodule.lua
│  └── 📂 other_modules
│     ├── 🌑 anothermodule.lua
│     └── 🌑 init.lua
├── 📁 pack
├── 📁 plugin
├── 📁 syntax
└── 🇻 init.vim

下面的 Lua 代码将加载 myluamodule.lua

require('myluamodule')

注意没有 .lua 扩展名。

类似地,加载 other_modules/anothermodule.lua 的过程如下:

require('other_modules.anothermodule')
-- or
require('other_modules/anothermodule')

路径分隔符可以用点 . 表示,也可以用斜杠 / 表示。

文件夹如果包含 init.lua 文件,可以直接引用该文件夹而不必指定该文件的名称

require('other_modules') -- loads other_modules/init.lua

更多信息 :help lua-require

警告

与 .vim 文件不同,.lua 文件不会自动从您的 runtimepath 目录中获取。相反,您必须从 Vimscript source/require 它们。计划增加 init.lua 文件加载选项,替代 init.vim

  • Issue #7895
  • Corresponding pull request

提示

多个 Lua 插件在它们的 lua/ 文件夹中可能有相同的文件名。这可能会导致命名空间冲突。如果两个不同的插件有一个 lua/main.lua 文件,那么执行 require('main') 是不明确的:我们想要加载哪个文件?最好将您的配置或插件命名为顶级文件夹,
例如这样的形式:lua/plugin_name/main.lua

包说明

如果您是 package 特性的用户或基于它的插件管理器例如 packer.nvim,minpac 或 vim-packager,那么在使用 Lua 插件时需要注意一些事情。start 文件夹中的包只有在源化您的 init.vim 之后才会加载。这意味着只有在 Neovim 处理完文件之后,才会将包添加到 runtimepath 中。如果插件期望
require 一个 Lua 模块或调用自动加载的函数,这可能会导致问题。假设包 start/foo 有一个 lua/bar.lua 文件,从您的 init.vim 执行此操作将引发错误,因为 runtimepath 尚未更新。

lua require('bar')

你需要使用 packadd! foo 命令在 require 这个模块之前

packadd! foo
lua require('bar')

Packadd 后附加 ! 表示 Neovim 会将包放在 runtimepath 中,而不会在其 pluginftDetect 目录下寻找任何脚本。

See also:

  • :help :packadd
  • Issue #11409

在 Vimscript 中使用 Lua

:lua

该命令执行一段 Lua 代码

:lua require('myluamodule')

可以使用以下语法编写多行脚本:

echo "Here's a bigger chunk of Lua code"

lua << EOF
local mod = require('mymodule')
local tbl = {1, 2, 3}

for k, v in ipairs(tbl) do
    mod.method(v)
end

print(tbl)
EOF

See also:

  • :help :lua
  • :help :lua-heredoc

警告

在 Vim 文件中编写 Lua 时,您不会得到正确的语法突出显示。使用 :lua 命令作为需要外部 Lua 文件的入口点可能会更方便。

:luado

该命令执行一段 Lua 代码,该代码作用于当前缓冲区中的选中的行。如果未指定范围,则改为使用整个缓冲区。从块 return 的任何字符串都用于确定应该用什么替换每行。

以下命令会将当前缓冲区中的每一行替换为文本 hello world

:luado return 'hello world'

提供了两个隐式的 linelinenr 变量。line 是被迭代的行的文本,而 linenr 是它的编号。以下命令将可以被 2 整数的行转成大写:

:luado if linenr % 2 == 0 then return line:upper() end

See also:

  • :help :luado

:luafile

这个命令加载一个 lua 文件

:luafile ~/foo/bar/baz/myluafile.lua

类似于 Vim 的 :source 命令或 Lua 内置的 dofile() 函数。

See also:

  • :help :luafile

luafile 对比 require():

您可能想知道 lua request()luafile 之间的区别是什么,以及您是否应该使用其中一个而不是另一个。它们有不同的使用情形:

  • require():
    • 是内置的 Lua 函数,它允许你使用 Lua 的模块系统。
    • 使用 Package.path 变量搜索模块(如前所述,您可以使用 runtimepath 中的 lua/ 文件夹内的 required() lua 脚本)
    • 跟踪已加载的模块,并防止第二次解析和执行脚本。如果您更改包含某个模块代码的文件,并在 Neovim 运行时再次尝试 required(),则该模块实际上不会更新。
  • :luafile:
    • 是一个执行命令,它不支持模块。
    • 采用相对于当前窗口的工作目录的绝对或相对路径
    • 执行脚本的内容,而不管该脚本以前是否执行过

如果您想运行您正在处理的 Lua 文件,:luafile 很有用:

:luafile %

luaeval()

luaeval() 是内置的 Vimscript 函数计算 Lua 表达式字符串并返回它的值。Lua 的数据类型自动转换为 Vimscript 类型(反之亦然)。

" 你可以将结果存储到一个变量中
let variable = luaeval('1 + 1')
echo variable
" 2
let concat = luaeval('"Lua".." is ".."awesome"')
echo concat
" 'Lua is awesome'

" Lua 中的 table 数组转成成 Vimscript 的 list
let list = luaeval('{1, 2, 3, 4}')
echo list[0]
" 1
echo list[1]
" 2
" 注意 Vimscript 的数组索引下标与 Lua 不同是从 0 开始的,Lua 中是从 1 开始

" Lua 中类似 dict 的 table 会被转成 Vimscript 中的 dict
let dict = luaeval('{foo = "bar", baz = "qux"}')
echo dict.foo
" 'bar'

" 布尔类型和 nil 是类似的
echo luaeval('true')
" v:true
echo luaeval('nil')
" v:null

" 您可以为 Lua 函数创建 Vimscript 别名
let LuaMathPow = luaeval('math.pow')
echo LuaMathPow(2, 2)
" 4
let LuaModuleFunction = luaeval('require("mymodule").myfunction')
call LuaModuleFunction()

" 还可以将 Lua 函数作为值传递给 Vim 函数
lua X = function(k, v) return string.format("%s:%s", k, v) end
echo map([1, 2, 3], luaeval("X"))

luaeval() 接受可选的第二个参数,该参数允许您将数据传递给表达式。然后您可以使用全局的 _A 从 Lua 访问该数据:

echo luaeval('_A[1] + _A[2]', [1, 1])
" 2

echo luaeval('string.format("Lua is %s", _A)', 'awesome')
" 'Lua is awesome'

See also:

  • :help luaeval()

v:lua

这个全局 Vim 变量允许您直接从 Vimscript 调用全局 Lua 函数。同样 Vim 数据类型被转换为 Lua 类型,反之亦然。

call v:lua.print('Hello from Lua!')
" 'Hello from Lua!'

let scream = v:lua.string.rep('A', 10)
echo scream
" 'AAAAAAAAAA'

" Requiring modules works
call v:lua.require('mymodule').myfunction()

" How about a nice statusline?
lua << EOF
function _G.statusline()
    local filepath = '%f'
    local align_section = '%='
    local percentage_through_file = '%p%%'
    return string.format(
        '%s%s%s',
        filepath,
        align_section,
        percentage_through_file
    )
end
EOF

set statusline=%!v:lua.statusline()

" Also works in expression mappings
lua << EOF
function _G.check_back_space()
    local col = vim.fn.col('.') - 1
    if col == 0 or vim.fn.getline('.'):sub(col, col):match('%s') then
        return true
    else
        return false
    end
end
EOF

inoremap <silent> <expr> <Tab>
    \ pumvisible() ? '\<C-n>' :
    \ v:lua.check_back_space() ? '\<Tab>' :
    \ completion#trigger_completion()

See also:

  • :help v:lua
  • :help v:lua-call

Caveats

此变量只能用于调用函数。以下代码将始终引发错误:

" Aliasing functions doesn't work
let LuaPrint = v:lua.print

" Accessing dictionaries doesn't work
echo v:lua.some_global_dict['key']

" Using a function as a value doesn't work
echo map([1, 2, 3], v:lua.global_callback)

Vim 命名空间

Neovim 会暴露一个全局的 vim 变量来作为 Lua 调用 Vim 的 APIs 的入口。它还提供给用户一些额外的函数和子模块“标准库”

一些比较实用的函数和子模块如下:

  • vim.inspect: 把 Lua 对象以更易读的方式打印(在打印 Lua table 时会很有用)
  • vim.regex: 在 Lua 中使用 Vim 寄存器
  • vim.api: 暴露 vim 的 API(:h API) 的模块(别的远程调用也是调用同样的 API)
  • vim.loop: Neovim 的 event lopp 模块(使用 LibUV)
  • vim.lsp: 控制内置 LSP 客户端的模块
  • vim.treesitter: 暴露 tree-sitter 库中一些实用函数的模块

上面列举功能的并不全面。如果你想知道更多可行的操作可以看::help lua-stdlibhelp lua-vim。你也可以通过 :lua print(vim.inspect(vim)) 获得所有可用模块

Tips

每次你想检查一个对象时到要用 print(vim.inspect(x)) 是相当繁琐的。你可以你的配置中写一个全局的包装器函数来替代这个繁琐的过程

function _G.dump(...)
    local objects = vim.tbl_map(vim.inspect, {...})
    print(unpack(objects))
end

之后你就可以使用如下命令来快速检查对象内容了

dump({1, 2, 3})
:lua dump(vim.loop)

另外要注意的是,你可能会发现 Lua 会比其他语言少一些实用的内置函数(例如:os.clock(),返回以秒为单位,而不是以毫秒为单位的值)。仔细阅读 Neovim 提供的标准库和 vim.fn (后续还会有更多内容),里面可以会有你想要的东西。

在 Lua 中使用 Vimscript

vim.api.nvim_eval()

此函数计算 Vimscript 表达式字符串并返回其值。Vimscript 数据类型自动转换为 Lua 类型(反之亦然)。

它等同于 vimscript 中的 luaeval() 函数

-- Data types are converted correctly
print(vim.api.nvim_eval('1 + 1')) -- 2
print(vim.inspect(vim.api.nvim_eval('[1, 2, 3]'))) -- { 1, 2, 3 }
print(vim.inspect(vim.api.nvim_eval('{"foo": "bar", "baz": "qux"}'))) -- { baz = "qux", foo = "bar" }
print(vim.api.nvim_eval('v:true')) -- true
print(vim.api.nvim_eval('v:null')) -- nil

TODO: is it possible for vim.api.nvim_eval() to return a funcref?

Caveats

luaeval() 不同,vim.api.nvim_eval() 不提供隐式 _A 变量来传递数据给表达式。

vim.api.nvim_exec()

此函数用于计算 Vimscript 代码块。它接受一个包含要执行的源代码的字符串和一个布尔值,以确定代码的输出是否应该由函数返回(例如,您可以将输出存储在变量中)。

local result = vim.api.nvim_exec(
[[
let mytext = 'hello world'

function! MyFunction(text)
    echo a:text
endfunction

call MyFunction(mytext)
]],
true)

print(result) -- 'hello world'

TODO: The docs say that script-scope (s:) is supported, but running this snippet with a script-scoped variable throws an error. Why?

vim.api.nvim_command()

此函数执行一个 EX 命令。它接受包含要执行的命令的字符串。

vim.api.nvim_command('new')
vim.api.nvim_command('wincmd H')
vim.api.nvim_command('set nonumber')
vim.api.nvim_command('%s/foo/bar/g')

注意:vim.cmd 是此函数的一个较短的别名

vim.cmd('buffers')

Tips

由于您必须将字符串传递给这些函数,因此通常需要添加转义的反斜杠:

vim.cmd('%s/\\Vfoo/bar/g')

Literal strings are easier to use as they do not require escaping characters:

vim.cmd([[%s/\Vfoo/bar/g]])

管理 vim 的设置选项

使用 api 函数

Neovim 提供了一组 API 函数来设置选项或获取其当前值:

  • 全局选项:
    • vim.api.nvim_set_option()
    • vim.api.nvim_get_option()
  • 缓冲区选项:
    • vim.api.nvim_buf_set_option()
    • vim.api.nvim_buf_get_option()
  • 窗口选项:
    • vim.api.nvim_win_set_option()
    • vim.api.nvim_win_get_option()

它们接受一个字符串,其中包含要设置或者要获取的选项的名称以及要将其设置为的值。

布尔选项(如 (no)number) 必须设置为 truefalse

vim.api.nvim_set_option('smarttab', false)
print(vim.api.nvim_get_option('smarttab')) -- false

字符串选项必须设置为字符串:

vim.api.nvim_set_option('selection', 'exclusive')
print(vim.api.nvim_get_option('selection')) -- 'exclusive'

数字选项必须接受数字类型

vim.api.nvim_set_option('updatetime', 3000)
print(vim.api.nvim_get_option('updatetime')) -- 3000

Buffer-local 和 Window-local 选项还需要缓冲区编号或窗口编号(使用 0 将设置 / 获取当前缓冲区 / 窗口的选项):

vim.api.nvim_win_set_option(0, 'number', true)
vim.api.nvim_buf_set_option(10, 'shiftwidth', 4)
print(vim.api.nvim_win_get_option(0, 'number')) -- true
print(vim.api.nvim_buf_get_option(10, 'shiftwidth')) -- 4

使用元访问器

如果您想以更“惯用”的方式设置选项,可以使用一些元访问器。它们本质上包装了上述 API 函数,并允许您像处理变量一样操作选项:

  • vim.o.{option}: 全局选项
  • vim.bo.{option}: buffer-local 选项
  • vim.wo.{option}: window-local 选项
vim.o.smarttab = false
print(vim.o.smarttab) -- false

vim.bo.shiftwidth = 4
print(vim.bo.shiftwidth) -- 4

您可以为缓冲区本地和窗口本地选项指定一个数字。如果未给出编号,则使用当前缓冲区 / 窗口:

vim.bo[4].expandtab = true -- same as vim.api.nvim_buf_set_option(4, 'expandtab', true)
vim.wo.number = true -- same as vim.api.nvim_win_set_option(0, 'number', true)

See also:

  • :help lua-vim-internal-options

Caveats

WARNING:以下部分基于我做的几个实验。文档似乎没有提到这种行为,我也没有检查源代码来验证我的声明。
TODO:有谁能确认一下吗?

如果您只使用 :set 命令处理过选项,那么某些选项的行为可能会让您大吃一惊。
本质上,选项可以是全局的、缓冲区 / 窗口的局部的,或者同时具有全局和局部值。

:setglobal 命令用于设置选项的全局值。
:setlocal 命令用于设置选项的本地值。
:set 命令用于设置选项的全局和局部值。

这是 :help :setglobal 的简易表格:

Commandglobal valuelocal value
:set option=valuesetset
:setlocal option=value-set
:setglobal option=valueset-

Lua 中没有 :set 命令的等价命令,可以全局设置,也可以本地设置。
您可能认为 number 选项是全局的,但文档将其描述为 Windows-local。这样的选项实际上是“粘性的”:当您打开一个新窗口时,它们的值是从当前窗口复制过来的。
因此,如果您要从您的 init.lua 设置选项,您应该这样做:

vim.wo.number = true

shiftwidthexpandtabundofile 等本地到缓冲区的选项更容易混淆。假设您的 init.lua 包含以下代码:

vim.bo.expandtab = true

当你启动 Neovim 并开始编辑时,一切都很好:按下 <Tab> 键会插入空格,而不是制表符。打开另一个缓冲区,您会突然返回到选项卡…

在全局设置它具有相反的问题:

vim.o.expandtab = true

这次,您在第一次启动 Neovim 时插入选项卡。打开另一个缓冲区,然后按 <Tab> 即可实现预期效果。
简而言之,如果您想要正确的行为,“本地到缓冲区”的选项必须这样设置:

vim.bo.expandtab = true
vim.o.expandtab = true

See also:

  • :help :setglobal
  • :help global-local

TODO: Why does this happen? Do all buffer-local options behave this way? Might be related to neovim/neovim#7658 and vim/vim#2390. Also for window-local options: neovim/neovim#11525 and vim/vim#4945

管理 vim 的内部变量

使用 api 函数

与选项非常的相似,内部变量也有自己的 api 函数:

  • 全局变量 (g:):
    • vim.api.nvim_set_var()
    • vim.api.nvim_get_var()
    • vim.api.nvim_del_var()
  • 缓冲区变量 (b:):
    • vim.api.nvim_buf_set_var()
    • vim.api.nvim_buf_get_var()
    • vim.api.nvim_buf_del_var()
  • 窗口变量 (w:):
    • vim.api.nvim_win_set_var()
    • vim.api.nvim_win_get_var()
    • vim.api.nvim_win_del_var()
  • 选项卡变量 (t:):
    • vim.api.nvim_tabpage_set_var()
    • vim.api.nvim_tabpage_get_var()
    • vim.api.nvim_tabpage_del_var()
  • 预定义的 vim 变量 (v:):
    • vim.api.nvim_set_vvar()
    • vim.api.nvim_get_vvar()

除了预定义的 Vim 变量外,还可以删除它们(等同于 Vimscript 中的 :unlet)。局部变量 (l:)、脚本变量 (s:) 和函数参数 (a:) 不能操作,因为它们只在 Vim 脚本上下文中有意义,Lua 有自己的作用域规则。
如果您不熟悉这些变量的作用,请参考 :help internal-variables 对其进行详细介绍。
这些函数接受一个字符串,该字符串包含要设置 / 获取 / 删除的变量的名称以及要将其设置为的值。

vim.api.nvim_set_var('some_global_variable', { key1 = 'value', key2 = 300 })
print(vim.inspect(vim.api.nvim_get_var('some_global_variable'))) -- { key1 = "value", key2 = 300 }
vim.api.nvim_del_var('some_global_variable')

范围为缓冲区、窗口或选项卡的变量会接受一个数字类型的参数 (0 意味着设置 / 获取 / 删除当前缓冲区 / 窗口 / 选项卡页的变量):

vim.api.nvim_win_set_var(0, 'some_window_variable', 2500)
vim.api.nvim_tab_set_var(3, 'some_tabpage_variable', 'hello world')
print(vim.api.nvim_win_get_var(0, 'some_window_variable')) -- 2500
print(vim.api.nvim_buf_get_var(3, 'some_tabpage_variable')) -- 'hello world'
vim.api.nvim_win_del_var(0, 'some_window_variable')
vim.api.nvim_buf_del_var(3, 'some_tabpage_variable')

使用元访问器

使用这些元访问器可以更直观地操作内部变量:

  • vim.g.{name}: 全局变量
  • vim.b.{name}: 缓冲区变量
  • vim.w.{name}: 窗口变量
  • vim.t.{name}: 选项卡变量
  • vim.v.{name}: 预定义变量
vim.g.some_global_variable = {
    key1 = 'value',
    key2 = 300
}

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

删除变量只需要将它的值设置为 nil

vim.g.some_global_variable = nil

Caveats

Unlike options meta-accessors, you cannot specify a number for buffer/window/tabpage-scoped variables.

Additionally, you cannot add/update/delete keys from a dictionary stored in one of these variables. For example, this snippet of Vimscript code does not work as expected:
与选项元访问器不同,您不能为缓冲区 / 窗口 / 选项卡页范围的变量指定数字。
此外,您不能从存储在这些变量之一的字典中添加 / 更新 / 删除键。 例如,这段 Vimscript 代码不能按预期工作:

let g:variable = {}
lua vim.g.variable.key = 'a'
echo g:variable
" {}

这是个已知的问题

  • Issue #12544

调用 Vimscript 函数

vim.call()

vim.call() 调用 Vimscript 函数。这可以是内置 Vim 函数,也可以是用户函数。同样,数据类型在 Lua 和 Vimscript 之间来回转换。它接受函数名,后跟要传递给该函数的参数:

print(vim.call('printf', 'Hello from %s', 'Lua'))

local reversed_list = vim.call('reverse', { 'a', 'b', 'c' })
print(vim.inspect(reversed_list)) -- { "c", "b", "a" }

local function print_stdout(chan_id, data, name)
    print(data[1])
end

vim.call('jobstart', 'ls', { on_stdout = print_stdout })

vim.call('my#autoload#function')

See also:

  • :help vim.call()

vim.fn.{function}()

vim.fn 的功能与 vim.call() 完全相同,但看起来更像是原生 Lua 函数调用

print(vim.fn.printf('Hello from %s', 'Lua'))

local reversed_list = vim.fn.reverse({ 'a', 'b', 'c' })
print(vim.inspect(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 })

Hashes # 不是 Lua 中识别符的有效字符,因此必须使用以下语法调用 autoload 函数:

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

See also:

  • :help vim.fn

Tips

Neovim 有一个强大的内置函数库,这些函数对插件非常有用。按字母顺序排列的函数列表参见 :help vim-function,按主题分组的函数列表参见 :help function-list

Caveats

一些应该返回布尔值的 Vim 函数返回 10。这在 Vimscript 中不是问题,因为 1 是真的,而 0 是假的,支持如下结构:

if has('nvim')
    " do something...
endif

然而,在 Lua 中,只有 falsenil 被认为是假的,数字的计算结果总是 true,无论它们的值是什么。您必须显式检查 10

if vim.fn.has('nvim') == 1 then
    -- do something...
end

定义映射

Neovim 提供了一系列的 api 函数来设置获取和删除映射:

  • 全局映射:
    • vim.api.nvim_set_keymap()
    • vim.api.nvim_get_keymap()
    • vim.api.nvim_del_keymap()
  • 缓冲区映射:
    • vim.api.nvim_buf_set_keymap()
    • vim.api.nvim_buf_get_keymap()
    • vim.api.nvim_buf_del_keymap()

让我们从 vim.api.nvim_set_keymap()vim.api.nvim_buf_set_keymap() 开始,传递给函数的第一个参数是一个包含映射生效模式名称的字符串:

String valueHelp pageAffected modesVimscript equivalent
'' (an empty string)mapmode-nvoNormal, Visual, Select, Operator-pending:map
'n'mapmode-nNormal:nmap
'v'mapmode-vVisual and Select:vmap
's'mapmode-sSelect:smap
'x'mapmode-xVisual:xmap
'o'mapmode-oOperator-pending:omap
'!'mapmode-icInsert and Command-line:map!
'i'mapmode-iInsert:imap
'l'mapmode-lInsert, Command-line, Lang-Arg:lmap
'c'mapmode-cCommand-line:cmap
't'mapmode-tTerminal:tmap

第二个参数是包含映射左侧的字符串(触发映射中定义的命令的键或键集)。空字符串相当于 <Nop>,表示禁用键位。

第三个参数是包含映射右侧(要执行的命令)的字符串。

最后一个参数是一个表,包含 :help :map-arguments 中定义的映射的布尔值选项(包括 noremap,不包括 buffer)。

缓冲区-本地映射也将缓冲区编号作为其第一个参数(0 设置当前缓冲区的映射)。

vim.api.nvim_set_keymap('n', '<leader><Space>', ':set hlsearch!<CR>', { noremap = true, silent = true })
-- :nnoremap <silent> <leader><Space> :set hlsearch<CR>

vim.api.nvim_buf_set_keymap(0, '', 'cc', 'line(".") == 1 ? "cc" : "ggcc"', { noremap = true, expr = true })
-- :noremap <buffer> <expr> cc line('.') == 1 ? 'cc' : 'ggcc'

vim.api.nvim_get_keymap() 接受一个字符串,该字符串包含您想要映射列表的模式的短名称(见上表)。返回值是包含该模式的所有全局映射的表。

print(vim.inspect(vim.api.nvim_get_keymap('n')))
-- :verbose nmap

vim.api.nvim_buf_get_keymap() 将缓冲区编号作为其第一个参数 (0 将获取当前缓冲区的映射)

print(vim.inspect(vim.api.nvim_buf_get_keymap(0, 'i')))
-- :verbose imap <buffer>

vim.api.nvim_del_keymap() 获取映射左侧的模式。

vim.api.nvim_del_keymap('n', '<leader><Space>')
-- :nunmap <leader><Space>

同样,vim.api.nvim_buf_del_keymap() 以缓冲区编号作为第一个参数,其中 0 表示当前缓冲区。

vim.api.nvim_buf_del_keymap(0, 'i', '<Tab>')
-- :iunmap <buffer> <Tab>

定义用户命令

目前在 Lua 中没有创建用户命令的接口。不过已经在计划内:

  • Pull request #11613

目前,您最好使用 Vimscript 创建命令。

定义自动命令

AUGROUP 和 AUTOCOMMAND 还没有接口,但正在处理

  • Pull request #12378

不过你可以使用 vim.api.nvim_command 来创建自动命令,例如:

-- Create autocmd
vim.api.nvim_command('autocmd FileType * do something')

-- Create augroup
vim.api.nvim_command('augroup groupname')
vim.api.nvim_command('autocmd do something')
vim.api.nvim_command('augroup end')

定义语法高亮

vim.api.nvim_buf_add_highlight 为 buffer 指定的行和位置添加高亮

Neovim 0.5 集成 treesitter,对于语法高亮相比之前使用正则的方式更加的高效,nvim-treesitter

  • :help lua-treesitter

我个人制作的主题 zephyr-nvim 语法高亮基于 nvim-treesitter

General tips and recommendations

TODO:

  • Hot-reloading of modules
  • vim.validate()?
  • Add stuff about unit tests? I know Neovim uses the busted framework, but I don’t know how to use it for plugins
  • Best practices? I’m not a Lua wizard so I wouldn’t know
  • How to use LuaRocks packages (wbthomason/packer.nvim?)

Miscellaneous

vim.loop

vim.loop 是暴露 LibUV 接口的模块。

local stop_signal = false

local function set_variable()
  for i = 1, 10, 1 do
    print(i)
    if i == 5 then
      stop_signal= true
      break
    end
  end
end

local function start_close_timer()
  local timer = vim.loop.new_timer()
  timer:start(10,1,vim.schedule_wrap(function()
  if stop_signal == true and timer:is_closing() == false then
    print('stop timer and close it')
    timer:stop()
    timer:close()
  end
  end))
end

set_variable()
start_close_timer()
  • Official documentation for LibUV
  • Luv documentation
  • teukka.tech - Using LibUV in Neovim

See also:

  • :help vim.loop

vim.lsp

vim.lsp 是内置的 lsp 库。官方的 lsp 配置插件 neovim/nvim-lspconfig

  • nvim-lua/completion-nvim
  • nvim-lua/diagnostic-nvim

See also:

  • :help lsp

vim.treesitter

vim.treesitter 是 Tree-sitter 的 neovim 集成,如果你想了解更多可以参考 presentation (38:37).

The nvim-treesitter organisation hosts various plugins taking advantage of the library.

See also:

  • :help lua-treesitter

Transpilers

One advantage of using Lua is that you don’t actually have to write Lua code! There is a multitude of transpilers available for the language.

  • Moonscript

Probably one of the most well-known transpilers for Lua. Adds a lots of convenient features like classes, list comprehensions or function literals. The svermeulen/nvim-moonmaker plugin allows you to write Neovim plugins and configuration directly in Moonscript.

  • Fennel

A lisp that compiles to Lua. You can write configuration and plugins for Neovim in Fennel with the Olical/aniseed plugin. Additionally, the Olical/conjure plugin provides an interactive development environment that supports Fennel (among other languages).

Other interesting projects:

  • TypeScriptToLua/TypeScriptToLua
  • teal-language/tl
  • Haxe
  • SwadicalRag/wasm2lua
  • hengestone/lua-languages
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 neovim 中使用 Lua 的相关文章

  • 逐行编写 Lua 脚本

    我使用以下命令将 Lua 脚本添加到我的 C 应用程序中动态Lua http dynamiclua codeplex com 图书馆 它运作得很好 我想实现您获取正在执行的当前行 就像在 Visual Studio 中一样 并突出显示它 目
  • 寻找 Lua 4.1 alpha

    我正在帮助为一款相当老的游戏 孤岛惊魂 开发多人模式 我想编译lua代码 但游戏使用版本4 1 alpha 我在任何地方都找不到 lua 4 1 alpha tar gz http www lua org work old lua 4 1
  • Lua:C++模块无法互相引用,未定义的符号

    我创建了两个模块 共享对象 CPU 和 SaveState 作为模拟器的一部分 两者都独立编译成 so 单独文件 并在运行时由 Lua 脚本使用 require 加载 IE SaveState require SaveState CPU r
  • 在 lua 中使用相等运算符比较数字有多安全?

    在我的引擎中 我有一个用于脚本编写的 Lua VM 在脚本中 我写了这样的内容 stage stage 1 if stage 5 then end and objnum tonumber 5 if stage objnum 根据 Lua 来
  • 如何访问废弃的函数参数?

    在 Lua 中 调用带有多余参数的函数将简单地丢弃这些参数 有没有可能与debug库来访问这些被丢弃的参数 我不是在寻找可变参数函数 function test local info debug getinfo 1 u print info
  • 十六进制常数 = 格式错误的数字?

    我有一个 Lua 脚本 我试图在其中使用十六进制数字 0x 如果我使用官方 Windows 二进制文件在控制台中运行此脚本 它可以正常工作 但是如果我在我的应用程序中运行它 简单的 dofile 我得到 malformed number n
  • Lua中如何对数字表求和?

    Lua有内置的吗sum 功能 我似乎找不到一个 我几乎翻遍了文档中的所有地方 或许table sum 或类似的东西 以遵循当前的约定 但由于我找不到它 我不得不实现它 function sum t local sum 0 for k v i
  • 使用 corona sdk 验证电子邮件地址

    在我的项目中 有一个供用户填写详细信息的表单 其中有一个文本字段用于输入用户的电子邮件 ID 所以我需要在 corona 项目中验证该文本字段中的电子邮件 试试这个正则表达式 local email email protected cdn
  • 如何解密Lua字节码?

    早上好 我正在尝试破译 Moon 字节码 但我无法以任何方式 有人可以帮助我吗 我有这个 例如 code 27 76 117 97 81 0 1 4 4 4 8 0 如何将此字节码解密为文本 我已经在这里搜索 http www asciit
  • 为什么 LuaJIT 和 Lua 中的数字舍入格式不同?

    Using string format 据说遵循 Csprintf 在 LuaJIT 轮次中格式化数字与我尝试过的所有其他 Lua 解释器不同 lua v Lua 5 4 1 Copyright C 1994 2020 Lua org PU
  • lua http套接字超时

    LuaSocket HTTP 模块文档说可以在 HTTP 连接上设置超时 可以设置以下常量来控制 HTTP 模块的默认行为 PORT 用于连接的默认端口 PROXY 用于连接的默认代理 TIMEOUT 设置所有I O操作的超时时间 USER
  • 如何在aerospike中获取ttl为-1的记录集?

    我在aerospike中有很多记录 我想获取ttl为 1的记录 请提供解决方案 只是为了澄清 设置TTL 为 1 https github com aerospike aerospike client go blob master docs
  • 如何在 Lua 中实现 OO?

    Lua 没有内置对 OO 的支持 但它允许您自己构建它 您能否分享一些实现面向对象的方法 请为每个答案写一个例子 如果您有更多示例 请发布另一个答案 我喜欢将 OOP 视为容器 对象 内的数据封装以及可以使用该数据完成的操作子集 还有很多内
  • 如何在Conky中实现一个基本的Lua功能?

    我正在尝试向我的 Conky 添加一个函数 该函数打印字符串的长度以用于调试目的 代码位于名为的文件内test lua 非常简单 function test word return string len word end 我这样加载它 在我
  • lua中的权限问题

    是否需要在 corona build settings 中设置一些特定权限才能将高分永久保存在文件中 每次运行代码时都会出现 权限被拒绝 的错误 如何纠正这个错误 这是我尝试过的代码 function read score local f1
  • 安装Lua套接字库

    要么我太累了 要么我瞎了 我想学习 Lua 网络 因此我必须安装socketlib 所以我可以轻松地要求它 但我不知道我应该 要求 哪些文件 例子说 local socket require socket 但正如我所说 如果我使用 我不知道
  • Lua中按字符分割字符串

    我有像这样的字符串 ABC DEF 我需要将它们分开 字符并将两个部分分别分配给一个变量 在 Ruby 中 我会这样做 a b ABC DEF split 显然Lua没有这么简单的方法 经过一番挖掘后 我找不到一种简短的方法来实现我所追求的
  • 如何使用 srlua 制作可执行的 Lua 脚本?

    我的主要目标是使我的 lua 文件成为可执行文件或使其成为咬代码 最好是两者皆有 我正在尝试 srlua 但在自述文件中它告诉我要做的事情 对于Windows 您需要首先创建srlua exe和glue exe 然后为每个 你想把Lua程序
  • Lua 访问表的键和值

    我想在关卡编辑器中读取 Lua 文件 这样我就可以以可视化格式显示其数据供用户编辑 如果我有一个像这样的 Lua 表 properties Speed 10 TurnSpeed 5 Speed显然是关键并且10价值 我知道如果我知道像这样的
  • 关闭 Löve2D 中的抗锯齿功能

    我在用着L ve2D http love2d org用于编写一个小游戏 L ve2D 是 Lua 的开源游戏引擎 我遇到的问题是 当您在非整数位置绘制精灵时 某些抗锯齿过滤器会自动应用于精灵 love graphics draw sprit

随机推荐

  • CentOS7一键开启VNC服务脚本

    最近在研究舒适 xff0c 小巧 xff0c 好用的图形界面远程控制 输入法这块卡了我很久时间 xff0c 经过一周的努力 xff0c 查资料无数 xff0c 终于打造出一套可以令自己满意的远程方案了 我的工作中接触的远程Linux服务系统
  • shell用户输入数字加法操作

    用户输入2个数字进行加法操作 span class token builtin class name echo span span class token string 34 请输入数字1 34 span span class token
  • 浅谈linux文件权限

    权限介绍 访问权限 读取 xff08 r xff09 xff1a 允许查看文件内容 xff0c 显示目录列表写入 xff08 w xff09 xff1a 允许修改文件内容 xff0c 允许在目录中新建 删除 移动文件或者子目录可执行 xff
  • Linux实用脚本分享

    源码 xff1a https gitee com lxyoucan tools CentOS7专用脚本 因为我平时接触的大部分机器都是CentOS7 这里的脚本都是针对CentOS7开发的 虽然个人比较喜欢新的软件 xff0c 喜欢arch
  • react踩坑之函数名(){}与函数名=()=>{}的区别

    在React Component组件中 xff0c 函数不同的写法有什么区别 最近在看书 xff0c 照着写敲代码 xff0c 敲完后发现与书上效果不一样 找了很久才找到区别 打这个小坑记录下来 xff0c 这个应该新手比较容易犯的错 结论
  • chrome全屏

    有一些web网站可能当作单独的应用使用 如果全屏显示 xff0c 不显示地址栏 让更多的区域给应用呢 xff1f 首先说一下 xff0c 我用的是Macos系统 xff0c 其他系统的参数应该是类似的 MacOS MacOS配置别名 默认的
  • expo cli升级后无法iOS模拟器调试Request failed with status code 400

    国内expo的教程和文档相对比较少 xff0c 记录下过程方便一下遇到相同问题的朋友们 现象 expo初次安装使用正常 xff0c 当升级expo cli到最新版本后 xff0c 无法正常使用iOS模拟器调试了 开启调试报错如下 xff1a
  • docker-compose 巧妙使用

    一般做ctf题目 xff0c 有时候题目给了docker 一般 docker compose up build d d表示运行成果后进入后台 这个命令不仅帮我们build image xff0c 也帮我们run了个容器 docker com
  • expo中使用react navigation 6.x笔记

    新版本react navigation 6 x xff0c 与5 x有什么区别呢 xff1f 记个笔记吧 xff01 文章目录 环境安装依赖安装 native stack navigator library示例 xff1a 导航到一个新的屏
  • windows10安装子系统Ubuntu 20.04

    很久之前就听说了windows10的子系统 因为平时用macOS比较多 xff0c 一直没有体验一把 最近切到windows环境下开发 xff0c 正好有空体验一把windows的子系统 文章目录 安装环境先决条件检查版本 实战程序与功能启
  • Termux配置neovim IDE

    之前玩Termux基本就是通过proot distro来安装Linux发行版本 xff0c 然后在发行版本中在配置neovim环境 这样比较简单一些 xff0c 遇到问题也可以直接网上搜索对应发行版本的解决办法就可以了 今天挑战一下直接在T
  • sed:-e 表达式 #1,字符 11:“s”的未知选项

    报错的命令 apex 64 localhost span class token function sed span i span class token string 34 s lt USER gt span class token en
  • 手机也有生产力,手把手教你用手机开发APP

    只有手机没有电脑 xff0c 能不能学编程 xff1f 能 xff01 今天的教程献给 xff0c 那些没有电脑但是对电脑或者编程有浓厚兴趣的同学 如果你有手机 xff0c 但是没电脑或者电脑不在身边 xff0c 也许这个教程能对你学习编程
  • coc-lua安装报错解决办法

    项目地址 xff1a https github com josa42 coc lua 现象 macOS中使用正常CentOS7有异常 环境CentOS7中 执行命令 CocInstall coc lua成功安装后 xff0c 重启nvim
  • Lua学习笔记

    neovim 0 5正式版本已经发布了 xff0c 现在学习Lua语言还来的及吧 lua替换vim脚本渐渐成为了趋势了 Lua学习下来 xff0c 感觉与javascript特别的像 就是代码块不是使用 分割 xff0c 而是end xff
  • neovim0.5笔记

    neovim0 5版本更新比较大 xff0c 有一些大佬开始抛弃coc xff0c 而使用native lsp了 学习一下记录一下 我参考的是 xff1a https github com craftzdog dotfiles public
  • vim-airline底部状态栏不显示图标的解决办法

    现象 正常的 以前我使用的airline都是有图标显示的 xff0c 如下图所示 不正常的 最近刚升级neovim0 5 尽可能的使用lua配置 xff0c 把环境重新配置了一下 后来就发现我的nvim下面的状态栏 xff0c 不显示图标了
  • LuaSnip代码段使用的正确姿势

    自己升级了neovim 0 5 以来各种尝试从coc切换到native lsp 在coc中代码段配置非常的简单 我之前有文章记录过 vim自动关闭标签 https blog csdn net lxyoucan article details
  • python一键删除误触复制的文件

    现在拍照有时候多选照片不小心变成了复制 xff0c 一个个删除太麻烦 xff0c 就出现了如下脚本 import os for file in os listdir 39 39 if 39 副本 39 in file os remove f
  • 在 neovim 中使用 Lua

    原文 xff1a https github com glepnir nvim lua guide zh nvim lua guide 中文版简易教程 译者 Neovim Core Developer 文章目录 在 neovim 中使用 Lu