docs: dev, lsp, indent-guides #39756

- document "indent guides" https://github.com/neovim/neovim/issues/39726
- document guidance for "subcommands" https://github.com/neovim/neovim/issues/32263#issuecomment-4436002808

Co-authored-by: glepnir <glephunter@gmail.com>
Co-authored-by: Noa Levi <275430404+lphuc2250gma@users.noreply.github.com>
This commit is contained in:
Justin M. Keyes
2026-05-14 13:13:30 -04:00
committed by GitHub
parent c7f5f44b60
commit 4f27b585e9
14 changed files with 158 additions and 100 deletions

View File

@@ -171,7 +171,7 @@ Put this in `uppercase.vim` and run: >bash
nvim --headless --cmd "source uppercase.vim"
==============================================================================
5. Using a prompt buffer *prompt-buffer*
5. Using a prompt buffer *repl* *prompt-buffer*
Prompt buffers provide a "prompt" interface: they are like regular buffers,
except only the last section of the buffer is editable, and the user can
@@ -221,8 +221,12 @@ Any command that starts Insert mode, such as "a", "i", "A" and "I", will move
the cursor to the last line. "A" will move to the end of the line, "I" to the
start of the line.
Example: start a shell in the background and prompt for the next shell
command, displaying shell output above the prompt: >lua
*emacs-eshell*
Example: The following code snippet implements a "shell buffer" similar to
Emacs "eshell". Because the interface is a prompt-buffer, you can edit the
prompt using normal Nvim commands, unlike a |:terminal| shell. Each prompt is
sent to the shell process started by |jobstart()|, and appended to the buffer
when output is received: >lua
local shell_job

View File

@@ -275,7 +275,8 @@ Docstring format:
- {somefield}? (integer) Documentation
for some field
<
- Files declared as `@meta` are only used for typing and documentation (similar to "*.d.ts" typescript files).
- Files declared as `@meta` are only used for typing and documentation
(similar to "*.d.ts" typescript files).
Example: the help for |vim.paste()| is generated from a docstring decorating
vim.paste in runtime/lua/vim/_core/editor.lua like this: >
@@ -315,10 +316,11 @@ have help tags. It runs in CI, but you can also run it locally: >
<
------------------------------------------------------------------------------
Stdlib design *dev-lua*
Stdlib *dev-lua*
See also |dev-naming|.
GUIDANCE
- Lua names generally should conform to |dev-naming|.
- Keep the core Lua modules |lua-stdlib| simple. Avoid elaborate OOP or
pseudo-OOP designs. Plugin authors just want functions to call, not a big,
fancy inheritance hierarchy.
@@ -337,7 +339,8 @@ See also |dev-naming|.
- Mimic the pairs() or ipairs() interface if the function is intended for
use in a |for-in| loop.
*dev-error-patterns*
ERRORS *dev-error-patterns*
To communicate failure to a consumer, choose from these patterns (in order of
preference):
1. `retval, errmsg`
@@ -359,9 +362,37 @@ preference):
- High-level / application-level messages. End-user invokes these directly.
INTERFACE CONVENTIONS *dev-patterns*
------------------------------------------------------------------------------
API *dev-api*
Where applicable, these patterns apply to _both_ Lua and the API:
Where applicable, this section applies to both Lua stdlib and RPC API.
GUIDANCE
- When adding an API, check the following:
- What precedents did you draw from? How does your solution compare to them?
- Does your new API allow future expansion? How? Or why not?
- Is the new API similar to existing APIs? Do we need to deprecate the old ones?
- Did you cross-reference related concepts in the docs?
- Avoid "mutually exclusive" parameters (via constraints or limitations, if
necessary). For example nvim_create_autocmd() has mutually exclusive
"callback" and "command" args; but the "command" arg could be eliminated by
simply not supporting Vimscript function names, and treating a string
"callback" arg as an Ex command (which can call Vimscript functions). The
"buffer" arg could also be eliminated by treating a number "pattern" as
a buffer number.
- Avoid functions that depend on cursor position, current buffer, etc. Instead
the function should take a position parameter, buffer parameter, etc.
WHERE THINGS GO
- API (libnvim/RPC): exposes low-level internals, or fundamental things (such
as `nvim_exec_lua()`) needed by clients or C consumers.
- Lua stdlib = high-level functionality that builds on top of the API.
See also |dev-layout|.
API PATTERNS *dev-api-patterns*
- When accepting a buffer id, etc., 0 means "current buffer", nil means "all
buffers". Likewise for window id, tabpage id, etc.
@@ -375,11 +406,9 @@ Where applicable, these patterns apply to _both_ Lua and the API:
`function(<args>, cb(<ret)>))` => `function(<args>) -> <ret>`.
- Example: >lua
-- ✅ OK:
filter(…, opts, function() … end)
filter(…, opts, fn)
-- ❌ NO:
filter(function() … end, …, opts)
-- ❌ NO:
filter(…, function() … end, opts)
filter(…, fn, opts)
- Expose a `config(opts)` function if the module accepts user configuration.
If `opts` is omitted (or `nil`) it returns the current configuration.
- Example: See |vim.diagnostic.config()|.
@@ -406,32 +435,7 @@ Where applicable, these patterns apply to _both_ Lua and the API:
<
------------------------------------------------------------------------------
API design *dev-api*
See also |dev-naming|.
- When adding an API, check the following:
- What precedents did you draw from? How does your solution compare to them?
- Does your new API allow future expansion? How? Or why not?
- Is the new API similar to existing APIs? Do we need to deprecate the old ones?
- Did you cross-reference related concepts in the docs?
- Avoid "mutually exclusive" parameters--via constraints or limitations, if
necessary. For example nvim_create_autocmd() has mutually exclusive
"callback" and "command" args; but the "command" arg could be eliminated by
simply not supporting Vimscript function names, and treating a string
"callback" arg as an Ex command (which can call Vimscript functions). The
"buffer" arg could also be eliminated by treating a number "pattern" as
a buffer number.
- Avoid functions that depend on cursor position, current buffer, etc. Instead
the function should take a position parameter, buffer parameter, etc.
WHERE THINGS GO
- API (libnvim/RPC): exposes low-level internals, or fundamental things (such
as `nvim_exec_lua()`) needed by clients or C consumers.
- Lua stdlib = high-level functionality that builds on top of the API.
NAMING GUIDELINES *dev-naming*
NAMING *dev-naming*
Naming is exceedingly important: the name of a thing is the primary interface
for uses it, discusses it, searches for it, shares it... Consistent
@@ -456,6 +460,7 @@ Use existing common {verb} names (actions) if possible:
user-initiated and without error. (Compare "abort", which
cancels and signals error/failure.)
- clear: Clears state but does not destroy the container
- close: Releases/destroys a resource.
- create: Creates a new (non-trivial) thing (TODO: rename to "def"?)
- del: Deletes a thing (or group of things)
- detach: Dispose attached listener (TODO: rename to "un"?)
@@ -484,6 +489,7 @@ Use existing common {verb} names (actions) if possible:
Do NOT use these deprecated verbs:
- contains: Prefer "has".
- destroy: Prefer "close".
- disable: Prefer `enable(enable: boolean)`.
- exit: Prefer "cancel" (or "stop" if appropriate).
- is_disabled: Prefer `!is_enabled()`.
@@ -511,6 +517,23 @@ Do NOT use these deprecated nouns:
- command Use "cmd" instead
- window Use "win" instead
*continuation*
A "continuation" is implemented as a callback function that returns the result
of an async function and is called exactly once. Often accepts `err` as the
first parameter, to avoid erroring on the main thread. Example: >
foo(…, callback: fun(err, …))
Use the name `callback` only for a continuation. All other callbacks and event
handlers should be named with the `on_…` convention. |dev-name-events|
*dev-namespace-name*
Use namespace names like `nvim.foo.bar`: >
vim.api.nvim_create_namespace('nvim.lsp.codelens')
<
*dev-augroup-name*
Use autocommand group names like `nvim.foo.bar`: >
vim.api.nvim_create_augroup('nvim.treesitter.dev')
<
*dev-name-events*
Use the "on_" prefix to name event-handling callbacks and also the interface for
"registering" such handlers (on_key). The dual nature is acceptable to avoid
@@ -519,21 +542,12 @@ a confused collection of naming conventions for these related concepts.
Editor |events| (autocommands) are historically named like: >
{Noun}{Event}
Use this format to name API (RPC) events: >
nvim_{noun}_{event-name}_event
Example: >
nvim_buf_changedtick_event
RPC API events use this format: >
nvim_{noun}_{event}_event (example: nvim_buf_changedtick_event)
<
*continuation*
A "continuation" is implemented as a callback function that returns the result
of an async function and is called exactly once. Often accepts `err` as the
first parameter, to avoid erroring on the main thread. Example: >
foo(…, callback: fun(err, …))
Use the name `callback` only for a continuation. All other callbacks and event
handlers should be named with the |dev-name-events| "on_…" convention.
*dev-api-name*
RPC API NAMING *dev-api-name*
Use this format to name new RPC |API| functions: >
nvim_{topic}_{verb}_{arbitrary-qualifiers}
@@ -552,19 +566,8 @@ be a parameter (typically manifest as mutually-exclusive buf/win/… flags like
- Example: `nvim_buf_del_mark` acts on a `Buffer` object (the first parameter)
and uses the "del" {verb}.
*dev-namespace-name*
Use namespace names like `nvim.foo.bar`: >
vim.api.nvim_create_namespace('nvim.lsp.codelens')
<
*dev-augroup-name*
Use autocommand group names like `nvim.foo.bar`: >
vim.api.nvim_create_augroup('nvim.treesitter.dev')
<
INTERFACE PATTERNS *dev-api-patterns*
Prefer adding a single `nvim_{topic}_{verb}_…` interface for a given topic.
The RPC API doesn't have "modules" so its functions are grouped by a naming
convention. Prefer the `nvim_{topic}_{verb}_…` convention where possible.
Example: >
@@ -614,6 +617,34 @@ recommended (compare these 12(!) functions to the above 3 functions): >
nvim_win_get_ns(…)
nvim_tabpage_get_ns(…)
------------------------------------------------------------------------------
Interface patterns *dev-interface-patterns*
EX COMMANDS *dev-excmd*
Prefer "flat" Ex commands (`:foobar`) instead of "subcommands" (`:foo bar`).
except when the benefits below outweigh the costs.
Pros of subcommands:
- Useful when subcommands are discovered _at runtime_ (e.g. `:checkhealth foo`).
Plugins can extend the dispatcher by injecting subcommands.
- `:Foo<tab>` completes only the `Foo` subcommands, whereas completing
a flat name matches every command sharing the prefix (e.g. `:FooBar` and
`:FootballBar`).
- Completion can vary dynamically: e.g. `:lsp <tab>` can hide `stop` if no
client is running. (But this can be an antifeature, because it makes the
behavior less predictable.)
Cons of subcommands:
- Less harmonious with fuzzy pickers. Pickers can easily list `:foobar`,
whereas enumerating subcommands requires special logic. And in a picker,
`:foo bar` is often two "steps".
- Adds a step when tab-completing.
- Awkward to support `[!]`, ranges, `[count]`, etc., per-subcommand.
- Implementation cost: a dispatcher with custom completion.
------------------------------------------------------------------------------
API client *dev-api-client*
@@ -656,7 +687,7 @@ Examples of API-client package names:
- ❌ NO: python-client
- ❌ NO: neovim_
API CLIENT IMPLEMENTATION GUIDELINES
API CLIENT IMPLEMENTATION
- Separate the transport layer from the rest of the library. |rpc-connecting|
- Use a MessagePack library that implements at least version 5 of the
@@ -680,7 +711,7 @@ API CLIENT IMPLEMENTATION GUIDELINES
------------------------------------------------------------------------------
EXTERNAL UI *dev-ui*
External UI *dev-ui*
External UIs should be aware of the |api-contract|. In particular, future
versions of Nvim may add new items to existing events. The API is strongly

View File

@@ -20,7 +20,7 @@ The purpose of this document is to give:
Type |gO| to see the table of contents.
==============================================================================
Project layout: where things go
Project layout: where things go *dev-layout*
C CODE

View File

@@ -29,28 +29,25 @@ Follow these steps to get LSP features:
https://microsoft.github.io/language-server-protocol/implementors/servers/
2. Define a new config |lsp-new-config| (or install https://github.com/neovim/nvim-lspconfig).
Example: >lua
vim.lsp.config['lua_ls'] = {
vim.lsp.config['emmylua_ls'] = {
-- Command and arguments to start the server.
cmd = { 'lua-language-server' },
cmd = { 'emmylua_ls' },
-- Filetypes to automatically attach to.
filetypes = { 'lua' },
-- Sets the "workspace" to the directory where any of these files is found.
-- Files that share a root directory will reuse the LSP server connection.
-- Sets the workspace "root" to the directory where any of these files is found.
-- Files sharing a root will reuse the LSP client/connection.
-- Nested lists indicate equal priority, see |vim.lsp.Config|.
root_markers = { { '.luarc.json', '.luarc.jsonc' }, '.git' },
-- Specific settings to send to the server. The schema is server-defined.
-- Example: https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json
root_markers = { { '.emmyrc.json', '.luarc.json' }, '.git' },
-- Server-specific settings. https://github.com/EmmyLuaLs/emmylua-analyzer-rust/blob/main/docs/config/emmyrc_json_EN.md
settings = {
Lua = {
runtime = {
version = 'LuaJIT',
}
runtime = {
version = 'LuaJIT',
}
}
}
3. Use |vim.lsp.enable()| to enable the config.
Example: >lua
vim.lsp.enable('lua_ls')
vim.lsp.enable('emmylua_ls')
4. Open a code file matching one of the `filetypes` specified in the config.
Note: Depending on the LSP server, you may need to ensure your project has
a |lsp-root_markers| file so the workspace can be recognized.
@@ -1109,8 +1106,8 @@ config({name}, {cfg}) *vim.lsp.config()*
filetypes = { 'c', 'cpp' },
}
<
• Get the resolved configuration for "lua_ls": >lua
local cfg = vim.lsp.config.lua_ls
• Get the resolved configuration for "emmylua_ls": >lua
local cfg = vim.lsp.config.emmylua_ls
<
Attributes: ~
@@ -1137,13 +1134,13 @@ enable({name}, {enable}) *vim.lsp.enable()*
Examples: >lua
vim.lsp.enable('clangd')
vim.lsp.enable({'lua_ls', 'pyright'})
vim.lsp.enable({'emmylua_ls', 'pyright'})
<
Example: To dynamically decide whether LSP is activated, define a
|lsp-root_dir()| function which calls `on_dir()` only when you want that
config to activate: >lua
vim.lsp.config('lua_ls', {
vim.lsp.config('emmylua_ls', {
root_dir = function(bufnr, on_dir)
if vim.fs.ext(vim.fn.bufname(bufnr)) ~= 'txt' then
on_dir(vim.fn.getcwd())
@@ -1707,7 +1704,7 @@ workspace_diagnostics({opts}) *vim.lsp.buf.workspace_diagnostics()*
clients.
See also: ~
• https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_dagnostics
• https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_diagnostic
workspace_symbol({query}, {opts}) *vim.lsp.buf.workspace_symbol()*
Lists all symbols in the current workspace in the quickfix window.

View File

@@ -31,6 +31,8 @@ copying the "example_init.lua" file: >vim
:exe 'edit' stdpath('config') .. '/init.lua'
:read $VIMRUNTIME/example_init.lua
:write ++p
:restart
<
See |lua-guide| for practical notes on using Lua to configure Nvim.
@@ -55,7 +57,7 @@ Transitioning from Vim *nvim-from-vim*
1. To start the transition, create your |init.vim| (user config) file: >vim
:exe 'edit '.stdpath('config').'/init.vim'
:exe 'edit' stdpath('config') .. '/init.vim'
:write ++p
2. Add these contents to the file: >vim

View File

@@ -4189,7 +4189,7 @@ A jump table for the options with a short description can be found at |Q_op|.
combine it with "tab:", for example: >vim
set listchars+=tab:>-,lead:.
<
*lcs-leadmultispace*
*lcs-leadmultispace* *indent-guides*
leadmultispace:c...
Like the |lcs-multispace| value, but for leading
spaces only. Also overrides |lcs-lead| for leading
@@ -4200,6 +4200,13 @@ A jump table for the options with a short description can be found at |Q_op|.
<
Where "XXX" denotes the first non-blank characters in
the line.
Combined with |lcs-leadtab|, this can be used to show
"indentation guides" (vertical lines).
For example, with 'shiftwidth' 2: >vim
set list listchars=leadtab:\ \ │,tab:\ \ │,leadmultispace:\ \ │
< For richer rendering (per-level colors, treesitter-aware
scopes, etc.) use a third-party plugin.
*lcs-leadtab*
leadtab:xy[z]
Like |lcs-tab|, but only for leading tabs. When

View File

@@ -620,7 +620,7 @@ setting can affect the entire editor in ways that are not initially obvious.
To find the cause of a problem in your config, you must "bisect" it:
1. Remove or disable half of your |config|.
2. Restart Nvim.
2. Restart Nvim (|:restart|).
3. If the problem still occurs, goto 1.
4. If the problem is gone, restore half of the removed lines.
5. Continue narrowing your config in this way, until you find the setting or

View File

@@ -1780,7 +1780,7 @@ Query:iter_captures({node}, {source}, {start_row}, {end_row}, {opts})
Return: ~
(`fun(end_line: integer?, end_col: integer?): integer, TSNode, vim.treesitter.query.TSMetadata, TSQueryMatch, TSTree`)
capture id, capture node, metadata, match, tree
capture-id, capture-node, metadata, match, tree
*Query:iter_matches()*
Query:iter_matches({node}, {source}, {start}, {stop}, {opts})
@@ -1822,7 +1822,7 @@ Query:iter_matches({node}, {source}, {start}, {stop}, {opts})
Return: ~
(`fun(): integer, table<integer, TSNode[]>, vim.treesitter.query.TSMetadata, TSTree`)
pattern id, match, metadata, tree
pattern-id, match, metadata, tree
set({lang}, {query_name}, {text}) *vim.treesitter.query.set()*
Sets the runtime query named {query_name} for {lang}

View File

@@ -552,7 +552,7 @@ local function parse_cmdline(arg_lead, cmd_line)
if arg_lead:match('^[^()]+%([^()]*$') then
-- cursor (|) is at ':Man printf(|' or ':Man 1 printf(|'
-- The later is is allowed because of ':Man pri<TAB>'.
-- The latter is allowed because of ':Man pri<TAB>'.
-- It will offer 'priclass.d(1m)' even though section is specified as 1.
local tmp = vim.split(arg_lead, '(', { plain = true })
local name = tmp[1]

View File

@@ -4158,7 +4158,7 @@ vim.wo.list = vim.o.list
--- set listchars+=tab:>-,lead:.
--- ```
---
--- *lcs-leadmultispace*
--- *lcs-leadmultispace* *indent-guides*
--- leadmultispace:c...
--- Like the `lcs-multispace` value, but for leading
--- spaces only. Also overrides `lcs-lead` for leading
@@ -4171,6 +4171,16 @@ vim.wo.list = vim.o.list
---
--- Where "XXX" denotes the first non-blank characters in
--- the line.
---
--- Combined with `lcs-leadtab`, this can be used to show
--- "indentation guides" (vertical lines).
--- For example, with 'shiftwidth' 2:
---
--- ```vim
--- set list listchars=leadtab:\ \ │,tab:\ \ │,leadmultispace:\ \ │
--- ```
--- For richer rendering (per-level colors, treesitter-aware
--- scopes, etc.) use a third-party plugin.
--- *lcs-leadtab*
--- leadtab:xy[z]
--- Like `lcs-tab`, but only for leading tabs. When

View File

@@ -274,9 +274,9 @@ end
--- filetypes = { 'c', 'cpp' },
--- }
--- ```
--- - Get the resolved configuration for "lua_ls":
--- - Get the resolved configuration for "emmylua_ls":
--- ```lua
--- local cfg = vim.lsp.config.lua_ls
--- local cfg = vim.lsp.config.emmylua_ls
--- ```
---
---@since 13
@@ -589,14 +589,14 @@ end
---
--- ```lua
--- vim.lsp.enable('clangd')
--- vim.lsp.enable({'lua_ls', 'pyright'})
--- vim.lsp.enable({'emmylua_ls', 'pyright'})
--- ```
---
--- Example: To _dynamically_ decide whether LSP is activated, define a |lsp-root_dir()| function
--- which calls `on_dir()` only when you want that config to activate:
---
--- ```lua
--- vim.lsp.config('lua_ls', {
--- vim.lsp.config('emmylua_ls', {
--- root_dir = function(bufnr, on_dir)
--- if vim.fs.ext(vim.fn.bufname(bufnr)) ~= 'txt' then
--- on_dir(vim.fn.getcwd())

View File

@@ -1077,7 +1077,7 @@ end
--- Request workspace-wide diagnostics.
--- @param opts? vim.lsp.WorkspaceDiagnosticsOpts
--- @see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_dagnostics
--- @see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_diagnostic
function M.workspace_diagnostics(opts)
validate('opts', opts, 'table', true)

View File

@@ -923,7 +923,7 @@ end
--- - start_col (integer) Starting column for the search.
---
---@return (fun(end_line: integer|nil, end_col: integer|nil): integer, TSNode, vim.treesitter.query.TSMetadata, TSQueryMatch, TSTree):
--- capture id, capture node, metadata, match, tree
--- capture-id, capture-node, metadata, match, tree
---
---@note Captures are only returned if the query pattern of a specific capture contained predicates.
function Query:iter_captures(node, source, start_row, end_row, opts)
@@ -1040,7 +1040,7 @@ end
--- - max_start_depth (integer) if non-zero, sets the maximum start depth
--- for each match. This is used to prevent traversing too deep into a tree.
---
---@return (fun(): integer, table<integer, TSNode[]>, vim.treesitter.query.TSMetadata, TSTree): pattern id, match, metadata, tree
---@return (fun(): integer, table<integer, TSNode[]>, vim.treesitter.query.TSMetadata, TSTree): pattern-id, match, metadata, tree
function Query:iter_matches(node, source, start, stop, opts)
opts = opts or {}

View File

@@ -5447,7 +5447,7 @@ local options = {
combine it with "tab:", for example: >vim
set listchars+=tab:>-,lead:.
<
*lcs-leadmultispace*
*lcs-leadmultispace* *indent-guides*
leadmultispace:c...
Like the |lcs-multispace| value, but for leading
spaces only. Also overrides |lcs-lead| for leading
@@ -5458,6 +5458,13 @@ local options = {
<
Where "XXX" denotes the first non-blank characters in
the line.
Combined with |lcs-leadtab|, this can be used to show
"indentation guides" (vertical lines).
For example, with 'shiftwidth' 2: >vim
set list listchars=leadtab:\ \ │,tab:\ \ │,leadmultispace:\ \ │
< For richer rendering (per-level colors, treesitter-aware
scopes, etc.) use a third-party plugin.
*lcs-leadtab*
leadtab:xy[z]
Like |lcs-tab|, but only for leading tabs. When