mirror of
https://github.com/neovim/neovim.git
synced 2025-09-06 03:18:16 +00:00
docs: Lua plugin development guide
This commit is contained in:
@@ -10,19 +10,18 @@
|
|||||||
==============================================================================
|
==============================================================================
|
||||||
Introduction *lua-guide*
|
Introduction *lua-guide*
|
||||||
|
|
||||||
This guide will go through the basics of using Lua in Nvim. It is not meant
|
This guide introduces the basics of everyday usage of Lua to configure and
|
||||||
to be a comprehensive encyclopedia of all available features, nor will it
|
operate Nvim. It assumes some familiarity with the (non-Lua) basics of Nvim
|
||||||
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
|
|
||||||
(commands, options, mappings, autocommands), which are covered in the
|
(commands, options, mappings, autocommands), which are covered in the
|
||||||
|user-manual|.
|
|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*
|
Some words on the API *lua-guide-api*
|
||||||
|
|
||||||
|
@@ -10,11 +10,32 @@
|
|||||||
==============================================================================
|
==============================================================================
|
||||||
Introduction *lua-plugin*
|
Introduction *lua-plugin*
|
||||||
|
|
||||||
This is a guide for getting started with Nvim plugin development. It is not
|
This document provides guidance for developing Nvim (Lua) plugins:
|
||||||
intended as a set of rules, but as a collection of recommendations for good
|
|
||||||
practices.
|
|
||||||
|
|
||||||
For a guide to using Lua in Nvim, please refer to |lua-guide|.
|
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*
|
Type safety *lua-plugin-type-safety*
|
||||||
@@ -24,12 +45,12 @@ virtually immediate feedback.
|
|||||||
But for larger projects, this can be a double-edged sword, leaving your plugin
|
But for larger projects, this can be a double-edged sword, leaving your plugin
|
||||||
susceptible to unexpected bugs at the wrong time.
|
susceptible to unexpected bugs at the wrong time.
|
||||||
|
|
||||||
You can leverage LuaCATS https://luals.github.io/wiki/annotations/
|
You can leverage LuaCATS or "emmylua" annotations https://luals.github.io/wiki/annotations/
|
||||||
annotations, along with lua-language-server https://luals.github.io/ to catch
|
along with lua-language-server ("LuaLS") https://luals.github.io/ to catch
|
||||||
potential bugs in your CI before your plugin's users do.
|
potential bugs in your CI before your plugin's users do. The Nvim codebase
|
||||||
|
uses these annotations extensively.
|
||||||
|
|
||||||
------------------------------------------------------------------------------
|
TOOLS
|
||||||
Tools *lua-plugin-type-safety-tools*
|
|
||||||
|
|
||||||
- lua-typecheck-action https://github.com/marketplace/actions/lua-typecheck-action
|
- lua-typecheck-action https://github.com/marketplace/actions/lua-typecheck-action
|
||||||
- lua-language-server https://luals.github.io
|
- lua-language-server https://luals.github.io
|
||||||
@@ -37,11 +58,11 @@ Tools *lua-plugin-type-safety-tools*
|
|||||||
==============================================================================
|
==============================================================================
|
||||||
Keymaps *lua-plugin-keymaps*
|
Keymaps *lua-plugin-keymaps*
|
||||||
|
|
||||||
Avoid creating keymaps automatically, unless they are not controversial. Doing
|
Avoid creating excessive keymaps automatically. Doing so can conflict with
|
||||||
so can easily lead to conflicts with user |mapping|s.
|
user |mapping|s.
|
||||||
|
|
||||||
NOTE: An example for uncontroversial keymaps are buffer-local |mapping|s for
|
NOTE: An example for uncontroversial keymaps are buffer-local |mapping|s for
|
||||||
specific file types or floating windows.
|
specific file types or floating windows, or <Plug> mappings.
|
||||||
|
|
||||||
A common approach to allow keymap configuration is to define a declarative DSL
|
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.
|
https://en.wikipedia.org/wiki/Domain-specific_language via a `setup` function.
|
||||||
@@ -81,25 +102,24 @@ Some benefits of exposing a Lua function are:
|
|||||||
NOTE: If your function takes an options table, users may still benefit
|
NOTE: If your function takes an options table, users may still benefit
|
||||||
from |<Plug>| mappings for the most common combinations.
|
from |<Plug>| mappings for the most common combinations.
|
||||||
|
|
||||||
------------------------------------------------------------------------------
|
KEYMAP EXAMPLE
|
||||||
Example *lua-plugin-plug-mapping-example*
|
|
||||||
|
|
||||||
In your plugin:
|
In your plugin:
|
||||||
>lua
|
>lua
|
||||||
vim.keymap.set("n", "<Plug>(SayHello)", function()
|
vim.keymap.set('n', '<Plug>(SayHello)', function()
|
||||||
print("Hello from normal mode")
|
print('Hello from normal mode')
|
||||||
end, { noremap = true })
|
end, { noremap = true })
|
||||||
|
|
||||||
vim.keymap.set("v", "<Plug>(SayHello)", function()
|
vim.keymap.set('v', '<Plug>(SayHello)', function()
|
||||||
print("Hello from visual mode")
|
print('Hello from visual mode')
|
||||||
end, { noremap = true })
|
end, { noremap = true })
|
||||||
<
|
<
|
||||||
In the user's config:
|
In the user's config:
|
||||||
>lua
|
>lua
|
||||||
vim.keymap.set({"n", "v"}, "<leader>h", "<Plug>(SayHello)")
|
vim.keymap.set({'n', 'v'}, '<leader>h', '<Plug>(SayHello)')
|
||||||
<
|
<
|
||||||
==============================================================================
|
==============================================================================
|
||||||
Initialization *lua-plugin-initialization*
|
Initialization *lua-plugin-init*
|
||||||
|
|
||||||
Newcomers to Lua plugin development will often put all initialization logic in
|
Newcomers to Lua plugin development will often put all initialization logic in
|
||||||
a single `setup` function, which takes a table of options.
|
a single `setup` function, which takes a table of options.
|
||||||
@@ -109,8 +129,8 @@ your plugin, even if they are happy with the default configuration.
|
|||||||
Strictly separated configuration and smart initialization allow your plugin to
|
Strictly separated configuration and smart initialization allow your plugin to
|
||||||
work out of the box.
|
work out of the box.
|
||||||
|
|
||||||
NOTE: A well designed plugin has minimal impact on startup time.
|
NOTE: A well designed plugin has minimal impact on startup time. See also
|
||||||
See also |lua-plugin-lazy-loading|.
|
|lua-plugin-lazy|.
|
||||||
|
|
||||||
Common approaches to a strictly separated configuration are:
|
Common approaches to a strictly separated configuration are:
|
||||||
|
|
||||||
@@ -124,37 +144,45 @@ Typically, automatic initialization logic is done in a |plugin| or |ftplugin|
|
|||||||
script. See also |'runtimepath'|.
|
script. See also |'runtimepath'|.
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
Lazy loading *lua-plugin-lazy-loading*
|
Lazy loading *lua-plugin-lazy*
|
||||||
|
|
||||||
When it comes to initializing your plugin, assume your users may not be using
|
Some users like to micro-manage "lazy loading" of plugins by explicitly
|
||||||
a plugin manager that takes care of lazy loading for you.
|
configuring which commands and key mappings load the plugin.
|
||||||
Making sure your plugin does not unnecessarily impact startup time is your
|
|
||||||
responsibility. A plugin's functionality may evolve over time, potentially
|
Your plugin should not depend on every user micro-managing their configuration
|
||||||
leading to breakage if users have to hack into the loading mechanisms.
|
in such a way. Nvim has a mechanism for every plugin to do its own implicit
|
||||||
Furthermore, a plugin that implements its own lazy initialization properly will
|
lazy-loading (in Vimscript it's called |autoload|), via `autoload/`
|
||||||
likely have less overhead than the mechanisms used by a plugin manager or user
|
(Vimscript) and `lua/` (Lua). Plugin authors can provide "lazy loading" by
|
||||||
to load that plugin lazily.
|
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-lazy-loading-defer-require*
|
Defer require() calls *lua-plugin-defer-require*
|
||||||
|
|
||||||
|plugin| scripts should not eagerly `require` Lua modules.
|
`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:
|
For example, instead of:
|
||||||
>lua
|
>lua
|
||||||
local foo = require("foo")
|
local foo = require('foo')
|
||||||
vim.api.nvim_create_user_command("MyCommand", function()
|
vim.api.nvim_create_user_command('MyCommand', function()
|
||||||
foo.do_something()
|
foo.do_something()
|
||||||
end, {
|
end, { -- ... })
|
||||||
-- ...
|
|
||||||
})
|
|
||||||
<
|
<
|
||||||
which will eagerly load the `foo` module and any other modules it imports
|
which calls `require('foo')` as soon as the module is loaded, you can
|
||||||
eagerly, you can lazy load it by moving the `require` into the command's
|
lazy-load it by moving the `require` into the command's implementation:
|
||||||
implementation.
|
|
||||||
>lua
|
>lua
|
||||||
vim.api.nvim_create_user_command("MyCommand", function()
|
vim.api.nvim_create_user_command('MyCommand', function()
|
||||||
local foo = require("foo")
|
local foo = require('foo')
|
||||||
foo.do_something()
|
foo.do_something()
|
||||||
end, {
|
end, {
|
||||||
-- ...
|
-- ...
|
||||||
@@ -165,23 +193,23 @@ defer `require` calls too.
|
|||||||
|
|
||||||
NOTE: For a Vimscript alternative to `require`, see |autoload|.
|
NOTE: For a Vimscript alternative to `require`, see |autoload|.
|
||||||
|
|
||||||
NOTE: In case you are worried about eagerly creating user commands, autocommands
|
NOTE: If you are worried about eagerly creating user commands, autocommands or
|
||||||
or keymaps at startup:
|
keymaps at startup: Plugin managers that provide abstractions for lazy-loading
|
||||||
Plugin managers that provide abstractions for lazy-loading plugins on
|
plugins on such events do the same amount of work. There is no performance
|
||||||
such events will need to create these themselves.
|
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
|
NOTE: You can use |--startuptime| to |profile| the impact a plugin has on
|
||||||
startup time.
|
startup time.
|
||||||
|
|
||||||
------------------------------------------------------------------------------
|
------------------------------------------------------------------------------
|
||||||
Filetype-specific functionality *lua-plugin-lazy-loading-filetype*
|
Filetype-specific functionality *lua-plugin-filetype*
|
||||||
|
|
||||||
Consider making use of |filetype| for any functionality that is specific to a
|
Consider making use of 'filetype' for any functionality that is specific to
|
||||||
filetype, by putting the initialization logic in a `ftplugin/{filetype}.lua`
|
a filetype, by putting the initialization logic in a `ftplugin/{filetype}.lua`
|
||||||
script.
|
script.
|
||||||
|
|
||||||
------------------------------------------------------------------------------
|
FILETYPE EXAMPLE
|
||||||
Example *lua-plugin-lazy-loading-filetype-example*
|
|
||||||
|
|
||||||
A plugin tailored to Rust development might have initialization in
|
A plugin tailored to Rust development might have initialization in
|
||||||
`ftplugin/rust.lua`:
|
`ftplugin/rust.lua`:
|
||||||
@@ -197,12 +225,12 @@ A plugin tailored to Rust development might have initialization in
|
|||||||
local bufnr = vim.api.nvim_get_current_buf()
|
local bufnr = vim.api.nvim_get_current_buf()
|
||||||
-- do something specific to this buffer,
|
-- do something specific to this buffer,
|
||||||
-- e.g. add a |<Plug>| mapping or create a command
|
-- e.g. add a |<Plug>| mapping or create a command
|
||||||
vim.keymap.set("n", "<Plug>(MyPluginBufferAction)", function()
|
vim.keymap.set('n', '<Plug>(MyPluginBufferAction)', function()
|
||||||
print("Hello")
|
print('Hello')
|
||||||
end, { noremap = true, buffer = bufnr, })
|
end, { noremap = true, buffer = bufnr, })
|
||||||
<
|
<
|
||||||
==============================================================================
|
==============================================================================
|
||||||
Configuration *lua-plugin-configuration*
|
Configuration *lua-plugin-config*
|
||||||
|
|
||||||
Once you have merged the default configuration with the user's config, you
|
Once you have merged the default configuration with the user's config, you
|
||||||
should validate configs.
|
should validate configs.
|
||||||
@@ -217,10 +245,16 @@ Validations could include:
|
|||||||
==============================================================================
|
==============================================================================
|
||||||
Troubleshooting *lua-plugin-troubleshooting*
|
Troubleshooting *lua-plugin-troubleshooting*
|
||||||
|
|
||||||
------------------------------------------------------------------------------
|
While developing a plugin, you can use the |:restart| command to see the
|
||||||
Health *lua-plugin-troubleshooting-health*
|
result of code changes in your plugin.
|
||||||
|
|
||||||
Provide health checks in `lua/{plugin}/health.lua`.
|
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:
|
Some things to validate:
|
||||||
|
|
||||||
@@ -229,19 +263,18 @@ Some things to validate:
|
|||||||
- Presence of Lua dependencies (e.g. other plugins)
|
- Presence of Lua dependencies (e.g. other plugins)
|
||||||
- Presence of external dependencies
|
- Presence of external dependencies
|
||||||
|
|
||||||
See also |vim.health| and |health-dev|.
|
MINIMAL CONFIG TEMPLATE
|
||||||
|
|
||||||
------------------------------------------------------------------------------
|
|
||||||
Minimal config template *lua-plugin-troubleshooting-minimal-config*
|
|
||||||
|
|
||||||
It can be useful to provide a template for a minimal configuration, along with
|
It can be useful to provide a template for a minimal configuration, along with
|
||||||
a guide on how to use it to reproduce issues.
|
a guide on how to use it to reproduce issues.
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
Versioning and releases *lua-plugin-versioning-releases*
|
Versioning and releases *lua-plugin-versioning*
|
||||||
|
|
||||||
Consider
|
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
|
- Using SemVer https://semver.org/ tags and releases to properly communicate
|
||||||
bug fixes, new features, and breaking changes.
|
bug fixes, new features, and breaking changes.
|
||||||
- Automating versioning and releases in CI.
|
- Automating versioning and releases in CI.
|
||||||
@@ -249,13 +282,11 @@ Consider
|
|||||||
has dependencies or components that need to be built; or if it could be a
|
has dependencies or components that need to be built; or if it could be a
|
||||||
dependency for another plugin.
|
dependency for another plugin.
|
||||||
|
|
||||||
------------------------------------------------------------------------------
|
FURTHER READING
|
||||||
Further reading *lua-plugin-versioning-releases-further-reading*
|
|
||||||
|
|
||||||
- Luarocks <3 Nvim https://github.com/nvim-neorocks/sample-luarocks-plugin
|
- Luarocks ❤️ Nvim https://github.com/nvim-neorocks/sample-luarocks-plugin
|
||||||
|
|
||||||
------------------------------------------------------------------------------
|
VERSIONING TOOLS
|
||||||
Tools *lua-plugin-versioning-releases-tools*
|
|
||||||
|
|
||||||
- luarocks-tag-release
|
- luarocks-tag-release
|
||||||
https://github.com/marketplace/actions/luarocks-tag-release
|
https://github.com/marketplace/actions/luarocks-tag-release
|
||||||
@@ -265,14 +296,14 @@ Tools *lua-plugin-versioning-releases-tools*
|
|||||||
https://github.com/semantic-release/semantic-release
|
https://github.com/semantic-release/semantic-release
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
Documentation *lua-plugin-documentation*
|
Documentation *lua-plugin-doc*
|
||||||
|
|
||||||
Provide vimdoc (see |help-writing|), so that users can read your plugin's
|
Provide vimdoc (see |help-writing|), so that users can read your plugin's
|
||||||
documentation in Nvim, by entering `:h {plugin}` in |command-mode|.
|
documentation in Nvim, by entering `:h {plugin}` in |command-mode|.
|
||||||
|
|
||||||
------------------------------------------------------------------------------
|
DOCUMENTATION TOOLS
|
||||||
Tools *lua-plugin-documentation-tools*
|
|
||||||
|
|
||||||
- panvimdoc https://github.com/kdheepak/panvimdoc
|
- panvimdoc https://github.com/kdheepak/panvimdoc
|
||||||
|
|
||||||
|
|
||||||
vim:tw=78:ts=8:sw=4:sts=4:et:ft=help:norl:
|
vim:tw=78:ts=8:sw=4:sts=4:et:ft=help:norl:
|
||||||
|
Reference in New Issue
Block a user