lua-plugin.txt (14069B)
1 *lua-plugin.txt* Nvim 2 3 NVIM REFERENCE MANUAL 4 5 Guide to developing Lua plugins for Nvim 6 7 8 Type |gO| to see the table of contents. 9 10 ============================================================================== 11 Introduction *lua-plugin* 12 13 This document provides guidance for developing Nvim Lua plugins. 14 15 See |lua-guide| for guidance on using Lua to configure and operate Nvim. 16 See |luaref| and |lua-concepts| for details on the Lua programming language. 17 18 ============================================================================== 19 Creating your first plugin *lua-plugin-new* 20 21 Any Vimscript or Lua code file that lives in the right directory, 22 automatically is a "plugin". There's no manifest or "registration" step. 23 24 You can try it right now: 25 26 1. Visit your config directory: > 27 :exe 'edit' stdpath('config') 28 2. Create a `plugin/foo.lua` file in there. 29 3. Add something to it, like: >lua 30 vim.print('Hello World') 31 4. Start `nvim` and notice that it prints "Hello World" in the messages area. 32 Check `:messages` if you don't see it. 33 34 Besides `plugin/foo.lua`, which is always run at startup, you can define Lua 35 modules in the `lua/` directory. Those modules aren't loaded until your 36 `plugin/foo.lua`, or the user, calls `require(…)`. 37 38 ============================================================================== 39 Type safety *lua-plugin-type-safety* 40 41 Lua, as a dynamically typed language, is great for configuration. It provides 42 virtually immediate feedback. 43 But for larger projects, this can be a double-edged sword, leaving your plugin 44 susceptible to unexpected bugs at the wrong time. 45 46 You can leverage LuaCATS or "emmylua" annotations https://luals.github.io/wiki/annotations/ 47 along with lua-language-server ("LuaLS") https://luals.github.io/ to catch 48 potential bugs in your CI before your plugin's users do. The Nvim codebase 49 uses these annotations extensively. 50 51 TOOLS 52 53 - lua-typecheck-action https://github.com/marketplace/actions/lua-typecheck-action 54 - lua-language-server https://luals.github.io 55 56 ============================================================================== 57 Keymaps *lua-plugin-keymaps* 58 59 Avoid creating excessive keymaps automatically. Doing so can conflict with 60 user |mapping|s. 61 62 NOTE: An example for uncontroversial keymaps are buffer-local |mapping|s for 63 specific file types or floating windows, or <Plug> mappings. 64 65 A common approach to allow keymap configuration is to define a declarative DSL 66 https://en.wikipedia.org/wiki/Domain-specific_language via a `setup` function. 67 68 However, doing so means that 69 70 - You will have to implement and document it yourself. 71 - Users will likely face inconsistencies if another plugin has a slightly 72 different DSL. 73 - |init.lua| scripts that call such a `setup` function may throw an error if 74 the plugin is not installed or disabled. 75 76 As an alternative, you can provide |<Plug>| mappings to allow users to define 77 their own keymaps with |vim.keymap.set()|. 78 79 - This requires one line of code in user configs. 80 - Even if your plugin is not installed or disabled, creating the keymap won't 81 throw an error. 82 83 Another option is to simply expose a Lua function or |user-commands|. 84 85 Some benefits of |<Plug>| mappings are that you can 86 87 - Enforce options like `expr = true`. 88 - Use |vim.keymap|'s built-in mode handling to expose functionality only for 89 specific |map-modes|. 90 - Handle different |map-modes| differently with a single mapping, without 91 adding mode checks to the underlying implementation. 92 - Detect user-defined mappings through |hasmapto()| before creating defaults. 93 94 Some benefits of exposing a Lua function are: 95 96 - Extensibility, if the function takes an options table as an argument. 97 - A cleaner UX, if there are many options and enumerating all combinations 98 of options would result in a lot of |<Plug>| mappings. 99 100 NOTE: If your function takes an options table, users may still benefit 101 from |<Plug>| mappings for the most common combinations. 102 103 KEYMAP EXAMPLE 104 105 In your plugin: 106 >lua 107 vim.keymap.set('n', '<Plug>(SayHello)', function() 108 print('Hello from normal mode') 109 end) 110 111 vim.keymap.set('v', '<Plug>(SayHello)', function() 112 print('Hello from visual mode') 113 end) 114 < 115 In the user's config: 116 >lua 117 vim.keymap.set({'n', 'v'}, '<leader>h', '<Plug>(SayHello)') 118 < 119 ============================================================================== 120 Initialization *lua-plugin-init* 121 122 Strictly separated configuration and smart initialization allow your plugin to 123 work out of the box. Common approaches are: 124 125 - A Lua function, e.g. `setup(opts)` or `configure(opts)`, which only overrides the 126 default configuration and does not contain any initialization logic. 127 - A Vimscript compatible table (e.g. in the |vim.g| or |vim.b| namespace) that your 128 plugin reads from and validates at initialization time. 129 See also |lua-vim-variables|. 130 131 Typically, automatic initialization logic is done in a |plugin| or |ftplugin| 132 script. See also 'runtimepath'. 133 134 On the other hand, a single `setup(opts)` that combines configuration and 135 initialization may be useful in specific cases: 136 137 - Customizing complex initialization, where there is a significant risk of 138 misconfiguration. 139 - Requiring users to opt in for plugin functionality that should not be 140 initialized automatically. 141 142 Keep in mind that this approach requires users to call `setup` in order to 143 use your plugin, even if the default configuration is enough for them. 144 Consider carefully whether your plugin benefits from combined `setup()` pattern 145 before adopting it. This article chronicles the history and tradeoffs of 146 `setup()`: https://mrcjkb.dev/posts/2023-08-22-setup.html 147 148 NOTE: A well designed plugin has minimal impact on startup time. See also 149 |lua-plugin-lazy|. 150 151 ============================================================================== 152 Lazy loading *lua-plugin-lazy* 153 154 Some users like to micro-manage "lazy loading" of plugins by explicitly 155 configuring which commands and key mappings load the plugin. 156 157 Your plugin should not depend on every user micro-managing their configuration 158 in such a way. Nvim has a mechanism for every plugin to do its own implicit 159 lazy-loading (in Vimscript it's called |autoload|), via `autoload/` 160 (Vimscript) and `lua/` (Lua). Plugin authors can provide "lazy loading" by 161 providing a `plugin/<name>.lua` file which defines their commands and 162 keymappings. This file should be small, and should not eagerly `require()` the 163 rest of your plugin. Commands and mappings should do the `require()`. 164 165 Guidance: 166 167 - Plugins should arrange their "lazy" behavior once, instead of expecting every user to micromanage it. 168 - Keep `plugin/<name>.lua` small, avoid eagerly calling `require()` on modules 169 until a command or mapping is actually used. 170 171 ------------------------------------------------------------------------------ 172 Defer require() calls *lua-plugin-defer-require* 173 174 `plugin/<name>.lua` scripts (|plugin|) are eagerly run at startup; this is 175 intentional, so that plugins can setup the (minimal) commands and keymappings 176 that users will use to invoke the plugin. This also means these "plugin/" 177 files should NOT eagerly `require` Lua modules. 178 179 For example, instead of: 180 >lua 181 local foo = require('foo') 182 vim.api.nvim_create_user_command('MyCommand', function() 183 foo.do_something() 184 end, { 185 -- ... 186 }) 187 < 188 which calls `require('foo')` as soon as the module is loaded, you can 189 lazy-load it by moving the `require` into the command's implementation: 190 >lua 191 vim.api.nvim_create_user_command('MyCommand', function() 192 local foo = require('foo') 193 foo.do_something() 194 end, { 195 -- ... 196 }) 197 < 198 Likewise, if a plugin uses a Lua module as an entrypoint, it should 199 defer `require` calls too. 200 201 NOTE: For a Vimscript alternative to `require`, see |autoload|. 202 203 NOTE: If you are worried about eagerly creating user commands, autocommands or 204 keymaps at startup: Plugin managers that provide abstractions for lazy-loading 205 plugins on such events do the same amount of work. There is no performance 206 benefit for users to define lazy-loading entrypoints in their configuration 207 instead of plugins defining it in `plugin/<name>.lua`. 208 209 NOTE: You can use |--startuptime| to |profile| the impact a plugin has on 210 startup time. 211 212 ------------------------------------------------------------------------------ 213 Filetype-specific functionality *lua-plugin-filetype* 214 215 Consider making use of 'filetype' for any functionality that is specific to 216 a filetype, by putting the initialization logic in a `ftplugin/{filetype}.lua` 217 script. 218 219 For buffers owned by your plugin (often used to show a custom UI or view), 220 typically your plugin will set a custom 'filetype'. In that case, it's useful 221 to set the 'filetype' "as late as possible", so that users can override 222 buffer-local settings after your plugin has (re)initialized the buffer. 223 224 FILETYPE EXAMPLE 225 226 A plugin tailored to Rust development might have initialization in 227 `ftplugin/rust.lua`: 228 >lua 229 if not vim.g.loaded_my_rust_plugin then 230 -- Initialize 231 end 232 -- NOTE: Using `vim.g.loaded_` prevents the plugin from initializing twice 233 -- and allows users to prevent plugins from loading 234 -- (in both Lua and Vimscript). 235 vim.g.loaded_my_rust_plugin = true 236 237 local bufnr = vim.api.nvim_get_current_buf() 238 -- do something specific to this buffer, 239 -- e.g. add a |<Plug>| mapping or create a command 240 vim.keymap.set('n', '<Plug>(MyPluginBufferAction)', function() 241 print('Hello') 242 end, { buffer = bufnr, }) 243 < 244 ============================================================================== 245 Configuration *lua-plugin-config* 246 247 To allow users to override buffer-local configuration for filetypes owned by 248 your plugin, publish a |FileType| event, "as late as possible". 249 |lua-plugin-filetype| 250 251 Once you have merged the default configuration with the user's config, you 252 should validate configs. 253 254 Validations could include: 255 256 - Correct types, see |vim.validate()| 257 - Unknown fields in the user config (e.g. due to typos). 258 This can be tricky to implement, and may be better suited for a |health| 259 check, to reduce overhead. 260 261 ============================================================================== 262 UI *lua-plugin-ui* 263 264 Some plugins have their own "UI" which they present in a buffer that the 265 plugin "owns". In that buffer typically you will want to provide custom 266 actions. 267 268 Besides creating |<Plug>| mappings, you may want to consider providing actions 269 by defining an in-process LSP server. Offering actions as code-actions 270 |vim.lsp.buf.code_action()| means the user can see all available actions using 271 the default |gra| mapping to view the code-actions menu. They can even define 272 mappings to a specific action by invoking `vim.lsp.buf.code_action()` with the 273 `filter` + `apply` parameters: >lua 274 275 vim.lsp.buf.code_action({ 276 apply = true, 277 filter = function(a) 278 return a.title == 'Do something' 279 end, 280 }) 281 < 282 283 Example: See `runtime/lua/vim/pack/_lsp.lua` for how vim.pack defines an 284 in-process LSP server to provide interactive features in its 285 `nvim-pack://confirm` buffer. 286 287 ============================================================================== 288 Troubleshooting *lua-plugin-troubleshooting* 289 290 While developing a plugin, you can use the |:restart| command to see the 291 result of code changes in your plugin. 292 293 HEALTH 294 295 Nvim's "health" framework gives plugins a simple way to report status checks 296 to users. See |health-dev| for an example. 297 298 Basically, this just means your plugin will have a `lua/{plugin}/health.lua` 299 file. |:checkhealth| will automatically find this file when it runs. 300 301 Some things to validate: 302 303 - User configuration 304 - Proper initialization 305 - Presence of Lua dependencies (e.g. other plugins) 306 - Presence of external dependencies 307 308 MINIMAL CONFIG TEMPLATE 309 310 It can be useful to provide a template for a minimal configuration, along with 311 a guide on how to use it to reproduce issues. 312 313 ============================================================================== 314 Versioning and releases *lua-plugin-versioning* 315 316 Consider: 317 318 - Use |vim.deprecate()| or a `---@deprecate` annotation when you need to 319 communicate a (future) breaking change or discouraged practice. 320 - Using SemVer https://semver.org/ tags and releases to properly communicate 321 bug fixes, new features, and breaking changes. 322 - Automating versioning and releases in CI. 323 - Publishing to luarocks https://luarocks.org, especially if your plugin 324 has dependencies or components that need to be built; or if it could be a 325 dependency for another plugin. 326 327 FURTHER READING 328 329 - Luarocks ❤️ Nvim https://github.com/nvim-neorocks/sample-luarocks-plugin 330 331 VERSIONING TOOLS 332 333 - luarocks-tag-release 334 https://github.com/marketplace/actions/luarocks-tag-release 335 - release-please-action 336 https://github.com/marketplace/actions/release-please-action 337 - semantic-release 338 https://github.com/semantic-release/semantic-release 339 340 ============================================================================== 341 Documentation *lua-plugin-doc* 342 343 Provide vimdoc (see |help-writing|), so that users can read your plugin's 344 documentation in Nvim, by entering `:h {plugin}` in |command-mode|. The 345 help-tags (the right-aligned "search keywords" in the help documents) are 346 regenerated using the |:helptags| command. 347 348 DOCUMENTATION TOOLS 349 350 - panvimdoc https://github.com/kdheepak/panvimdoc 351 352 353 vim:tw=78:ts=8:sw=4:sts=4:et:ft=help:norl: