*lua-plugin.txt* Nvim NVIM REFERENCE MANUAL Guide to developing Lua plugins for Nvim Type |gO| to see the table of contents. ============================================================================== Introduction *lua-plugin* This document provides guidance for developing Nvim Lua plugins. See |lua-guide| for guidance on using Lua to configure and operate Nvim. See |luaref| and |lua-concepts| for details on the Lua programming language. ============================================================================== Creating your first plugin *lua-plugin-new* Any Vimscript or Lua code file that lives in the right directory, automatically is a "plugin". There's no manifest or "registration" step. You can try it right now: 1. Visit your config directory: > :exe 'edit' stdpath('config') < 2. Create a `plugin/foo.lua` file in there. 3. Add something to it, like: >lua vim.print('Hello World') < 4. Start `nvim` and notice that it prints "Hello World" in the messages area. Check `:messages` if you don't see it. Besides `plugin/foo.lua`, which is always run at startup, you can define Lua modules in the `lua/` directory. Those modules aren't loaded until your `plugin/foo.lua`, or the user, calls `require(…)`. ============================================================================== Type safety *lua-plugin-type-safety* Lua, as a dynamically typed language, is great for configuration. It provides virtually immediate feedback. But for larger projects, this can be a double-edged sword, leaving your plugin susceptible to unexpected bugs at the wrong time. You can leverage LuaCATS or "emmylua" annotations https://luals.github.io/wiki/annotations/ along with lua-language-server ("LuaLS") https://luals.github.io/ to catch potential bugs in your CI before your plugin's users do. The Nvim codebase uses these annotations extensively. TOOLS - lua-typecheck-action https://github.com/marketplace/actions/lua-typecheck-action - lua-language-server https://luals.github.io ============================================================================== Keymaps *lua-plugin-keymaps* Avoid creating excessive keymaps automatically. Doing so can conflict with user |mapping|s. NOTE: An example for uncontroversial keymaps are buffer-local |mapping|s for specific file types or floating windows, or mappings. A common approach to allow keymap configuration is to define a declarative DSL https://en.wikipedia.org/wiki/Domain-specific_language via a `setup` function. However, doing so means that - You will have to implement and document it yourself. - Users will likely face inconsistencies if another plugin has a slightly different DSL. - |init.lua| scripts that call such a `setup` function may throw an error if the plugin is not installed or disabled. As an alternative, you can provide || mappings to allow users to define their own keymaps with |vim.keymap.set()|. - This requires one line of code in user configs. - Even if your plugin is not installed or disabled, creating the keymap won't throw an error. Another option is to simply expose a Lua function or |user-commands|. Some benefits of || mappings are that you can - Enforce options like `expr = true`. - Use |vim.keymap|'s built-in mode handling to expose functionality only for specific |map-modes|. - Handle different |map-modes| differently with a single mapping, without adding mode checks to the underlying implementation. - Detect user-defined mappings through |hasmapto()| before creating defaults. Some benefits of exposing a Lua function are: - Extensibility, if the function takes an options table as an argument. - A cleaner UX, if there are many options and enumerating all combinations of options would result in a lot of || mappings. NOTE: If your function takes an options table, users may still benefit from || mappings for the most common combinations. KEYMAP EXAMPLE In your plugin: >lua vim.keymap.set('n', '(SayHello)', function() print('Hello from normal mode') end) vim.keymap.set('v', '(SayHello)', function() print('Hello from visual mode') end) < In the user's config: >lua vim.keymap.set({'n', 'v'}, 'h', '(SayHello)') < ============================================================================== Initialization *lua-plugin-init* Strictly separated configuration and smart initialization allow your plugin to work out of the box. Common approaches are: - A Lua function, e.g. `setup(opts)` or `configure(opts)`, which only overrides the default configuration and does not contain any initialization logic. - A Vimscript compatible table (e.g. in the |vim.g| or |vim.b| namespace) that your plugin reads from and validates at initialization time. See also |lua-vim-variables|. Typically, automatic initialization logic is done in a |plugin| or |ftplugin| script. See also |'runtimepath'|. On the other hand, a single `setup(opts)` that combines configuration and initialization may be useful in specific cases: - Customizing complex initialization, where there is a significant risk of misconfiguration. - Requiring users to opt in for plugin functionality that should not be initialized automatically. Keep in mind that this approach requires users to call `setup` in order to use your plugin, even if the default configuration is enough for them. Consider carefully whether your plugin benefits from combined `setup()` pattern before adopting it. NOTE: A well designed plugin has minimal impact on startup time. See also |lua-plugin-lazy|. ============================================================================== Lazy loading *lua-plugin-lazy* Some users like to micro-manage "lazy loading" of plugins by explicitly configuring which commands and key mappings load the plugin. Your plugin should not depend on every user micro-managing their configuration in such a way. Nvim has a mechanism for every plugin to do its own implicit lazy-loading (in Vimscript it's called |autoload|), via `autoload/` (Vimscript) and `lua/` (Lua). Plugin authors can provide "lazy loading" by providing a `plugin/.lua` file which defines their commands and keymappings. This file should be small, and should not eagerly `require()` the rest of your plugin. Commands and mappings should do the `require()`. Guidance: - Plugins should arrange their "lazy" behavior once, instead of expecting every user to micromanage it. - Keep `plugin/.lua` small, avoid eagerly calling `require()` on modules until a command or mapping is actually used. ------------------------------------------------------------------------------ Defer require() calls *lua-plugin-defer-require* `plugin/.lua` scripts (|plugin|) are eagerly run at startup; this is intentional, so that plugins can setup the (minimal) commands and keymappings that users will use to invoke the plugin. This also means these "plugin/" files should NOT eagerly `require` Lua modules. For example, instead of: >lua local foo = require('foo') vim.api.nvim_create_user_command('MyCommand', function() foo.do_something() end, { -- ... }) < which calls `require('foo')` as soon as the module is loaded, you can lazy-load it by moving the `require` into the command's implementation: >lua vim.api.nvim_create_user_command('MyCommand', function() local foo = require('foo') foo.do_something() end, { -- ... }) < Likewise, if a plugin uses a Lua module as an entrypoint, it should defer `require` calls too. NOTE: For a Vimscript alternative to `require`, see |autoload|. NOTE: If you are worried about eagerly creating user commands, autocommands or keymaps at startup: Plugin managers that provide abstractions for lazy-loading plugins on such events do the same amount of work. There is no performance benefit for users to define lazy-loading entrypoints in their configuration instead of plugins defining it in `plugin/.lua`. NOTE: You can use |--startuptime| to |profile| the impact a plugin has on startup time. ------------------------------------------------------------------------------ Filetype-specific functionality *lua-plugin-filetype* Consider making use of 'filetype' for any functionality that is specific to a filetype, by putting the initialization logic in a `ftplugin/{filetype}.lua` script. FILETYPE EXAMPLE A plugin tailored to Rust development might have initialization in `ftplugin/rust.lua`: >lua if not vim.g.loaded_my_rust_plugin then -- Initialize end -- NOTE: Using `vim.g.loaded_` prevents the plugin from initializing twice -- and allows users to prevent plugins from loading -- (in both Lua and Vimscript). vim.g.loaded_my_rust_plugin = true local bufnr = vim.api.nvim_get_current_buf() -- do something specific to this buffer, -- e.g. add a || mapping or create a command vim.keymap.set('n', '(MyPluginBufferAction)', function() print('Hello') end, { buffer = bufnr, }) < ============================================================================== Configuration *lua-plugin-config* Once you have merged the default configuration with the user's config, you should validate configs. Validations could include: - Correct types, see |vim.validate()| - Unknown fields in the user config (e.g. due to typos). This can be tricky to implement, and may be better suited for a |health| check, to reduce overhead. ============================================================================== Troubleshooting *lua-plugin-troubleshooting* While developing a plugin, you can use the |:restart| command to see the result of code changes in your plugin. HEALTH Nvim's "health" framework gives plugins a simple way to report status checks to users. See |health-dev| for an example. Basically, this just means your plugin will have a `lua/{plugin}/health.lua` file. |:checkhealth| will automatically find this file when it runs. Some things to validate: - User configuration - Proper initialization - Presence of Lua dependencies (e.g. other plugins) - Presence of external dependencies MINIMAL CONFIG TEMPLATE It can be useful to provide a template for a minimal configuration, along with a guide on how to use it to reproduce issues. ============================================================================== Versioning and releases *lua-plugin-versioning* Consider: - Use |vim.deprecate()| or a `---@deprecate` annotation when you need to communicate a (future) breaking change or discouraged practice. - Using SemVer https://semver.org/ tags and releases to properly communicate bug fixes, new features, and breaking changes. - Automating versioning and releases in CI. - Publishing to luarocks https://luarocks.org, especially if your plugin has dependencies or components that need to be built; or if it could be a dependency for another plugin. FURTHER READING - Luarocks ❤️ Nvim https://github.com/nvim-neorocks/sample-luarocks-plugin VERSIONING TOOLS - luarocks-tag-release https://github.com/marketplace/actions/luarocks-tag-release - release-please-action https://github.com/marketplace/actions/release-please-action - semantic-release https://github.com/semantic-release/semantic-release ============================================================================== Documentation *lua-plugin-doc* Provide vimdoc (see |help-writing|), so that users can read your plugin's documentation in Nvim, by entering `:h {plugin}` in |command-mode|. The help-tags (the right-aligned "search keywords" in the help documents) are regenerated using the |:helptags| command. DOCUMENTATION TOOLS - panvimdoc https://github.com/kdheepak/panvimdoc vim:tw=78:ts=8:sw=4:sts=4:et:ft=help:norl: