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