Skip to content

Commit 28ab656

Browse files
mrcjkbjustinmk
authored andcommitted
docs: Lua plugin development guide
1 parent 8a12a01 commit 28ab656

File tree

1 file changed

+278
-0
lines changed

1 file changed

+278
-0
lines changed

runtime/doc/lua-plugin.txt

Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
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 is a guide for getting started with Nvim plugin development. It is not
14+
intended as a set of rules, but as a collection of recommendations for good
15+
practices.
16+
17+
For a guide to using Lua in Nvim, please refer to |lua-guide|.
18+
19+
==============================================================================
20+
Type safety *lua-plugin-type-safety*
21+
22+
Lua, as a dynamically typed language, is great for configuration. It provides
23+
virtually immediate feedback.
24+
But for larger projects, this can be a double-edged sword, leaving your plugin
25+
susceptible to unexpected bugs at the wrong time.
26+
27+
You can leverage LuaCATS https://luals.github.io/wiki/annotations/
28+
annotations, along with lua-language-server https://luals.github.io/ to catch
29+
potential bugs in your CI before your plugin's users do.
30+
31+
------------------------------------------------------------------------------
32+
Tools *lua-plugin-type-safety-tools*
33+
34+
- lua-typecheck-action https://github.com/marketplace/actions/lua-typecheck-action
35+
- lua-language-server https://luals.github.io
36+
37+
==============================================================================
38+
Keymaps *lua-plugin-keymaps*
39+
40+
Avoid creating keymaps automatically, unless they are not controversial. Doing
41+
so can easily lead to conflicts with user |mapping|s.
42+
43+
NOTE: An example for uncontroversial keymaps are buffer-local |mapping|s for
44+
specific file types or floating windows.
45+
46+
A common approach to allow keymap configuration is to define a declarative DSL
47+
https://en.wikipedia.org/wiki/Domain-specific_language via a `setup` function.
48+
49+
However, doing so means that
50+
51+
- You will have to implement and document it yourself.
52+
- Users will likely face inconsistencies if another plugin has a slightly
53+
different DSL.
54+
- |init.lua| scripts that call such a `setup` function may throw an error if
55+
the plugin is not installed or disabled.
56+
57+
As an alternative, you can provide |<Plug>| mappings to allow users to define
58+
their own keymaps with |vim.keymap.set()|.
59+
60+
- This requires one line of code in user configs.
61+
- Even if your plugin is not installed or disabled, creating the keymap won't
62+
throw an error.
63+
64+
Another option is to simply expose a Lua function or |user-commands|.
65+
66+
Some benefits of |<Plug>| mappings are that you can
67+
68+
- Enforce options like `expr = true`.
69+
- Use |vim.keymap|'s built-in mode handling to expose functionality only for
70+
specific |map-modes|.
71+
- Handle different |map-modes| differently with a single mapping, without
72+
adding mode checks to the underlying implementation.
73+
- Detect user-defined mappings through |hasmapto()| before creating defaults.
74+
75+
Some benefits of exposing a Lua function are:
76+
77+
- Extensibility, if the function takes an options table as an argument.
78+
- A cleaner UX, if there are many options and enumerating all combinations
79+
of options would result in a lot of |<Plug>| mappings.
80+
81+
NOTE: If your function takes an options table, users may still benefit
82+
from |<Plug>| mappings for the most common combinations.
83+
84+
------------------------------------------------------------------------------
85+
Example *lua-plugin-plug-mapping-example*
86+
87+
In your plugin:
88+
>lua
89+
vim.keymap.set("n", "<Plug>(SayHello)", function()
90+
print("Hello from normal mode")
91+
end, { noremap = true })
92+
93+
vim.keymap.set("v", "<Plug>(SayHello)", function()
94+
print("Hello from visual mode")
95+
end, { noremap = true })
96+
<
97+
In the user's config:
98+
>lua
99+
vim.keymap.set({"n", "v"}, "<leader>h", "<Plug>(SayHello)")
100+
<
101+
==============================================================================
102+
Initialization *lua-plugin-initialization*
103+
104+
Newcomers to Lua plugin development will often put all initialization logic in
105+
a single `setup` function, which takes a table of options.
106+
If you do this, users will be forced to call this function in order to use
107+
your plugin, even if they are happy with the default configuration.
108+
109+
Strictly separated configuration and smart initialization allow your plugin to
110+
work out of the box.
111+
112+
NOTE: A well designed plugin has minimal impact on startup time.
113+
See also |lua-plugin-lazy-loading|.
114+
115+
Common approaches to a strictly separated configuration are:
116+
117+
- A Lua function, e.g. `setup(opts)` or `configure(opts)`, which only overrides the
118+
default configuration and does not contain any initialization logic.
119+
- A Vimscript compatible table (e.g. in the |vim.g| or |vim.b| namespace) that your
120+
plugin reads from and validates at initialization time.
121+
See also |lua-vim-variables|.
122+
123+
Typically, automatic initialization logic is done in a |plugin| or |ftplugin|
124+
script. See also |'runtimepath'|.
125+
126+
==============================================================================
127+
Lazy loading *lua-plugin-lazy-loading*
128+
129+
When it comes to initializing your plugin, assume your users may not be using
130+
a plugin manager that takes care of lazy loading for you.
131+
Making sure your plugin does not unnecessarily impact startup time is your
132+
responsibility. A plugin's functionality may evolve over time, potentially
133+
leading to breakage if users have to hack into the loading mechanisms.
134+
Furthermore, a plugin that implements its own lazy initialization properly will
135+
likely have less overhead than the mechanisms used by a plugin manager or user
136+
to load that plugin lazily.
137+
138+
------------------------------------------------------------------------------
139+
Defer `require` calls *lua-plugin-lazy-loading-defer-require*
140+
141+
|plugin| scripts should not eagerly `require` Lua modules.
142+
143+
For example, instead of:
144+
>lua
145+
local foo = require("foo")
146+
vim.api.nvim_create_user_command("MyCommand", function()
147+
foo.do_something()
148+
end, {
149+
-- ...
150+
})
151+
<
152+
which will eagerly load the `foo` module and any other modules it imports
153+
eagerly, you can lazy load it by moving the `require` into the command's
154+
implementation.
155+
>lua
156+
vim.api.nvim_create_user_command("MyCommand", function()
157+
local foo = require("foo")
158+
foo.do_something()
159+
end, {
160+
-- ...
161+
})
162+
<
163+
Likewise, if a plugin uses a Lua module as an entrypoint, it should
164+
defer `require` calls too.
165+
166+
NOTE: For a Vimscript alternative to `require`, see |autoload|.
167+
168+
NOTE: In case you are worried about eagerly creating user commands, autocommands
169+
or keymaps at startup:
170+
Plugin managers that provide abstractions for lazy-loading plugins on
171+
such events will need to create these themselves.
172+
173+
NOTE: You can use |--startuptime| to |profile| the impact a plugin has on
174+
startup time.
175+
176+
------------------------------------------------------------------------------
177+
Filetype-specific functionality *lua-plugin-lazy-loading-filetype*
178+
179+
Consider making use of |filetype| for any functionality that is specific to a
180+
filetype, by putting the initialization logic in a `ftplugin/{filetype}.lua`
181+
script.
182+
183+
------------------------------------------------------------------------------
184+
Example *lua-plugin-lazy-loading-filetype-example*
185+
186+
A plugin tailored to Rust development might have initialization in
187+
`ftplugin/rust.lua`:
188+
>lua
189+
if not vim.g.loaded_my_rust_plugin then
190+
-- Initialize
191+
end
192+
-- NOTE: Using `vim.g.loaded_` prevents the plugin from initializing twice
193+
-- and allows users to prevent plugins from loading
194+
-- (in both Lua and Vimscript).
195+
vim.g.loaded_my_rust_plugin = true
196+
197+
local bufnr = vim.api.nvim_get_current_buf()
198+
-- do something specific to this buffer,
199+
-- e.g. add a |<Plug>| mapping or create a command
200+
vim.keymap.set("n", "<Plug>(MyPluginBufferAction)", function()
201+
print("Hello")
202+
end, { noremap = true, buffer = bufnr, })
203+
<
204+
==============================================================================
205+
Configuration *lua-plugin-configuration*
206+
207+
Once you have merged the default configuration with the user's config, you
208+
should validate configs.
209+
210+
Validations could include:
211+
212+
- Correct types, see |vim.validate()|
213+
- Unknown fields in the user config (e.g. due to typos).
214+
This can be tricky to implement, and may be better suited for a |health|
215+
check, to reduce overhead.
216+
217+
==============================================================================
218+
Troubleshooting *lua-plugin-troubleshooting*
219+
220+
------------------------------------------------------------------------------
221+
Health *lua-plugin-troubleshooting-health*
222+
223+
Provide health checks in `lua/{plugin}/health.lua`.
224+
225+
Some things to validate:
226+
227+
- User configuration
228+
- Proper initialization
229+
- Presence of Lua dependencies (e.g. other plugins)
230+
- Presence of external dependencies
231+
232+
See also |vim.health| and |health-dev|.
233+
234+
------------------------------------------------------------------------------
235+
Minimal config template *lua-plugin-troubleshooting-minimal-config*
236+
237+
It can be useful to provide a template for a minimal configuration, along with
238+
a guide on how to use it to reproduce issues.
239+
240+
==============================================================================
241+
Versioning and releases *lua-plugin-versioning-releases*
242+
243+
Consider
244+
245+
- Using SemVer https://semver.org/ tags and releases to properly communicate
246+
bug fixes, new features, and breaking changes.
247+
- Automating versioning and releases in CI.
248+
- Publishing to luarocks https://luarocks.org, especially if your plugin
249+
has dependencies or components that need to be built; or if it could be a
250+
dependency for another plugin.
251+
252+
------------------------------------------------------------------------------
253+
Further reading *lua-plugin-versioning-releases-further-reading*
254+
255+
- Luarocks <3 Nvim https://github.com/nvim-neorocks/sample-luarocks-plugin
256+
257+
------------------------------------------------------------------------------
258+
Tools *lua-plugin-versioning-releases-tools*
259+
260+
- luarocks-tag-release
261+
https://github.com/marketplace/actions/luarocks-tag-release
262+
- release-please-action
263+
https://github.com/marketplace/actions/release-please-action
264+
- semantic-release
265+
https://github.com/semantic-release/semantic-release
266+
267+
==============================================================================
268+
Documentation *lua-plugin-documentation*
269+
270+
Provide vimdoc (see |help-writing|), so that users can read your plugin's
271+
documentation in Nvim, by entering `:h {plugin}` in |command-mode|.
272+
273+
------------------------------------------------------------------------------
274+
Tools *lua-plugin-documentation-tools*
275+
276+
- panvimdoc https://github.com/kdheepak/panvimdoc
277+
278+
vim:tw=78:ts=8:sw=4:sts=4:et:ft=help:norl:

0 commit comments

Comments
 (0)