commit 4f374bf938283b2d8c842bddb4583337aba0555d
parent c4c69c5012e24b8df38e8ed9f738f0e179a24790
Author: Justin M. Keyes <justinkz@gmail.com>
Date: Mon, 1 Sep 2025 19:53:13 -0400
Merge #29073 docs: Lua plugin guide
Diffstat:
2 files changed, 318 insertions(+), 10 deletions(-)
diff --git a/runtime/doc/lua-guide.txt b/runtime/doc/lua-guide.txt
@@ -10,19 +10,18 @@
==============================================================================
Introduction *lua-guide*
-This guide will go through the basics of using Lua in Nvim. It is not meant
-to be a comprehensive encyclopedia of all available features, nor will it
-detail all intricacies. Think of it as a survival kit -- the bare minimum
-needed to know to comfortably get started on using Lua in Nvim.
-
-An important thing to note is that this isn't a guide to the Lua language
-itself. Rather, this is a guide on how to configure and modify Nvim through
-the Lua language and the functions we provide to help with this. Take a look
-at |luaref| and |lua-concepts| if you'd like to learn more about Lua itself.
-Similarly, this guide assumes some familiarity with the basics of Nvim
+This guide introduces the basics of everyday usage of Lua to configure and
+operate Nvim. It assumes some familiarity with the (non-Lua) basics of Nvim
(commands, options, mappings, autocommands), which are covered in the
|user-manual|.
+This is not a comprehensive encyclopedia of all available features. Think of
+it as a survival kit: the bare minimum needed to comfortably get started on
+using Lua in Nvim.
+
+See |lua-plugin| for guidance on developing Lua plugins.
+See |luaref| and |lua-concepts| for details on the Lua programming language.
+
------------------------------------------------------------------------------
Some words on the API *lua-guide-api*
diff --git a/runtime/doc/lua-plugin.txt b/runtime/doc/lua-plugin.txt
@@ -0,0 +1,309 @@
+*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 maniest or "registration" required.
+
+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`, 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 <Plug> 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 |<Plug>| 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 |<Plug>| 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 |<Plug>| mappings.
+
+NOTE: If your function takes an options table, users may still benefit
+ from |<Plug>| mappings for the most common combinations.
+
+KEYMAP EXAMPLE
+
+In your plugin:
+>lua
+ vim.keymap.set('n', '<Plug>(SayHello)', function()
+ print('Hello from normal mode')
+ end, { noremap = true })
+
+ vim.keymap.set('v', '<Plug>(SayHello)', function()
+ print('Hello from visual mode')
+ end, { noremap = true })
+<
+In the user's config:
+>lua
+ vim.keymap.set({'n', 'v'}, '<leader>h', '<Plug>(SayHello)')
+<
+==============================================================================
+Initialization *lua-plugin-init*
+
+Newcomers to Lua plugin development will often put all initialization logic in
+a single `setup` function, which takes a table of options.
+If you do this, users will be forced to call this function in order to use
+your plugin, even if they are happy with the default configuration.
+
+Strictly separated configuration and smart initialization allow your plugin to
+work out of the box.
+
+NOTE: A well designed plugin has minimal impact on startup time. See also
+|lua-plugin-lazy|.
+
+Common approaches to a strictly separated configuration 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'|.
+
+==============================================================================
+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/<name>.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/<name>.lua` small, avoid eagerly calling `require()` on modules
+ until a command or mapping is actually used.
+
+------------------------------------------------------------------------------
+Defer require() calls *lua-plugin-defer-require*
+
+`plugin/<name>.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/<name>.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 |<Plug>| mapping or create a command
+ vim.keymap.set('n', '<Plug>(MyPluginBufferAction)', function()
+ print('Hello')
+ end, { noremap = true, 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 discourged 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|.
+
+DOCUMENTATION TOOLS
+
+- panvimdoc https://github.com/kdheepak/panvimdoc
+
+
+vim:tw=78:ts=8:sw=4:sts=4:et:ft=help:norl: