Creating Neovim User-Commands in Lua

The Neovim Lua API provides several commands for creating user-commands, depending on whether the goal is to create a global command or a buffer-local command:.

The call signature for creating a global user-command is:

vim.api.nvim_create_user_command(
{name}, -- string
{command}, -- string or Lua function
{attributes} -- table
)

Similarly, buffer-local user commands can be created using:

vim.api.nvim_buf_create_user_command(
{buffer number}, -- integer
{name}, -- string
{command}, -- string or Lua function
{attributes} -- table
)

where the buffer number specifies the buffer to attach the command to, or 0 to attach to the current buffer

Other than the buffer number, these commands require the same positional arguments:

Name

User-commands must start with an upper-case letter. By convention camel-case is often used, though this is not required.

Command

The command can be a string containing Vim commands, or a Lua function that is called when the user command is invoked.

Command arguments

When the command is defined as a Lua function, each time the function is called a table of context information is passed as an argument to the function. The most important information is:

Key Description
name a string with the command name
args the string of arguments passed to the user command
fargs a list-like table containing each of the arguments passed to the command
range the number of items in the command range: 0, 1, or 2
line1 the line number of the start of the range
line2 the line number of the end of the range
count any count supplied
bang true if the command was executed with a ! modifier, else false

Attributes

When user-commands are created they are passed an attributes table that allows various aspects of the command to be configured. The most common attributes are:

nargs [int|string]

By default user-commands take no arguments. Allow command arguments by setting the nargs argument to one of:

nargs Meaning
0 No arguments are allowed (default)
1 Exactly 1 argument is required
n Exactly [n] arguments are required
? 0 or 1 arguments are allowed
* 0 or more arguments are allowed
+ 1 or more arguments are required

When arguments are allowed, they should be passed after the command, with each argument separated by whitespace.

desc [str]

Set the desc attribute to a string that provides a brief description of the command.

bang [bool]

User-commands can accept a bang modifier to differentiate command behavior between:

:Command

and

:Command!

Enable bang detection by setting the bang attribute to true. When bang is true, the bang attribute will indicate whether or not the bang modifier was used when the command was invoked.

As a simple example, consider the following user-command:

local callback = function(args)
local lines = {}

-- detect if the user includes a bang
if args.bang == true then
table.insert(lines, "bang was used")
else
table.insert(lines, "bang was not used")
end

vim.api.nvim_buf_set_lines(0, 0, -1, true, lines)
end

vim.api.nvim_create_user_command("Bang", callback, {
nargs = 0,
desc = "User Command with bang",
-- enable bang detection
bang = true,
})

which detects whether or not the bang modifier is present in the command invocation, and updates the buffer text accordingly.

Here is the result When the command is called without a bang modifier:

Without Bang
bang·was·not·used
NORMAL
Top
1:1
 

For comparison, here is the result when the call includes a bang modifier:

With Bang
bang·was·used
NORMAL
Top
1:1
 

complete [string|function]

When a command expects one or more arguments, argument completion can be enabled by setting the completion to one of the available values. The following table provides a sample of the completion providers:

Value Description
arglist file names from the argument list
augroup autocmd groups
buffer buffer names
color color schemes
command Ex commands and arguments
dir directory names
environment environment variable names
event autocommand events
file file and directory names
file_in_path file and directory names in 'path'
filetype filetype names
highlight highlight groups
lua Lua expression
mapping mapping name
shellcmd Shell command
var user variables

Neovim additionally supports custom completion functions, by setting the value for the complete key to a Lua function, as follows:

vim.api.nvim_create_user_command("Command", callback, {
nargs = 1,
desc = "User Command with completion",
complete = function(ArgLead, CmdLine, CursorPos)
return { "one", "two", "three" }
end,
})

The arguments passed to the Lua function are:

Argument Description
ArgLead the leading portion of the argument currently being completed on
CmdLine the entire command line
CursorPos the position of the cursor

The function should return a list-like table containing all completion candidates. Note that the complete function does not need to implement filtering; Neovim will automatically filter the returned list.

count [bool]

By default, user-defined commands do not accept a count. Set the count attribute to true to support optional counts.

When count is true, a count may be passed to the user-function in either of two ways:

:[count]Command

:Command [count]

Count information is passed to the Lua function as an integer value associated with the count key.

As a simple example, consider the following user-command:

local callback = function(args)
local lines = {}

for i = 0, args.count do
table.insert(lines, tostring(i))
end

vim.api.nvim_buf_set_lines(0, 0, -1, true, lines)
end

vim.api.nvim_create_user_command("Count", callback, {
nargs = 0,
desc = "User Command with count",
-- enable count detection
count = true,
})

This command takes a count, then replaces the buffer contents with lines that count up to the specified number. As a first example the count is passed as an argument to the command:

Count as Argument
0 
1
2
3
NORMAL
Top
1:1
 

As a second example, the count is passed as a prefix to the command:

Count as Prefix
0 
1
2
NORMAL
Top
1:1
 

Note that because passing the count as a prefix is identical to passing a single-list range, count and range are mutually-exclusive; user commands can accept one, the other, or neither.

range [bool]

By default, user-defined commands do not accept a range. Set the range attribute to true to support optional ranges. As mentioned in the count section, count and range are mutually-exclusive.

When range is true, information about the range is passed to the Lua function using a combination of the range, line1, and line2 keys. range will be assigned an integer value of 0, 1, or 2, indicating the type of range that was specified:

Value range line1 line2
0 None current line current line
1 Single Line line index line index
2 Multi-line range start range end

As a simple example, consider the following user-command:

local decorate = function(lines, range_start, range_end, text)
for i = range_start, range_end do
lines[i] = lines[i] .. " " .. text
end
return lines
end

local callback = function(args)
local lines = vim.api.nvim_buf_get_lines(0, 0, -1, true)

if args.range == 0 then
-- no range given
-- decorate all lines (per Vim standard behavior)
lines = decorate(lines, 1, #lines, args.range)
else
-- single or multi-line range
lines = decorate(lines, args.line1, args.line2, args.range)
end

vim.api.nvim_buf_set_lines(0, 0, -1, true, lines)
end

vim.api.nvim_create_user_command("Range", callback, {
nargs = 0,
desc = "User Command with Range",
-- enable range detection
range = true,
})

This command takes an optional range, then:

  1. if no range is specified, add " 0" to each line in the buffer,

  2. if a single-line range is specified, add " 1" to the specified line,

  3. if a multi-line range is specified, then add " 2" to each line in the specified range.

We will execute this command using the following buffer:

Initial Conditions
1 
2
3
4
5
6
COMMAND
Top
1:1
:Range

First, although ranges are supported they are not required. This command detects this condition and decorates the entire buffer:

No Range
1·0
2·0
3·0
4·0
5·0
6·0
NORMAL
Top
1:1
 

Next, we specify a single-line range:

Single Line Range
1·0
2·0·1
3·0
4·0
5·0
6·0
NORMAL
Top
1:1
 

Finally, we specify a multi-line range:

Multi-Line Range
1·0
2·0·1
3·0
4·0·2
5·0·2
6·0
NORMAL
Top
1:1
 

As with any range specifications, we could have used visual selection, shortcuts such as %, ., etc.