neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

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:
Mruntime/doc/lua-guide.txt | 19+++++++++----------
Aruntime/doc/lua-plugin.txt | 309+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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: