├── LICENSE └── README.md /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Raphael 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 在 neovim 中使用 Lua 2 | 3 | **在neovim 0.9版本中添加了lua教程,如果你使用该版本的neovim可以使用 `:h lua-guide` 查看** 4 | **对于英文不好的同学那么此文档对你仍然有用** 5 | 6 | [nvim-lua-guide](https://github.com/nanotee/nvim-lua-guide) 中文版简易教程 7 | 8 | 译者:Neovim Core Developer 9 | 10 | :arrow_upper_left: (感觉太多太杂乱?使用 Github TOC 来浏览大纲!) 11 | 12 | ## 简介 13 | 14 | Lua 作为 Neovim 中的一等语言的集成正在成为它的杀手级特性之一。然而,学习如何用 Lua 编写插件的教程数量并不像用 Vimscript 编写插件那样多。这是一种尝试,试图提供一些基本信息,让人们可以使用 Lua 编写 Neovim 插件。 15 | 16 | 本指南假定您使用的是 Neovim 0.5+ 17 | 18 | ### 学习 Lua 19 | 20 | 不同于原版教程,以下资源适用于国内用户: 21 | - [在 Y 分钟内学习 X 关于 Lua 的页面](https://learnxinyminutes.com/docs/lua/) 22 | - [Lua 菜鸟教程](https://www.runoob.com/lua/lua-tutorial.html) 23 | - [Lua 用户维基](http://lua-users.org/wiki/LuaDirectory) 24 | - [Lua 的官方参考手册](https://www.lua.org/manual/5.1/) 25 | 26 | Lua 是一种非常干净和简单的语言。它很容易学习,特别是如果你有其他编程语言基础的例如 TypeScript / JavaScript 等,会更加容易上手 Lua。注意:Neovim 嵌入的 Lua 版本是 LuaJIT 2.1.0,它与 Lua 5.1 保持兼容(带有几个 5.2 扩展) 27 | 28 | ### 现有的一些在 Neovim 中使用 Lua 的教程 29 | 30 | 已经编写了一些教程来帮助人们用 Lua 编写插件。他们中的一些人在写这本指南时提供了不少的帮助。非常感谢它们的作者。 31 | 32 | - [teukka.tech - 从 init.vim 转到 init.lua](https://teukka.tech/luanvim.html) 33 | - [2n.pl - 如何使用 Lua 编写 neovim 插件](https://www.2n.pl/blog/how-to-write-neovim-plugins-in-lua.md) 34 | - [2n.pl - 如何使用 Lua 制作 neovim UI](https://www.2n.pl/blog/how-to-make-ui-for-neovim-plugins-in-lua) 35 | - [ms-jpq - NeoVim 异步教程](https://ms-jpq.github.io/lua-async-await/index.html) 36 | 37 | ### 相关插件 38 | 39 | - [Vimpeccable](https://github.com/svermeulen/vimpeccable) - Plugin to help write your .vimrc in Lua 40 | - [plenary.nvim](https://github.com/nvim-lua/plenary.nvim) - All the lua functions I don't want to write twice 41 | - [popup.nvim](https://github.com/nvim-lua/popup.nvim) - An implementation of the Popup API from vim in Neovim 42 | - [nvim_utils](https://github.com/norcalli/nvim_utils) 43 | - [nvim-luadev](https://github.com/bfredl/nvim-luadev) - REPL/debug console for nvim lua plugins 44 | - [nvim-luapad](https://github.com/rafcamlet/nvim-luapad) - Interactive real time neovim scratchpad for embedded lua engine 45 | - [nlua.nvim](https://github.com/tjdevries/nlua.nvim) - Lua Development for Neovim 46 | - [galaxyline.nvim](https://github.com/glepnir/galaxyline.nvim) - neovim statusline plugin written in lua 47 | - [BetterLua.vim](https://github.com/euclidianAce/BetterLua.vim) - Better Lua syntax highlighting in Vim/NeoVim 48 | 49 | ## Lua 文件位置 50 | 51 | ### init.lua 52 | 53 | Neovim 支持从 `init.lua` 文件加载配置而不是通常的 `init.vim` 文件。 54 | 55 | 注意:`init.lua` 文件是完全可选的。Neovim 仍然支持从 `init.vim` 加载配置。请记住,Neovim 的一些功能还没有 100% 暴露给 Lua 模块部分。 56 | 57 | ### 模块 58 | 59 | Lua 模块通常位于您的 `runtimepath` 中的 `lua/` 文件夹中(对于大多数用户来说,在 *nix 系统上为 `~/.config/nvim/lua`,在 Windows 系统上为 `~/appdata/Local/nvim/lua`)。这意味着您可以 `require()` 这些文件作为 Lua 模块 60 | 61 | 我们以下面的文件夹结构为例: 62 | 63 | ```text 64 | 📂 ~/.config/nvim 65 | ├── 📁 after 66 | ├── 📁 ftplugin 67 | ├── 📂 lua 68 | │ ├── 🌑 myluamodule.lua 69 | │ └── 📂 other_modules 70 | │ ├── 🌑 anothermodule.lua 71 | │ └── 🌑 init.lua 72 | ├── 📁 pack 73 | ├── 📁 plugin 74 | ├── 📁 syntax 75 | └── 🇻 init.vim 76 | ``` 77 | 78 | 下面的 Lua 代码将加载 `myluamodule.lua` 79 | 80 | ```lua 81 | require('myluamodule') 82 | ``` 83 | 84 | 注意没有 `.lua` 扩展名。 85 | 86 | 类似地,加载 `other_modules/anothermodule.lua` 的过程如下: 87 | 88 | ```lua 89 | require('other_modules.anothermodule') 90 | -- or 91 | require('other_modules/anothermodule') 92 | ``` 93 | 94 | 路径分隔符可以用点 `.` 表示,也可以用斜杠 `/` 表示。 95 | 96 | 文件夹如果包含 `init.lua` 文件,可以直接引用该文件夹而不必指定该文件的名称 97 | 98 | ```lua 99 | require('other_modules') -- loads other_modules/init.lua 100 | ``` 101 | 102 | 加载一个不存在的模块或者加载的模块有语法错误会直接中止当前正在执行的脚本。`pcall()` 函数可以用来处理这类错误 103 | 104 | ```lua 105 | local ok, _ = pcall(require, 'module_with_error') 106 | if not ok then 107 | -- not loaded 108 | end 109 | ``` 110 | 111 | 更多信息请参见: 112 | 113 | * [`:help lua-require`](https://neovim.io/doc/user/lua.html#lua-require) 114 | 115 | #### 提示 116 | 117 | 多个 Lua 插件在它们的 `lua/` 文件夹中可能有相同的文件名。这可能会导致命名空间冲突。如果两个不同的插件有一个 `lua/main.lua` 文件,那么执行 `require('main')` 是不明确的:我们想要加载哪个文件?最好将您的配置或插件命名为顶级文件夹, 118 | 例如这样的形式:`lua/plugin_name/main.lua`。 119 | 120 | ### 运行时文件 121 | 122 | 和 Vimscript 文件很像,位于 ` runtimepath` 中的一些特殊目录中的 Lua 文件可以被 Neovim 自动加载。目前有以下这些特殊目录: 123 | 124 | * `colors/` 125 | * `compiler/` 126 | * `ftplugin/` 127 | * `indent/` 128 | * `plugin/` 129 | * `syntax/` 130 | 131 | 注意:在同一个运行时目录中,`*.vim` 文件会先于所有的 `*.lua` 文件被加载。 132 | 133 | 更多信息请参见: 134 | 135 | * [`:help 'runtimepath'`](https://neovim.io/doc/user/options.html#'runtimepath') 136 | * [`:help load-plugins`](https://neovim.io/doc/user/starting.html#load-plugins) 137 | 138 | #### 提示 139 | 140 | 因为运行时文件并不基于 Lua 模块系统,所以两个不同的插件都拥有 `plugins/main.lua` 文件是没有任何问题的。 141 | 142 | ## 在 Vimscript 中使用 Lua 143 | 144 | ### :lua 145 | 146 | 该命令执行一段 Lua 代码 147 | 148 | ```vim 149 | :lua require('myluamodule') 150 | ``` 151 | 152 | 可以使用以下语法编写多行脚本: 153 | 154 | ```vim 155 | echo "Here's a bigger chunk of Lua code" 156 | 157 | lua << EOF 158 | local mod = require('mymodule') 159 | local tbl = {1, 2, 3} 160 | 161 | for k, v in ipairs(tbl) do 162 | mod.method(v) 163 | end 164 | 165 | print(tbl) 166 | EOF 167 | ``` 168 | 169 | 注意:每个 `:lua` 命令都有它自己独立的作用域,在一条 `:lua` 命令中使用 `local` 关键字声明的变量是无法在这条命令之外访问的。如下代码所示 170 | 171 | ```vim 172 | :lua local foo = 1 173 | :lua print(foo) 174 | " prints 'nil' instead of '1' 175 | ``` 176 | 177 | 注意:Lua 中的 `print()` 函数的行为类似于 `:echomsg` 命令。它的输出会被保存在消息历史中,可以使用 `:slient` 命令来抛弃输出。 178 | 179 | 更多信息请参见: 180 | 181 | - [`:help :lua`](https://neovim.io/doc/user/lua.html#Lua) 182 | - [`:help :lua-heredoc`](https://neovim.io/doc/user/lua.html#:lua-heredoc) 183 | 184 | ### :luado 185 | 186 | 该命令执行一段 Lua 代码,该代码作用于当前缓冲区中的选中的行。如果未指定范围,则改为使用整个缓冲区。从块 `return` 的任何字符串都用于确定应该用什么替换每行。 187 | 188 | 以下命令会将当前缓冲区中的每一行替换为文本 `hello world` 189 | 190 | ```vim 191 | :luado return 'hello world' 192 | ``` 193 | 194 | 提供了两个隐式的 `line` 和 `linenr` 变量。`line` 是被迭代的行的文本,而 `linenr` 是它的编号。以下命令将可以被 2 整除的行转成大写: 195 | 196 | ```vim 197 | :luado if linenr % 2 == 0 then return line:upper() end 198 | ``` 199 | 200 | 更多信息请参见: 201 | 202 | - [`:help :luado`](https://neovim.io/doc/user/lua.html#:luado) 203 | 204 | ### 加载 Lua 文件 205 | 206 | Neovim 提供了三种执行命令来加载 Lua 文件 207 | 208 | * `:luafile` 209 | * `:source` 210 | * `:runtime` 211 | 212 | 其中 `:luafile` 和 `:source` 非常类似: 213 | 214 | ```vim 215 | :luafile ~/foo/bar/baz/myluafile.lua " 加载 myluafile.lua 216 | :luafile % " 加载当前正在处理的文件 217 | :source ~/foo/bar/baz/myluafile.lua 218 | :source % 219 | ``` 220 | 221 | 两种命令都可以指定文件范围,可以只执行脚本的一部分 222 | 223 | ```vim 224 | :1,10source 225 | ``` 226 | 227 | `:runtime` 与上述两种命令有所不同,它使用 `'runtimepath'` 选项来决定加载哪个文件。更多细节信息请参见 [`:help :runtime`](https://neovim.io/doc/user/repeat.html#:runtime) 228 | 229 | 更多信息请参见: 230 | 231 | - [`:help :luafile`](https://neovim.io/doc/user/lua.html#:luafile) 232 | - [`:help :source`](https://neovim.io/doc/user/repeat.html#:source) 233 | - [`:help :runtime`](https://neovim.io/doc/user/repeat.html#:runtime) 234 | 235 | #### 加载 Lua 文件对比 require(): 236 | 237 | 您可能想知道调用 `require()` 函数和使用上述命令加载之间的区别是什么,以及您是否应该使用其中一个而不是另一个。它们有不同的使用情形: 238 | 239 | - `require()`: 240 | - 是内置的 Lua 函数,它允许你使用 Lua 的模块系统 241 | - 在 `'runtimepath'` 中的 `lua/` 文件夹中搜索模块 242 | - 跟踪已加载的模块,并防止第二次解析和执行脚本。如果您更改包含某个模块代码的文件,并在 Neovim 运行时再次尝试 `required()`,则该模块实际上不会更新 243 | - `:luafile`/`:source`/`:runtime`: 244 | - 是一个执行命令,它不支持模块 245 | - 执行脚本的内容,而不管该脚本以前是否执行过 246 | - `:luafile`/`:source` 命令采用相对于当前窗口的工作目录的绝对或相对路径 247 | - `:runtime` 命令使用 `'runtimepath'` 选项来寻找文件 248 | 249 | 同时,通过 `:source`/`:runtime` 命令(不包括 `:luafile` )加载或者从运行时目录被自动加载的文件会显示在 `:scriptnames` 和 `--startuptime` 中。 250 | 251 | ### luaeval() 252 | 253 | `luaeval()` 是内置的 Vimscript 函数计算 Lua 表达式字符串并返回它的值。Lua 的数据类型自动转换为 Vimscript 类型(反之亦然)。 254 | 255 | ```vim 256 | " 你可以将结果存储到一个变量中 257 | let variable = luaeval('1 + 1') 258 | echo variable 259 | " 2 260 | let concat = luaeval('"Lua".." is ".."awesome"') 261 | echo concat 262 | " 'Lua is awesome' 263 | 264 | " Lua 中的 table 数组转成成 Vimscript 的 list 265 | let list = luaeval('{1, 2, 3, 4}') 266 | echo list[0] 267 | " 1 268 | echo list[1] 269 | " 2 270 | " 注意 Vimscript 的数组索引下标与 Lua 不同是从 0 开始的,Lua 中是从 1 开始 271 | 272 | " Lua 中类似 dict 的 table 会被转成 Vimscript 中的 dict 273 | let dict = luaeval('{foo = "bar", baz = "qux"}') 274 | echo dict.foo 275 | " 'bar' 276 | 277 | " 布尔类型和 nil 是类似的 278 | echo luaeval('true') 279 | " v:true 280 | echo luaeval('nil') 281 | " v:null 282 | 283 | " 您可以为 Lua 函数创建 Vimscript 别名 284 | let LuaMathPow = luaeval('math.pow') 285 | echo LuaMathPow(2, 2) 286 | " 4 287 | let LuaModuleFunction = luaeval('require("mymodule").myfunction') 288 | call LuaModuleFunction() 289 | 290 | " 还可以将 Lua 函数作为值传递给 Vim 函数 291 | lua X = function(k, v) return string.format("%s:%s", k, v) end 292 | echo map([1, 2, 3], luaeval("X")) 293 | ``` 294 | 295 | `luaeval()` 接受可选的第二个参数,该参数允许您将数据传递给表达式。然后您可以使用全局的 `_A` 从 Lua 访问该数据: 296 | 297 | ```vim 298 | echo luaeval('_A[1] + _A[2]', [1, 1]) 299 | " 2 300 | 301 | echo luaeval('string.format("Lua is %s", _A)', 'awesome') 302 | " 'Lua is awesome' 303 | ``` 304 | 305 | 更多信息请参见: 306 | - [`:help luaeval()`](https://neovim.io/doc/user/lua.html#luaeval()) 307 | 308 | ### v:lua 309 | 310 | 这个全局 Vim 变量允许您直接从 Vimscript 调用全局 Lua 函数([`_G`](https://www.lua.org/manual/5.1/manual.html#pdf-_G))。同样 Vim 数据类型被转换为 Lua 类型,反之亦然。 311 | 312 | ```vim 313 | call v:lua.print('Hello from Lua!') 314 | " 'Hello from Lua!' 315 | 316 | let scream = v:lua.string.rep('A', 10) 317 | echo scream 318 | " 'AAAAAAAAAA' 319 | 320 | " Requiring modules works 321 | call v:lua.require('mymodule').myfunction() 322 | 323 | " How about a nice statusline? 324 | lua << EOF 325 | function _G.statusline() 326 | local filepath = '%f' 327 | local align_section = '%=' 328 | local percentage_through_file = '%p%%' 329 | return string.format( 330 | '%s%s%s', 331 | filepath, 332 | align_section, 333 | percentage_through_file 334 | ) 335 | end 336 | EOF 337 | 338 | set statusline=%!v:lua.statusline() 339 | 340 | " Also works in expression mappings 341 | lua << EOF 342 | function _G.check_back_space() 343 | local col = vim.fn.col('.') - 1 344 | if col == 0 or vim.fn.getline('.'):sub(col, col):match('%s') then 345 | return true 346 | else 347 | return false 348 | end 349 | end 350 | EOF 351 | 352 | inoremap 353 | \ pumvisible() ? '\' : 354 | \ v:lua.check_back_space() ? '\' : 355 | \ completion#trigger_completion() 356 | 357 | " 通过使用单引号包围模块名并省略 require 的括号来调用 Lua 模块中的函数 358 | call v:lua.require'module'.foo() 359 | ``` 360 | 361 | 更多信息请参见: 362 | - [`:help v:lua`](https://neovim.io/doc/user/eval.html#v:lua) 363 | - [`:help v:lua-call`](https://neovim.io/doc/user/lua.html#v:lua-call) 364 | 365 | #### Caveats 366 | 367 | `v:lua` 变量只能用于调用函数。以下代码将始终引发错误: 368 | 369 | ```vim 370 | " 不适用于别名一个函数 371 | let LuaPrint = v:lua.print 372 | 373 | " 不适用于访问 dict 374 | echo v:lua.some_global_dict['key'] 375 | 376 | " 不适用于将函数作为值使用 377 | echo map([1, 2, 3], v:lua.global_callback) 378 | ``` 379 | 380 | #### 提示 381 | 382 | 在配置文件中,可以通过设置 `let g:vimsyn_embed = 'l'` 实现 .vim 文件中的 Lua 语法高亮。关于此选项的更多信息请参见 [`:help g:vimsyn_embed`](https://neovim.io/doc/user/syntax.html#g:vimsyn_embed) 383 | 384 | ## Vim 命名空间 385 | 386 | Neovim 会暴露一个全局的 `vim` 变量来作为 Lua 调用 Vim 的 APIs 的入口。它还提供给用户一些额外的函数和子模块“标准库” 387 | 388 | 一些比较实用的函数和子模块如下: 389 | 390 | - `vim.inspect`: 把 Lua 对象以更易读的方式打印(在打印 Lua table 时会很有用) 391 | - `vim.regex`: 在 Lua 中使用 Vim 正则表达式 392 | - `vim.api`: vim 暴露的 API (`:h API`) 模块(别的远程调用也是调用同样的 API) 393 | - `vim.ui`: 可被插件覆写的 UI 相关函数 394 | - `vim.loop`: Neovim 的 event loop 模块(使用 LibUV) 395 | - `vim.lsp`: 控制内置 LSP 客户端的模块 396 | - `vim.treesitter`: 暴露 tree-sitter 库中一些实用函数的模块 397 | 398 | 上面列举功能的并不全面。如果你想知道更多可行的操作可以参见:[`:help lua-stdlib`](https://neovim.io/doc/user/lua.html#lua-stdlib) 和 [`:help lua-vim`](https://neovim.io/doc/user/lua.html#lua-vim)。你也可以通过 `:lua print(vim.inspect(vim))` 获得所有可用模块。API 函数的详细文档请参见 [`:help api-global`](https://neovim.io/doc/user/api.html#api-global) 399 | 400 | #### Tips 401 | 402 | 每次你想检查一个对象时到要用 `print(vim.inspect(x))` 是相当繁琐的。你可以你的配置中写一个全局的包装器函数来替代这个繁琐的过程(在 Neovim 0.7.0+ 中,你可以使用内建的 `vim.pretty_print()` ,请参见 [`:help vim.pretty_print()`](https://neovim.io/doc/user/lua.html#vim.pretty_print())) 403 | 404 | ```lua 405 | function _G.put(...) 406 | local objects = {} 407 | for i = 1, select('#', ...) do 408 | local v = select(i, ...) 409 | table.insert(objects, vim.inspect(v)) 410 | end 411 | 412 | print(table.concat(objects, '\n')) 413 | return ... 414 | end 415 | ``` 416 | 417 | 之后你就可以使用如下命令来快速检查对象内容了 418 | 419 | ```lua 420 | put({1, 2, 3}) 421 | ``` 422 | 423 | ```vim 424 | :lua put(vim.loop) 425 | ``` 426 | 427 | 此外,你也可以使用 `:lua` 命令通过在 Lua 表达式前加上 `=` 来美观地打印它 428 | 429 | ```vim 430 | :lua =vim.loop 431 | ``` 432 | 433 | 另外要注意的是,你可能会发现 Lua 会比其他语言少一些实用的内置函数(例如:`os.clock()`,返回以秒为单位,而不是以毫秒为单位的值)。仔细阅读 Neovim 提供的标准库和 `vim.fn` (后续还会有更多内容),里面可以会有你想要的东西。 434 | 435 | ## 在 Lua 中使用 Vimscript 436 | 437 | ### vim.api.nvim_eval() 438 | 439 | 此函数计算 Vimscript 表达式字符串并返回其值。Vimscript 数据类型自动转换为 Lua 类型(反之亦然)。 440 | 441 | 它等同于 vimscript 中的 `luaeval()` 函数 442 | 443 | ```lua 444 | -- Data types are converted correctly 445 | print(vim.api.nvim_eval('1 + 1')) -- 2 446 | print(vim.inspect(vim.api.nvim_eval('[1, 2, 3]'))) -- { 1, 2, 3 } 447 | print(vim.inspect(vim.api.nvim_eval('{"foo": "bar", "baz": "qux"}'))) -- { baz = "qux", foo = "bar" } 448 | print(vim.api.nvim_eval('v:true')) -- true 449 | print(vim.api.nvim_eval('v:null')) -- nil 450 | ``` 451 | 452 | #### Caveats 453 | 454 | 与 `luaeval()` 不同,`vim.api.nvim_eval()` 不提供隐式 `_A` 变量来传递数据给表达式。 455 | 456 | ### vim.api.nvim_exec() 457 | 458 | 此函数用于计算 Vimscript 代码块。它接受一个包含要执行的源代码的字符串和一个布尔值,以确定代码的输出是否应该由函数返回(例如,您可以将输出存储在变量中)。 459 | 460 | ```lua 461 | local result = vim.api.nvim_exec( 462 | [[ 463 | let mytext = 'hello world' 464 | 465 | function! MyFunction(text) 466 | echo a:text 467 | endfunction 468 | 469 | call MyFunction(mytext) 470 | ]], 471 | true) 472 | 473 | print(result) -- 'hello world' 474 | ``` 475 | 476 | #### Caveats 477 | 478 | 在 Neovim 0.6.0 之前,`nvim_exec` 不支持 script-local 的变量 (`s:`) 。 479 | 480 | ### vim.api.nvim_command() 481 | 482 | 此函数执行一个 EX 命令。它接受包含要执行的命令的字符串。 483 | 484 | ```lua 485 | vim.api.nvim_command('new') 486 | vim.api.nvim_command('wincmd H') 487 | vim.api.nvim_command('set nonumber') 488 | vim.api.nvim_command('%s/foo/bar/g') 489 | ``` 490 | 491 | ### vim.cmd() 492 | 493 | `vim.api.nvim_exec()` 的别名。只需要命令部分的参数,`output` 参数始终为 `false` 494 | 495 | ```vim 496 | vim.cmd('buffers') 497 | vim.cmd([[ 498 | let g:multiline_list = [ 499 | \ 1, 500 | \ 2, 501 | \ 3, 502 | \ ] 503 | 504 | echo g:multiline_list 505 | ]]) 506 | ``` 507 | 508 | #### Tips 509 | 510 | 由于您必须将字符串传递给这些函数,因此通常需要添加转义的反斜杠: 511 | 512 | ```lua 513 | vim.cmd('%s/\\Vfoo/bar/g') 514 | ``` 515 | 516 | 非转义字符串更方便使用,它们不需要额外的转义反斜杠: 517 | 518 | ```lua 519 | vim.cmd([[%s/\Vfoo/bar/g]]) 520 | ``` 521 | 522 | ### vim.api.nvim_replace_termcodes() 523 | 524 | 这个 API 函数允许你转义终端代码和 Vim 键码。 525 | 526 | 你可能见过这样的映射: 527 | 528 | ```vim 529 | inoremap pumvisible() ? "\" : "\" 530 | ``` 531 | 532 | 在 Lua 中实现相同的功能是比较困难的。你可能会像这样实现 533 | 534 | ```lua 535 | function _G.smart_tab() 536 | return vim.fn.pumvisible() == 1 and [[\]] or [[\]] 537 | end 538 | 539 | vim.api.nvim_set_keymap('i', '', 'v:lua.smart_tab()', {expr = true, noremap = true}) 540 | ``` 541 | 542 | 然后你会发现这样实现上述映射只会单纯插入 `\` 和 `\` 两个字符串字面量…… 543 | 544 | 能够转义键码实际上是 Vimscript 的一项功能。 除了许多编程语言常用的转义序列(如 `\r`、`\42` 或 `\x10`)外,Vimscript 中的 `expr-quotes`(用双引号括起来的字符串)允许你转义 Vim 键码的易读形式。 545 | 546 | Lua 中并没有内置这种功能。幸运的是,Neovim 提供了一个用来转义终端代码和 Vim 键码的 API 函数:`nvim_replace_termcodes()` 547 | 548 | ```lua 549 | print(vim.api.nvim_replace_termcodes('', true, true, true)) 550 | ``` 551 | 552 | 这样调用过于冗长,我们可以创建一个方便重复调用的包装: 553 | 554 | ```lua 555 | -- The function is called `t` for `termcodes`. 556 | -- You don't have to call it that, but I find the terseness convenient 557 | local function t(str) 558 | -- Adjust boolean arguments as needed 559 | return vim.api.nvim_replace_termcodes(str, true, true, true) 560 | end 561 | 562 | print(t'') 563 | ``` 564 | 565 | 回到先前的例子,我们可以这样实现那个映射 566 | 567 | ```lua 568 | local function t(str) 569 | return vim.api.nvim_replace_termcodes(str, true, true, true) 570 | end 571 | 572 | function _G.smart_tab() 573 | return vim.fn.pumvisible() == 1 and t'' or t'' 574 | end 575 | 576 | vim.api.nvim_set_keymap('i', '', 'v:lua.smart_tab()', {expr = true, noremap = true}) 577 | ``` 578 | 579 | 如果我们使用 `vim.keymap.set()` 函数(Neovim 0.7.0+)来设置的话就不需要转义键码,`vim.keymap.set()` 默认会自动转义返回值中的键码(`expr` 选项设置为 `true`): 580 | 581 | ```lua 582 | vim.keymap.set('i', '', function() 583 | return vim.fn.pumvisible() == 1 and '' or '' 584 | end, {expr = true}) 585 | ``` 586 | 587 | 更多信息请参见: 588 | 589 | * [`:help keycodes`](https://neovim.io/doc/user/intro.html#keycodes) 590 | * [`:help expr-quote`](https://neovim.io/doc/user/eval.html#expr-quote) 591 | * [`:help nvim_replace_termcodes()`](https://neovim.io/doc/user/api.html#nvim_replace_termcodes()) 592 | 593 | ## 管理 vim 的设置选项 594 | 595 | ### 使用 api 函数 596 | 597 | Neovim 提供了一组 API 函数来设置选项或获取其当前值: 598 | 599 | - 全局选项: 600 | - `vim.api.nvim_set_option()` 601 | - `vim.api.nvim_get_option()` 602 | - 缓冲区选项: 603 | - `vim.api.nvim_buf_set_option()` 604 | - `vim.api.nvim_buf_get_option()` 605 | - 窗口选项: 606 | - `vim.api.nvim_win_set_option()` 607 | - `vim.api.nvim_win_get_option()` 608 | 609 | 它们接受一个字符串,其中包含要设置或者要获取的选项的名称以及要将其设置为的值。 610 | 611 | 布尔选项(如 `(no)number`) 必须设置为 `true` 或 `false`: 612 | 613 | ```lua 614 | vim.api.nvim_set_option('smarttab', false) 615 | print(vim.api.nvim_get_option('smarttab')) -- false 616 | ``` 617 | 618 | 字符串选项必须设置为字符串: 619 | 620 | ```lua 621 | vim.api.nvim_set_option('selection', 'exclusive') 622 | print(vim.api.nvim_get_option('selection')) -- 'exclusive' 623 | ``` 624 | 625 | 数字选项必须接受数字类型 626 | 627 | ```lua 628 | vim.api.nvim_set_option('updatetime', 3000) 629 | print(vim.api.nvim_get_option('updatetime')) -- 3000 630 | ``` 631 | 632 | Buffer-local 和 Window-local 选项还需要缓冲区编号或窗口编号(使用 `0` 将设置 / 获取当前缓冲区 / 窗口的选项): 633 | 634 | ```lua 635 | vim.api.nvim_win_set_option(0, 'number', true) 636 | vim.api.nvim_buf_set_option(10, 'shiftwidth', 4) 637 | print(vim.api.nvim_win_get_option(0, 'number')) -- true 638 | print(vim.api.nvim_buf_get_option(10, 'shiftwidth')) -- 4 639 | ``` 640 | 641 | ### 使用元访问器 642 | 643 | 如果您想以更“惯用”的方式设置选项,可以使用一些元访问器。它们本质上包装了上述 API 函数,并允许您像处理变量一样操作选项: 644 | 645 | - [`vim.o`](https://neovim.io/doc/user/lua.html#vim.o): 行为类似于 `:let &{option-name}` 646 | - [`vim.go`](https://neovim.io/doc/user/lua.html#vim.go): 行为类似于 `:let &g:{option-name}` 647 | - [`vim.bo`](https://neovim.io/doc/user/lua.html#vim.bo): 适用于 buffer-local 选项,行为类似于 `:let &l:{option-name}` 648 | - [`vim.wo`](https://neovim.io/doc/user/lua.html#vim.wo): 适用于 window-local 选项,行为类似于 `:let &l:{option-name}` 649 | 650 | ```lua 651 | vim.o.smarttab = false -- let &smarttab = v:false 652 | print(vim.o.smarttab) -- false 653 | vim.o.isfname = vim.o.isfname .. ',@-@' -- on Linux: let &isfname = &isfname .. ',@-@' 654 | print(vim.o.isfname) -- '@,48-57,/,.,-,_,+,,,#,$,%,~,=,@-@' 655 | 656 | vim.bo.shiftwidth = 4 657 | print(vim.bo.shiftwidth) -- 4 658 | ``` 659 | 660 | 您可以为缓冲区本地和窗口本地选项指定一个数字。如果未给出编号,则使用当前缓冲区 / 窗口: 661 | 662 | ```lua 663 | vim.bo[4].expandtab = true -- same as vim.api.nvim_buf_set_option(4, 'expandtab', true) 664 | vim.wo.number = true -- same as vim.api.nvim_win_set_option(0, 'number', true) 665 | ``` 666 | 667 | 这些访问器还有更为复杂的 `vim.opt*` 变体,它们为在 Lua 中设置选项提供了更为灵活便利的机制,就像你在 `init.vim` 中使用的 `:set`/`:setglobal`/`:setlocal`: 668 | 669 | * `vim.opt`: 行为类似于 `:set` 670 | * `vim.opt_global`: 行为类似于 `:setglobal` 671 | * `vim.opt_local`: 行为类似于 `:setlocal` 672 | 673 | ```lua 674 | vim.opt.smarttab = false 675 | print(vim.opt.smarttab:get()) -- false 676 | ``` 677 | 678 | 一些选项也可以通过 Lua 的 table 来设置: 679 | 680 | ```lua 681 | vim.opt.completeopt = {'menuone', 'noselect'} 682 | print(vim.inspect(vim.opt.completeopt:get())) -- { "menuone", "noselect" } 683 | ``` 684 | 685 | 对于类似于 list/map/set 的选项,它们对应的包装器还实现了各种方法与元方法,行为类似于 Vimscript 中的 `:set+=`/`:set^=`/`:set-=`。 686 | 687 | ```lua 688 | vim.opt.shortmess:append({ I = true }) 689 | -- alternative form: 690 | vim.opt.shortmess = vim.opt.shortmess + { I = true } 691 | 692 | vim.opt.whichwrap:remove({ 'b', 's' }) 693 | -- alternative form: 694 | vim.opt.whichwrap = vim.opt.whichwrap - { 'b', 's' } 695 | ``` 696 | 697 | 关于 `vim.opt` 的更多信息请参见:[`:help vim.opt`](https://neovim.io/doc/user/lua.html#vim.opt) 698 | 699 | 更多信息请参见: 700 | 701 | - [`:help lua-vim-options`](https://neovim.io/doc/user/lua.html#lua-vim-options) 702 | 703 | ## 管理 vim 的内部变量 704 | 705 | ### 使用 api 函数 706 | 707 | 与选项非常的相似,内部变量也有自己的 api 函数: 708 | 709 | - 全局变量 (`g:`): 710 | - `vim.api.nvim_set_var()` 711 | - `vim.api.nvim_get_var()` 712 | - `vim.api.nvim_del_var()` 713 | - 缓冲区变量 (`b:`): 714 | - `vim.api.nvim_buf_set_var()` 715 | - `vim.api.nvim_buf_get_var()` 716 | - `vim.api.nvim_buf_del_var()` 717 | - 窗口变量 (`w:`): 718 | - `vim.api.nvim_win_set_var()` 719 | - `vim.api.nvim_win_get_var()` 720 | - `vim.api.nvim_win_del_var()` 721 | - 选项卡变量 (`t:`): 722 | - `vim.api.nvim_tabpage_set_var()` 723 | - `vim.api.nvim_tabpage_get_var()` 724 | - `vim.api.nvim_tabpage_del_var()` 725 | - 预定义的 vim 变量 (`v:`): 726 | - `vim.api.nvim_set_vvar()` 727 | - `vim.api.nvim_get_vvar()` 728 | 729 | 除了预定义的 Vim 变量外,还可以删除它们(等同于 Vimscript 中的 `:unlet`)。局部变量 (`l:`)、脚本变量 (`s:`) 和函数参数 (`a:`) 不能操作,因为它们只在 Vim 脚本上下文中有意义,Lua 有自己的作用域规则。 730 | 如果您不熟悉这些变量的作用,请参考 [`:help internal-variables`](https://neovim.io/doc/user/eval.html#internal-variables) 对其进行详细介绍。 731 | 这些函数接受一个字符串,该字符串包含要设置 / 获取 / 删除的变量的名称以及要将其设置为的值。 732 | 733 | ```lua 734 | vim.api.nvim_set_var('some_global_variable', { key1 = 'value', key2 = 300 }) 735 | print(vim.inspect(vim.api.nvim_get_var('some_global_variable'))) -- { key1 = "value", key2 = 300 } 736 | vim.api.nvim_del_var('some_global_variable') 737 | ``` 738 | 739 | 范围为缓冲区、窗口或选项卡的变量会接受一个数字类型的参数 (0 意味着设置 / 获取 / 删除当前缓冲区 / 窗口 / 选项卡页的变量): 740 | 741 | ```lua 742 | vim.api.nvim_win_set_var(0, 'some_window_variable', 2500) 743 | vim.api.nvim_tab_set_var(3, 'some_tabpage_variable', 'hello world') 744 | print(vim.api.nvim_win_get_var(0, 'some_window_variable')) -- 2500 745 | print(vim.api.nvim_buf_get_var(3, 'some_tabpage_variable')) -- 'hello world' 746 | vim.api.nvim_win_del_var(0, 'some_window_variable') 747 | vim.api.nvim_buf_del_var(3, 'some_tabpage_variable') 748 | ``` 749 | 750 | ### 使用元访问器 751 | 752 | 使用这些元访问器可以更直观地操作内部变量: 753 | 754 | - [`vim.g`](https://neovim.io/doc/user/lua.html#vim.g): 全局变量 755 | - [`vim.b`](https://neovim.io/doc/user/lua.html#vim.b): 缓冲区变量 756 | - [`vim.w`](https://neovim.io/doc/user/lua.html#vim.w): 窗口变量 757 | - [`vim.t`](https://neovim.io/doc/user/lua.html#vim.t): 选项卡变量 758 | - [`vim.v`](https://neovim.io/doc/user/lua.html#vim.v): 预定义变量 759 | - [`vim.env`](https://neovim.io/doc/user/lua.html#vim.env): 环境变量 760 | 761 | ```lua 762 | vim.g.some_global_variable = { 763 | key1 = 'value', 764 | key2 = 300 765 | } 766 | 767 | print(vim.inspect(vim.g.some_global_variable)) -- { key1 = "value", key2 = 300 } 768 | 769 | -- 针对特定 buffer/window/tabpage 的变量 (Neovim 0.6+) 770 | vim.b[2].myvar = 1 771 | ``` 772 | 773 | 一些变量名可能包含不能在 Lua 中用作标识符的字符。你可以使用以下语法操作这些变量:`vim.g['my#variable']`。 774 | 775 | > 在 Lua 中,`some_table.some_item` 本质上是 `some_table["some_item"]` 的语法糖,所以`vim.g['my#variable']` 也可以写为 `vim['g']['my#variable']` 776 | > 777 | > —— 译者注 778 | 779 | 删除变量只需要将它的值设置为 nil 780 | 781 | ```lua 782 | vim.g.some_global_variable = nil 783 | ``` 784 | 785 | 更多信息参见: 786 | 787 | * [`:help lua-vim-variables`](https://neovim.io/doc/user/lua.html#lua-vim-variables) 788 | 789 | #### Caveats 790 | 791 | 您不能从存储在这些变量之一的字典中添加 / 更新 / 删除键。 例如,这段 Vimscript 代码不能按预期工作: 792 | 793 | ```vim 794 | let g:variable = {} 795 | lua vim.g.variable.key = 'a' 796 | echo g:variable 797 | " {} 798 | ``` 799 | 800 | 可以使用一个临时变量来解决 801 | 802 | ```vim 803 | let g:variable = {} 804 | lua << EOF 805 | local tmp = vim.g.variable 806 | tmp.key = 'a' 807 | vim.g.variable = tmp 808 | EOF 809 | echo g:variable 810 | " {'key': 'a'} 811 | ``` 812 | 813 | 这是个已知的问题 814 | 815 | - [Issue #12544](https://github.com/neovim/neovim/issues/12544) 816 | 817 | ## 调用 Vimscript 函数 818 | 819 | ### vim.fn.{function}() 820 | 821 | `vim.fn` 可以用来调用 Vimscript 函数。数据类型在 Lua 和 Vimscript 之间自动转换。 822 | 823 | ```lua 824 | print(vim.fn.printf('Hello from %s', 'Lua')) 825 | 826 | local reversed_list = vim.fn.reverse({ 'a', 'b', 'c' }) 827 | print(vim.inspect(reversed_list)) -- { "c", "b", "a" } 828 | 829 | local function print_stdout(chan_id, data, name) 830 | print(data[1]) 831 | end 832 | 833 | vim.fn.jobstart('ls', { on_stdout = print_stdout }) 834 | ``` 835 | 836 | Hashes `#` 不是 Lua 中识别符的有效字符,因此必须使用以下语法调用 autoload 函数: 837 | 838 | ```lua 839 | vim.fn['my#autoload#function']() 840 | ``` 841 | 842 | `vim.fn` 的功能与 `vim.call` 完全相同,但看起来更像是原生 Lua 函数调用。 843 | 844 | 和 `vim.api.nvim_call_function` 的不同之处在于,`vim.fn` 中数据类型的转换是自动的:对于浮点数类型,`vim.api.nvim_call_function` 会返回一个 table 并且它不支持 Lua 闭包作为参数;`vim.fn` 可以直接处理这些类型。 845 | 846 | 更多信息请参见: 847 | 848 | - [`:help vim.fn`](https://neovim.io/doc/user/lua.html#vim.fn) 849 | 850 | #### Tips 851 | 852 | Neovim 有一个强大的内置函数库,这些函数对插件非常有用。按字母顺序排列的函数列表参见 [`:help vim-function`](https://neovim.io/doc/user/eval.html#vim-function),按主题分组的函数列表参见[`:help function-list`](https://neovim.io/doc/user/usr_41.html#function-list)。 853 | 854 | Neovim 中的 API 函数可以通过 `vim.api.{..}` 的方式直接调用。更多信息请参见 [`:help api`](https://neovim.io/doc/user/api.html#API) 855 | 856 | #### Caveats 857 | 858 | 一些应该返回布尔值的 Vim 函数返回 `1` 或 `0`。这在 Vimscript 中不是问题,因为 `1` 是真的,而 `0` 是假的,支持如下结构: 859 | 860 | ```vim 861 | if has('nvim') 862 | " do something... 863 | endif 864 | ``` 865 | 866 | 然而,在 Lua 中,只有 `false` 和 `nil` 被认为是假的,数字的计算结果总是 `true`,无论它们的值是什么。您必须显式检查 `1` 或 `0`: 867 | 868 | ```lua 869 | if vim.fn.has('nvim') == 1 then 870 | -- do something... 871 | end 872 | ``` 873 | 874 | ## 定义映射 875 | 876 | ### API 函数 877 | 878 | Neovim 提供了一系列的 api 函数来设置获取和删除映射: 879 | 880 | - 全局映射: 881 | - `vim.api.nvim_set_keymap()` 882 | - `vim.api.nvim_get_keymap()` 883 | - `vim.api.nvim_del_keymap()` 884 | - 缓冲区映射: 885 | - `vim.api.nvim_buf_set_keymap()` 886 | - `vim.api.nvim_buf_get_keymap()` 887 | - `vim.api.nvim_buf_del_keymap()` 888 | 889 | 让我们从 `vim.api.nvim_set_keymap()` 和 `vim.api.nvim_buf_set_keymap()` 开始,传递给函数的第一个参数是一个包含映射生效模式名称的字符串: 890 | 891 | | String value | Help page | Affected modes | Vimscript equivalent | 892 | |------------------------|---------------|------------------------------------------|----------------------| 893 | | `''` (an empty string) | `mapmode-nvo` | Normal, Visual, Select, Operator-pending | `:map` | 894 | | `'n'` | `mapmode-n` | Normal | `:nmap` | 895 | | `'v'` | `mapmode-v` | Visual and Select | `:vmap` | 896 | | `'s'` | `mapmode-s` | Select | `:smap` | 897 | | `'x'` | `mapmode-x` | Visual | `:xmap` | 898 | | `'o'` | `mapmode-o` | Operator-pending | `:omap` | 899 | | `'!'` | `mapmode-ic` | Insert and Command-line | `:map!` | 900 | | `'i'` | `mapmode-i` | Insert | `:imap` | 901 | | `'l'` | `mapmode-l` | Insert, Command-line, Lang-Arg | `:lmap` | 902 | | `'c'` | `mapmode-c` | Command-line | `:cmap` | 903 | | `'t'` | `mapmode-t` | Terminal | `:tmap` | 904 | 905 | 第二个参数是包含映射左侧的字符串(触发映射中定义的命令的键或键集)。空字符串相当于 ``,表示禁用键位。 906 | 907 | 第三个参数是包含映射右侧(要执行的命令)的字符串。 908 | 909 | 最后一个参数是一个表,包含 [`:help :map-arguments`](https://neovim.io/doc/user/map.html#:map-arguments) 中定义的映射的布尔值选项(包括 `noremap`,不包括 `buffer`)。 910 | 911 | 缓冲区-本地映射也将缓冲区编号作为其第一个参数(`0` 设置当前缓冲区的映射)。 912 | 913 | ```lua 914 | vim.api.nvim_set_keymap('n', '', ':set hlsearch!', { noremap = true, silent = true }) 915 | -- :nnoremap :set hlsearch 916 | 917 | vim.api.nvim_buf_set_keymap(0, '', 'cc', 'line(".") == 1 ? "cc" : "ggcc"', { noremap = true, expr = true }) 918 | -- :noremap cc line('.') == 1 ? 'cc' : 'ggcc' 919 | 920 | vim.api.nvim_set_keymap('n', 'ex', '', { 921 | noremap = true, 922 | callback = function() 923 | print('My example') 924 | end, 925 | -- Since Lua function don't have a useful string representation, you can use the "desc" option to document your mapping 926 | desc = 'Prints "My example" in the message area', 927 | }) 928 | ``` 929 | 930 | `vim.api.nvim_get_keymap()` 接受一个字符串,该字符串包含您想要映射列表的模式的短名称(见上表)。返回值是包含该模式的所有全局映射的表。 931 | 932 | ```lua 933 | print(vim.inspect(vim.api.nvim_get_keymap('n'))) 934 | -- :verbose nmap 935 | ``` 936 | 937 | `vim.api.nvim_buf_get_keymap()` 将缓冲区编号作为其第一个参数 (`0` 将获取当前缓冲区的映射) 938 | 939 | ```lua 940 | print(vim.inspect(vim.api.nvim_buf_get_keymap(0, 'i'))) 941 | -- :verbose imap 942 | ``` 943 | 944 | `vim.api.nvim_del_keymap()` 获取映射左侧的模式。 945 | 946 | ```lua 947 | vim.api.nvim_del_keymap('n', '') 948 | -- :nunmap 949 | ``` 950 | 951 | 同样,`vim.api.nvim_buf_del_keymap()` 以缓冲区编号作为第一个参数,其中 `0` 表示当前缓冲区。 952 | 953 | ```lua 954 | vim.api.nvim_buf_del_keymap(0, 'i', '') 955 | -- :iunmap 956 | ``` 957 | 958 | ### vim.keymap 959 | 960 | :warning: 本节讨论的函数仅适用于 Neovim 0.7.0+ 961 | 962 | Neovim 提供了两个函数来设置 / 删除映射: 963 | 964 | * [`vim.keymap.set()`](https://neovim.io/doc/user/lua.html#vim.keymap.set()) 965 | * [`vim.keymap.del()`](https://neovim.io/doc/user/lua.html#vim.keymap.del()) 966 | 967 | 它们是上述 API 函数的语法糖版本。 968 | 969 | `vim.keymap.set()` 第一个参数是字符串,代表映射生效的模式,也可以是一个字符串 table,这样可以一次性定义多个模式下的映射: 970 | 971 | ```lua 972 | vim.keymap.set('n', 'ex1', 'lua vim.notify("Example 1")') 973 | vim.keymap.set({'n', 'c'}, 'ex2', 'lua vim.notify("Example 2")') 974 | ``` 975 | 976 | 第二个参数是映射的左侧,是字符串类型 977 | 978 | 第三个参数是映射的右侧,既可以是字符串也可以是一个 Lua 函数: 979 | 980 | ```lua 981 | vim.keymap.set('n', 'ex1', 'echomsg "Example 1"') 982 | vim.keymap.set('n', 'ex2', function() print("Example 2") end) 983 | vim.keymap.set('n', 'pl1', require('plugin').plugin_action) 984 | -- To avoid the startup cost of requiring the module, you can wrap it in a function to require it lazily when invoking the mapping: 985 | vim.keymap.set('n', 'pl2', function() require('plugin').plugin_action() end) 986 | ``` 987 | 988 | 第四个参数是是一个选项的 table,它是可选的,对应于传递给 `vim.apt.nvim_set_keymap()` 的选项,并添加了一些内容(完整列表请参见 [`:help vim.keymap.set()`](https://neovim.io/doc/user/lua.html#vim.keymap.set()))。 989 | 990 | ```lua 991 | vim.keymap.set('n', 'ex1', 'echomsg "Example 1"', {buffer = true}) 992 | vim.keymap.set('n', 'ex2', function() print('Example 2') end, {desc = 'Prints "Example 2" to the message area'}) 993 | ``` 994 | 995 | 使用 Lua 函数定义映射不同于使用字符串。 像 `:nmap ex1` 之类的显示映射信息的常用方法不会输出有用的信息(映射到的字符串本身),而只会显示 Lua 函数。 建议添加一个 `desc` 项来描述你的按键映射的行为。这对于记录插件映射尤其重要,用户可以更轻松地了解按键映射的用法。 996 | 997 | 这个 API 的有用之处在于它消除了 Vim 映射的历史遗留问题: 998 | 999 | * 映射默认是 `noremap` 的,除非 `rhs` 是一个 `` 映射。这意味着你很少需要考虑映射是否应该是递归的: 1000 | 1001 | ```lua 1002 | vim.keymap.set('n', 'test1', 'echo "test"') 1003 | -- :nnoremap test echo "test" 1004 | 1005 | -- 如果你确定要设置一个递归的映射,把 "remap" 选项设置为 "true" 1006 | vim.keymap.set('n', '>', ']', {remap = true}) 1007 | -- :nmap > ] 1008 | 1009 | -- 除非是递归映射,否则 映射不会起作用,vim.keymap.set() 会自动为你处理 1010 | vim.keymap.set('n', 'plug', '(plugin)') 1011 | -- :nmap plug (plugin) 1012 | ``` 1013 | 1014 | * 在 `expr` 映射中,`nvim_replace_termcodes()` 会被自动应用于 Lua 函数返回的字符串 1015 | 1016 | ```lua 1017 | vim.keymap.set('i', '', function() 1018 | return vim.fn.pumvisible == 1 and '' or '' 1019 | end, {expr = true}) 1020 | ``` 1021 | 1022 | 更多信息请参见: 1023 | 1024 | * [`:help recursive_mapping`](https://neovim.io/doc/user/map.html#recursive_mapping) 1025 | 1026 | `vim.keymap.del()` 工作原理相同,但是效果是删除映射: 1027 | 1028 | ```lua 1029 | vim.keymap.del('n', 'ex1') 1030 | vim.keymap.del({'n', 'c'}, 'ex2', {buffer = true}) 1031 | ``` 1032 | 1033 | ## 定义用户命令 1034 | 1035 | :warning: 本节讨论的 API 函数仅适用于 Neovim 0.7.0+ 1036 | 1037 | Neovim 提供了如下 API 函数来创建用户命令 1038 | 1039 | * 全局用户命令 1040 | * [`vim.api.nvim_create_user_command()`](https://neovim.io/doc/user/api.html#nvim_create_user_command()) 1041 | * [`vim.api.nvim_del_user_command()`](https://neovim.io/doc/user/api.html#nvim_del_user_command()) 1042 | * Buffer-local 的用户命令 1043 | * [`vim.api.nvim_buf_create_user_command()`](https://neovim.io/doc/user/api.html#nvim_buf_create_user_command()) 1044 | * [`vim.api.nvim_buf_del_user_command()`](https://neovim.io/doc/user/api.html#nvim_buf_del_user_command()) 1045 | 1046 | 以 `vim.api.nvim_create_user_command()` 为例说明用法 1047 | 1048 | 此函数的第一个参数是命令的名字(必须以大写字母开头)。 1049 | 1050 | 第二个参数是调用该命令时要执行的代码。它可以是: 1051 | 1052 | 一个字符串(在这种情况下它将作为 Vimscript 执行)。你可以像使用 `:command` 一样使用转义序列,譬如 ``、`` 等 1053 | 1054 | ```lua 1055 | vim.api.nvim_create_user_command('Upper', 'echo toupper()', { nargs = 1 }) 1056 | -- :command! -nargs=1 Upper echo toupper() 1057 | 1058 | vim.cmd('Upper hello world') -- prints "HELLO WORLD" 1059 | ``` 1060 | 1061 | 或者是一个 Lua 函数。它接受一个类似字典的 table,其中包含通常是由转义序列提供的数据(可用的所有键请参见 [`:help nvim_create_user_command()`](https://neovim.io/doc/user/api.html#nvim_create_user_command())) 1062 | 1063 | ```lua 1064 | vim.api.nvim_create_user_command( 1065 | 'Upper', 1066 | function(opts) 1067 | print(string.upper(opts.args)) 1068 | end, 1069 | { nargs = 1 } 1070 | ) 1071 | ``` 1072 | 1073 | 第三个参数是一个包含命令属性的 table(请参见 [`:help command-attributes`](https://neovim.io/doc/user/map.html#command-attributes))。值得注意的是,由于你可以使用 `vim.api.nvim_buf_create_user_command()` 来创建 buffer-local 的用户命令,所以 `-buffer` 不是一个有效的属性。 1074 | 1075 | 此外还有两种属性可用: 1076 | 1077 | * `desc` 属性是你在定义为 Lua 回调函数的命令上运行 `:command {cmd}` 时显示的内容。与上文中的键盘映射类似,我们建议在定义为 Lua 函数的命令中加入此属性。 1078 | * `force` 属性相当于调用 `:command!` 并且替换已经存在的同名命令。与 Vimscript 不同,它的默认值为 `true` 1079 | 1080 | 除了 [`:help :command-complete`](https://neovim.io/doc/user/map.html#:command-complete) 中列出的属性之外,`-complete` 还可以设置为一个 Lua 函数。 1081 | 1082 | ```lua 1083 | vim.api.nvim_create_user_command('Upper', function() end, { 1084 | nargs = 1, 1085 | complete = function(ArgLead, CmdLine, CursorPos) 1086 | -- return completion candidates as a list-like table 1087 | return { 'foo', 'bar', 'baz' } 1088 | end, 1089 | }) 1090 | ``` 1091 | 1092 | Buffer-local 的用户命令把 buffer 编号作为第一个参数。这比 `-buffer` 更优,后者只能为当前的 buffer 定义命令。 1093 | 1094 | ```lua 1095 | vim.api.nvim_buf_create_user_command(4, 'Upper', function() end, {}) 1096 | ``` 1097 | 1098 | `vim.api.nvim_del_user_command()` 的参数是要删除命令的名字。 1099 | 1100 | ```lua 1101 | vim.api.nvim_del_user_command('Upper') 1102 | -- :delcommand Upper 1103 | ``` 1104 | 1105 | 同样的,`vim.api.nvim_buf_del_user_command()` 也把 buffer 编号作为第一个参数,`0` 代表当前的 buffer。 1106 | 1107 | ```lua 1108 | vim.api.nvim_buf_del_user_command(4, 'Upper') 1109 | ``` 1110 | 1111 | 更多信息请参见: 1112 | 1113 | * [`:help nvim_create_user_command`](https://neovim.io/doc/user/api.html#nvim_create_user_command()) 1114 | * [`:help 40.2`](https://neovim.io/doc/user/usr_40.html#40.2) 1115 | * [`:help command-attributes`](https://neovim.io/doc/user/map.html#command-attributes) 1116 | 1117 | ### Caveats 1118 | 1119 | `-complete=custom` 属性自动选出合适的候选补全并且支持内置的通配符([`:help wildcard`](https://neovim.io/doc/user/editing.html#wildcard)) 1120 | 1121 | ```vim 1122 | function! s:completion_function(ArgLead, CmdLine, CursorPos) abort 1123 | return join([ 1124 | \ 'strawberry', 1125 | \ 'star', 1126 | \ 'stellar', 1127 | \ ], "\n") 1128 | endfunction 1129 | 1130 | command! -nargs=1 -complete=custom,s:completion_function Test echo 1131 | " Typing `:Test st[ae]` returns "star" and "stellar" 1132 | ``` 1133 | 1134 | 把 `complete` 设置为 Lua 函数的话,它的行为会类似于 `customlist` ,Neovim 不会筛选函数返回的候选列表 1135 | 1136 | ```lua 1137 | vim.api.nvim_create_user_command('Test', function() end, { 1138 | nargs = 1, 1139 | complete = function(ArgLead, CmdLine, CursorPos) 1140 | return { 1141 | 'strawberry', 1142 | 'star', 1143 | 'stellar', 1144 | } 1145 | end, 1146 | }) 1147 | 1148 | -- Typing `:Test z` returns all the completion results because the list was not filtered 1149 | ``` 1150 | 1151 | ## 定义自动命令 1152 | 1153 | (本节内容尚未完成) 1154 | 1155 | Neovim 0.7.0 为创建自动命令提供了相应的 API 函数。更多细节请参见 `:help api-autocmd` 1156 | 1157 | * [Pull request #14661](https://github.com/neovim/neovim/pull/14661) (lua: autocmds take 2) 1158 | 1159 | ## 定义语法高亮 1160 | 1161 | (本节内容尚未完成) 1162 | 1163 | Neovim 0.7.0 为处理高亮组提供了相应的 API 函数。更多信息请参见: 1164 | 1165 | * [`:help nvim_set_hl()`](https://neovim.io/doc/user/api.html#nvim_set_hl()) 1166 | * [`:help nvim_get_hl_by_id()`](https://neovim.io/doc/user/api.html#nvim_get_hl_by_id()) 1167 | * [`:help nvim_get_hl_by_name()`](https://neovim.io/doc/user/api.html#nvim_get_hl_by_name()) 1168 | 1169 | ## General tips and recommendations 1170 | 1171 | ### 重新加载缓存的模块 1172 | 1173 | 在 Lua 中,`require()` 函数会缓存已加载的模块,这对提升性能是非常有用的。但是它会对插件的工作造成影响,因为已加载的模块不会在后续的 `require()` 调用中更新。 1174 | 1175 | 如果你想刷新某个特定模块的缓存,那么你必须去修改全局的 `package.loaded` : 1176 | 1177 | ```lua 1178 | package.loaded['modname'] = nil 1179 | require('modname') -- loads an updated version of module 'modname' 1180 | ``` 1181 | 1182 | [nvim-lua/plenary.nvim](https://github.com/nvim-lua/plenary.nvim) 插件提供了实现此功能的[自定义函数](https://github.com/nvim-lua/plenary.nvim/blob/master/lua/plenary/reload.lua) 1183 | 1184 | ### 不要填充 Lua 字符串! 1185 | 1186 | 当使用双重中括号的字符串时,尽量不要填充多余的字符(如空格、制表符等)!当字符没有特殊意义的时候这样做无可非议;但是当字符具有特殊意义时,这样做可能会导致一些难以发现的问题 1187 | 1188 | ```lua 1189 | vim.api.nvim_set_keymap('n', 'f', [[ call foo() ]], {noremap = true}) 1190 | ``` 1191 | 1192 | 在上面的例子中,`f` 被映射到了 `call foo()` ,而不是我们期望的 `call foo()`。 1193 | 1194 | ### 关于 Vimscript <-> Lua 之间类型转换的注意事项 1195 | 1196 | #### 变量转换时会创建一个副本 1197 | 1198 | 这意味着你对对象(从 Lua 转换到 Vimscript 的对象或者反过来)的引用进行修改不会影响到原对象。 1199 | 1200 | 例如,Vimscript 中的 `map()` 函数就地修改了一个变量: 1201 | 1202 | ```vim 1203 | let s:list = [1, 2, 3] 1204 | let s:newlist = map(s:list, {_, v -> v * 2}) 1205 | 1206 | echo s:list 1207 | " [2, 4, 6] 1208 | echo s:newlist 1209 | " [2, 4, 6] 1210 | echo s:list is# s:newlist 1211 | " 1 1212 | ``` 1213 | 1214 | 在 Lua 中调用这个函数,它改变的将会是参数的一个副本: 1215 | 1216 | ```lua 1217 | local tbl = {1, 2, 3} 1218 | local newtbl = vim.fn.map(tbl, function(_, v) return v * 2 end) 1219 | 1220 | print(vim.inspect(tbl)) -- { 1, 2, 3 } 1221 | print(vim.inspect(newtbl)) -- { 2, 4, 6 } 1222 | print(tbl == newtbl) -- false 1223 | ``` 1224 | 1225 | #### 并不是总能进行类型转换 1226 | 1227 | 这主要影响函数和 table: 1228 | 1229 | 混合列表和字典的 Lua table 是无法转换的: 1230 | 1231 | ```lua 1232 | print(vim.fn.count({1, 1, number = 1}, 1)) 1233 | -- E5100: Cannot convert given lua table: table should either have a sequence of positive integer keys or contain only string keys 1234 | ``` 1235 | 1236 | 尽管你可以在 Lua 中通过 `vim.fn` 调用 VIm 函数,但是你不能保存对它们的引用。这可能会导致一些意外的行为: 1237 | 1238 | ```lua 1239 | local FugitiveHead = vim.fn.funcref('FugitiveHead') 1240 | print(FugitiveHead) -- vim.NIL 1241 | 1242 | vim.cmd("let g:test_dict = {'test_lambda': {-> 1}}") 1243 | print(vim.g.test_dict.test_lambda) -- nil 1244 | print(vim.inspect(vim.g.test_dict)) -- {} 1245 | ``` 1246 | 1247 | 把 Lua 函数作为参数传给 Vim 函数是可行的,但是不能把它们存在 Vim 变量中(Neovim 0.7.0+ 中修复了这个问题): 1248 | 1249 | ```lua 1250 | -- This works: 1251 | vim.fn.jobstart({'ls'}, { 1252 | on_stdout = function(chan_id, data, name) 1253 | print(vim.inspect(data)) 1254 | end 1255 | }) 1256 | 1257 | -- This doesn't: 1258 | vim.g.test_dict = {test_lambda = function() return 1 end} -- Error: Cannot convert given lua type 1259 | ``` 1260 | 1261 | 值得注意的是,在 Vimscript 中使用 `luaeval()` 执行相同操作却是可行的: 1262 | 1263 | ```vim 1264 | let g:test_dict = {'test_lambda': luaeval('function() return 1 end')} 1265 | echo g:test_dict 1266 | " {'test_lambda': function('4714')} 1267 | ``` 1268 | 1269 | #### Vim booleans 1270 | 1271 | 在 Vim 脚本中,一种常见的情况是使用 `1` 或 `0` 来代表布尔值。事实上,直到版本 7.4.1154,Vim 才有单独的布尔类型。 1272 | 1273 | 在 Vimscript 中,Lua 的布尔值会被转换为真正的布尔值,而不是数字: 1274 | 1275 | ```vim 1276 | lua vim.g.lua_true = true 1277 | echo g:lua_true 1278 | " v:true 1279 | lua vim.g.lua_false = false 1280 | echo g:lua_false 1281 | " v:false 1282 | ``` 1283 | 1284 | ### 设置 linters/language servers 1285 | 1286 | 如果你使用 liner 和 / 或 language server 来获得 Lua 项目的自动补全和错误检查,你可能需要为它们配置 Neovim 特定的设置。以下是一些流行工具的推荐配置: 1287 | 1288 | #### luacheck 1289 | 1290 | 你可以通过把此配置放入 `~/.luacheckrc` (或 `$XDG_CONFIG_HOME/luacheck/.luacheckrc`)中来让 [luacheck](https://github.com/mpeterv/luacheck/) 识别全局的 `vim` 变量: 1291 | 1292 | ```lua 1293 | globals = { 1294 | "vim", 1295 | } 1296 | ``` 1297 | 1298 | [Alloyed/lua-lsp ](https://github.com/Alloyed/lua-lsp/) 使用 `luacheck` 提供 linting 并读取相同的文件。 1299 | 1300 | 有关如何配置 `luacheck` 的更多信息,请参见它的[文档](https://luacheck.readthedocs.io/en/stable/config.html) 1301 | 1302 | #### sumneko/lua-language-server 1303 | 1304 | [nvim-lspconfig](https://github.com/neovim/nvim-lspconfig/) 仓库中包含了如何配置 sumneko/lua-language-server 的[说明](https://github.com/neovim/nvim-lspconfig/blob/master/doc/server_configurations.md#sumneko_lua)(示例使用内置 LSP 客户端,但其他 LSP 客户端实现的配置应该相同)。 1305 | 1306 | 有关如何配置 [sumneko/lua-language-server](https://github.com/sumneko/lua-language-server/) 的更多信息,请参阅 ["Setting"](https://github.com/sumneko/lua-language-server/wiki/Setting) 1307 | 1308 | #### coc.nvim 1309 | 1310 | [coc.nvim](https://github.com/neoclide/coc.nvim/) 的 [rafcamlet/coc-nvim-lua](https://github.com/rafcamlet/coc-nvim-lua/) 补全源为 Neovim 标准库提供了补全项。 1311 | 1312 | ### 调试 Lua 代码 1313 | 1314 | 你可以使用 [jbyuki/one-small-step-for-vimkind](https://github.com/jbyuki/one-small-step-for-vimkind) 调试在单独的 Neovim 实例中运行的 Lua 代码 1315 | 1316 | 该插件使用 [Debug Adapter Protocol](https://microsoft.github.io/debug-adapter-protocol/)。连接到一个 debug adapter 需要 DAP 客户端,例如 [mfussenegger/nvim-dap](https://github.com/mfussenegger/nvim-dap/) 或 [puremourning/vimspector](https://github.com/puremourning/vimspector/)。 1317 | 1318 | ### 调试 Lua 设置的按键映射 / 用户命令 / 自动命令 1319 | 1320 | `:verbose` 命令允许你查看 按键映射 / 用户命令 / 自动命令的定义位置: 1321 | 1322 | ```vim 1323 | :verbose map m 1324 | ``` 1325 | 1326 | ```vim 1327 | n m_ * echo 'example' 1328 | Last set from ~/.config/nvim/init.vim line 26 1329 | ``` 1330 | 1331 | 默认情况下,出于性能原因,此功能在 Lua 中是禁用的。你可以通过使用大于 0 的 verbose level 启动 Neovim 来启用它: 1332 | 1333 | ```shell 1334 | nvim -V1 1335 | ``` 1336 | 1337 | 更多信息请参见: 1338 | 1339 | * [`:help 'verbose'`](https://neovim.io/doc/user/options.html#'verbose') 1340 | * [`:help -V`](https://neovim.io/doc/user/starting.html#-V) 1341 | * [neovim/neovim#15079](https://github.com/neovim/neovim/pull/15079) 1342 | 1343 | ### 测试 Lua 代码 1344 | 1345 | * [plenary.nvim: test harness](https://github.com/nvim-lua/plenary.nvim/#plenarytest_harness) 1346 | * [notomo/vusted](https://github.com/notomo/vusted) 1347 | 1348 | ### 使用 Luarocks 包 1349 | 1350 | [wbthomason/packer.nvim](https://github.com/wbthomason/packer.nvim) 支持 Luarocks 包。它的 [README](https://github.com/wbthomason/packer.nvim/#luarocks-support) 中提供了有关如何设置的说明 1351 | 1352 | ## Miscellaneous 1353 | 1354 | ### vim.loop 1355 | 1356 | `vim.loop` 是暴露 LibUV 接口的模块。一些相关资源: 1357 | 1358 | - [Official documentation for LibUV](https://docs.libuv.org/en/v1.x/) 1359 | - [Luv documentation](https://github.com/luvit/luv/blob/master/docs.md) 1360 | - [teukka.tech - Using LibUV in Neovim](https://teukka.tech/vimloop.html) 1361 | 1362 | 更多信息请参见: 1363 | - [`:help vim.loop`](https://neovim.io/doc/user/lua.html#vim.loop) 1364 | 1365 | ### vim.lsp 1366 | 1367 | `vim.lsp` 是内置的 lsp 库。官方的 lsp 配置插件 [neovim/nvim-lspconfig](https://github.com/neovim/nvim-lspconfig/) 包含一些流行的 language server 的默认配置。 1368 | 1369 | 客户端的行为可以使用 "sp-handlers" 进行配置。更多信息请参见: 1370 | 1371 | - [`:help lsp-handler`](https://neovim.io/doc/user/lsp.html#lsp-handler) 1372 | - [neovim/neovim#12655](https://github.com/neovim/neovim/pull/12655) 1373 | - [how to migrate from diagnostic-nvim](https://github.com/nvim-lua/diagnostic-nvim/issues/73#issue-737897078) 1374 | 1375 | 你可能还想了解一下一些基于 LSP 客户端构建的[插件](https://github.com/rockerBOO/awesome-neovim#lsp) 1376 | 1377 | 更多信息请参见: 1378 | 1379 | - [`:help lsp`](https://neovim.io/doc/user/lsp.html#LSP) 1380 | 1381 | ### vim.treesitter 1382 | 1383 | `vim.treesitter` 是 [Tree-sitter](https://tree-sitter.github.io/tree-sitter/) 的 neovim 集成,如果你想了解更多可以参考 [presentation (38:37)](https://www.youtube.com/watch?v=Jes3bD6P0To). 1384 | 1385 | The [nvim-treesitter](https://github.com/nvim-treesitter/) organisation hosts various plugins taking advantage of the library. 1386 | 1387 | > [glepnir](https://github.com/glepnir) 制作的主题 [zephyr-nvim](https://github.com/glepnir/zephyr-nvim) 语法高亮基于 nvim-treesitter 1388 | > 1389 | > —— 译者注 1390 | 1391 | See also: 1392 | - [`:help lua-treesitter`](https://neovim.io/doc/user/treesitter.html#lua-treesitter) 1393 | 1394 | ### Transpilers 1395 | 1396 | 使用 Lua 的一个优点是您实际上不必编写 Lua 代码!有许多其他语言可以转译到 Lua。 1397 | 1398 | - [Moonscript](https://moonscript.org/) 1399 | 1400 | 可能是 Lua 最著名的转译器之一。添加了许多方便的功能,如类、列表推导或函数字面量。 [svermeulen/nvim-moonmaker](https://github.com/svermeulen/nvim-moonmaker) 插件允许您直接在 Moonscript 中编写 Neovim 插件和配置。 1401 | 1402 | - [Fennel](https://fennel-lang.org/) 1403 | 1404 | 可以编译为 Lua 的 lisp 方言。你可以使用 [Olical/aniseed](https://github.com/Olical/aniseed) 或 [Hotpot](https://github.com/rktjmp/hotpot.nvim) 插件在 Fennel 中为 Neovim 编写配置和插件。此外,[Olical/conjure](https://github.com/Olical/conjure) 插件提供了一个支持 Fennel(以及其他语言)的交互式开发环境。 1405 | 1406 | * [Teal](https://github.com/teal-language/tl) 1407 | 1408 | Teal 这个名字来自 TL(typed lua)的发音。这代表了它的目标——向 lua 添加强类型,同时语法尽量保持接近标准 lua 语法。 [nvim-teal-maker](https://github.com/svermeulen/nvim-teal-maker) 插件可用于直接在 Teal 中编写 Neovim 插件或配置文件 1409 | 1410 | 其他一些有趣的项目: 1411 | 1412 | - [TypeScriptToLua/TypeScriptToLua](https://github.com/TypeScriptToLua/TypeScriptToLua) 1413 | - [teal-language/tl](https://github.com/teal-language/tl) 1414 | - [Haxe](https://haxe.org/) 1415 | - [SwadicalRag/wasm2lua](https://github.com/SwadicalRag/wasm2lua) 1416 | - [hengestone/lua-languages](https://github.com/hengestone/lua-languages) 1417 | --------------------------------------------------------------------------------