mega.cmdparse
August 30, 2025 · View on GitHub
A Python argparse-inspired command mode parser for Neovim.
This library is for people who have to define a command like :Foo and it
takes arguments. This library supports customizable arguments and subcommands
so no more needing to define :FooToggle :FooOpen /path/to/file.txt
:FooClose 2, etc commands. You can easily create one command with completely
separate interfaces like :Foo toggle, :Foo open /path/to/file.txt, and
:Foo close --buffer=2.
There's a lot of features. Whatever you need, mega.cmdparse has you covered!
| Build Status | |
| License | |
| Social |
⚡️ Features
- Position, flag, and named arguments. e.g.
foo bar --fizz=buzz -z - Parser / Parameter paradigm
- Builtin auto-complete
- Auto-generated
--helpparameter - Auto-complete any argument in any cursor position
- Auto-complete a flag argument's values
- Non-standard arguments. e.g.
--foo,++bar,-f, etc - Automated value type conversions
- Multi-argument-per-parameter
- Multi-parameters
- Nested subparsers
- Merged flag support. e.g.
-fbtflags parse as{f=true, b=true, t=true}. - Automated parameter validation. e.g. "foo parameter requires 2 arguments, got 1", etc.
- Unicode support
- Supports required / optional arguments
- 2 supported flag formats.
--foo barand--foo=bar - Dynamic parsers (supports plugin-like interfaces like Telescope and more)
- This plugin is defer-evaluated (<1 ms plugin start-up time)
⭐ Demos
Builtin Auto-Complete
https://github.com/user-attachments/assets/49cb410c-b49d-435a-ac8b-5cb46999f7f7
Auto-Generated --help Parameter
If you want details on what to type, add -h or --help to any command and
the automated help message will show.
https://github.com/user-attachments/assets/da01f274-87d5-4768-8719-54a72fb5627c
A summarized version may also show if you make a mistake in your input.
https://github.com/user-attachments/assets/9be524e3-fccd-4cd0-8626-a2dc6c096518
Automated Parameter Validation
Parameters know how many arguments they need and in what order.
https://github.com/user-attachments/assets/29f57c5c-c977-47a0-82c8-423250e26688
2 Supported Flag Formats
--foo bar and --foo=bar are both supported styles.
https://github.com/user-attachments/assets/a4467230-c1c6-49a0-8405-5c4be40dcba2
🌟 Examples
Simple Hello, World!
Hello, World! Parser
local cmdparse = require("mega.cmdparse")
local parser = cmdparse.ParameterParser.new({ name = "Test", help = "Hello, World!"})
parser:set_execute(function(data) print("Hello, World!") end)
cmdparse.create_user_command(parser)
Run: :Test
Vim Visual Mode
Print the selected lines
local cmdparse = require("mega.cmdparse")
local parser = cmdparse.ParameterParser.new({ name = "Test", help = "Test visual selection." })
parser:set_execute(
function(data)
print(string.format('Start: "%s"', data.options.line1))
print(string.format('End: "%s"', data.options.line2))
end
)
cmdparse.create_user_command(parser)
- Run:
'<,'>Test
Automated Value Type Conversions
Automated value type conversions
local cmdparse = require("mega.cmdparse")
local parser = cmdparse.ParameterParser.new({ name = "Test", help = "Automated value type conversions" })
parser:add_parameter({ name = "thing", type = tonumber, help = "Test." })
parser:add_parameter({ name = "another", type = "number", help = "Test." })
parser:set_execute(function(data)
print(string.format('Thing: "%d"', data.namespace.thing + 10))
print(string.format('Another: "%d"', data.namespace.another + 10))
end)
cmdparse.create_user_command(parser)
Run: :Test 10 -123
Multi-Argument-Per-Parameter
Multi-argument-per-parameter
In this example, the "thing" parameter takes exactly 2 arguments, indicated
by nargs=2.
nargs="*"= 0-or-morenargs="+"= 1-or-more
local cmdparse = require("mega.cmdparse")
local parser = cmdparse.ParameterParser.new({ name = "Test", help = "Multi-argument-per-parameter" })
parser:add_parameter({ name = "thing", nargs=2, type=tonumber, help = "Test." })
parser:set_execute(function(data)
local values = data.namespace.thing
local first = values[1]
local second = values[2]
local total = first + second
print(string.format('Thing: "%f + %f = %f"', first, second, total))
end)
cmdparse.create_user_command(parser)
Run: :Test 123 54545.1231
Position, Flag, And Named Arguments
Position, flag, and named arguments. e.g. `foo bar --fizz=buzz -dbz`
local cmdparse = require("mega.cmdparse")
local parser = cmdparse.ParameterParser.new({ name = "Test", help = "Position, flag, and named arguments support." })
parser:add_parameter({ name = "items", nargs="*", help="non-flag arguments." })
parser:add_parameter({ name = "--fizz", help="A word." })
parser:add_parameter({ name = "-d", action="store_true", help="Delta single-word." })
parser:add_parameter({ names = {"--beta", "-b"}, action="store_true", help="Beta single-word." })
parser:add_parameter({ name = "-z", action="store_true", help="Zulu single-word." })
parser:set_execute(function(data)
local namespace = data.namespace
local items = namespace.items
print(vim.fn.join(vim.fn.sort(items), ", "))
print(string.format('-d: %s, -b: %s, -z: %s', namespace.d, namespace.beta, namespace.z))
end)
cmdparse.create_user_command(parser)
Run: :Test foo bar --fizz=buzz -dbz
Supports Required / Optional Arguments
Supports Required / Optional Arguments
By default, flag / named arguments like --foo or --foo=bar are optional.
By default, position arguments like thing are required.
But you can explicitly make flag / named arguments required or position
arguments optional, using required=true and required=false.
local cmdparse = require("mega.cmdparse")
local parser = cmdparse.ParameterParser.new({ name = "Test", help = "Unicode Parameters." })
parser:add_parameter({ name = "required_thing", help = "Test." })
parser:add_parameter({ name = "optional_thing", required=false, help = "Test." })
parser:add_parameter({ name = "--optional-flag", help = "Test." })
parser:add_parameter({ name = "--required-flag", required=true, help = "Test." })
parser:set_execute(function(data)
print(vim.inspect(data.namespace))
end)
cmdparse.create_user_command(parser)
Run: :Test foo bar --required-flag=aaa
Nested Subparsers
Nested Subparsers
local cmdparse = require("mega.cmdparse")
local parser = cmdparse.ParameterParser.new({ name = "Test", help = "Nested Subparsers" })
local top_subparsers = parser:add_subparsers({ destination = "commands" })
local view = top_subparsers:add_parser({ name = "view", help = "View some data." })
local view_subparsers = view:add_subparsers({ destination = "view_commands" })
local log = view_subparsers:add_parser({ name = "log" })
log:add_parameter({ name = "path", help = "Open a log path file." })
log:add_parameter({ name = "--relative", action="store_true", help = "A relative log path." })
log:set_execute(function(data)
print(string.format('Opening "%s" log path.', data.namespace.path))
end)
cmdparse.create_user_command(parser)
Run: :Test view log /some/path.txt
Static Auto-Complete Values
Static Auto-Complete Values
local cmdparse = require("mega.cmdparse")
local parser = cmdparse.ParameterParser.new({ name = "Test", help = "Static Auto-Complete Values."})
parser:add_parameter({ name = "thing", choices={ "aaa", "apple", "apply" }, help="Test word."})
parser:set_execute(function(data) print(data.namespace.thing) end)
cmdparse.create_user_command(parser)
Run: :Test apply
Dynamic Parsers
Dynamic Auto-Complete Values
local cmdparse = require("mega.cmdparse")
local parser = cmdparse.ParameterParser.new({ name = "Test", help = "Dynamic Auto-Complete Values."})
local choices = function(data)
local output = {}
local value = data.value or 0
for index = 1, 5 do
table.insert(output, "text " .. tostring(value + index))
end
return output
end
parser:add_parameter({ name = "--thing", choices=choices, help="Test word."})
parser:set_execute(
function(data) print(data.namespace.thing) end,
)
cmdparse.create_user_command(parser)
Run: :Test --thing=4
Remainder Values
Stop parsing on when a REMAINDER token is found
Sometimes you may need a parser to stop parsing text mid-way, this can be done using
nargs=cmdparse.REMAINDER. This inspired by Python argparse's functionality.
local cmdparse = require("mega.cmdparse")
local parser = cmdparse.ParameterParser.new({ help = "Test" })
parser:add_parameter({ name = "paths", nargs="*", help = "Some paths on-disk or something." })
parser:add_parameter({ name = "remainder", nargs = cmdparse.REMAINDER, help = "Etc." })
print(vim.inspect(parser:parse_arguments("something blah here")))
-- Result: { paths = {"something", "blah", "here"}, remainder = "" }
print(vim.inspect(parser:parse_arguments("something blah here -- more stuff")))
-- Result: { paths = {"something", "blah", "here"}, remainder = "more stuff" }
This is most useful when your arguments need to go to 2+ separate parsers or some other special situation.
Dynamic Plug-ins
Dynamic Plug-ins
Subparsers are not static, you can create dynamic subparsers with dynamic names
and dynamic contents if you'd like. This makes mega.cmdparse great for
writing a plugin that supports CLI hooks, like how
telescope.nvim behaves.
---@return mega.cmdparse.ParameterParser # Some example parser.
local function make_example_plugin_a()
local parser = cmdparse.ParameterParser.new({ name = "plugin-a", help = "Test plugin-a." })
parser:add_parameter({ name = "--foo", action="store_true", help="A required value for plugin-a." })
parser:set_execute(function(data)
print("Running plugin-a")
end)
return parser
end
---@return mega.cmdparse.ParameterParser # Another example parser.
local function make_example_plugin_b()
local parser = cmdparse.ParameterParser.new({ name = "plugin-b", help = "Test plugin-b." })
parser:add_parameter({ name = "foo", help="A required value for plugin-b." })
parser:set_execute(function(data)
print("Running plugin-b")
end)
return parser
end
---@return mega.cmdparse.ParameterParser # A parser whose auto-complete and executer uses auto-found plugins.
local function create_parser()
local parser = cmdparse.ParameterParser.new({ name = "Test", help = "Test." })
local subparsers = parser:add_subparsers({ destination = "commands", help = "All main commands." })
-- NOTE: These functions would normally be "automatically discovered"
-- somehow, not hard-coded. But the purpose is the same, it's to add some
-- name and callable function so we can refer to it later in the parser.
--
subparsers:add_parser(make_example_plugin_a())
subparsers:add_parser(make_example_plugin_b())
return parser
end
local parser = create_parser()
cmdparse.create_user_command(parser)
Run: Test plugin-a --foo
Run: Test plugin-b 12345
Customizable / Automated --help Flag
Customizable / Automated `--help` flag
The help message is automatically generated but you can influence the output
a bit, using value_hint.
For example this code below:
local cmdparse = require("mega.cmdparse")
local parser = cmdparse.ParameterParser.new({ name = "Test", help = "Position, flag, and named arguments support." })
parser:add_parameter({ name = "items", nargs="*", help="non-flag arguments." })
parser:add_parameter({ name = "--fizz", nargs="+", help="A word." })
parser:add_parameter({ name = "-b", action="store_true", help="Zulu single-word." })
parser:set_execute(function(data)
print("Ran it")
end)
cmdparse.create_user_command(parser)
Creates this help message:
Usage: Test [ITEMS ...] [--fizz FIZZ [FIZZ ...]] [-b] [--help]
Positional Arguments:
[ITEMS ...] non-flag arguments.
Options:
--fizz FIZZ [FIZZ ...] A word.
-b Zulu single-word.
--help -h Show this help message and exit.
If you don't like the auto-generated value text, you can change it. For example
parser:add_parameter({ name = "--fizz", nargs="+", help="A word." })
can be changed to
parser:add_parameter({ name = "--fizz", nargs="+", value_hint="/path/to/file.txt", help="A word." })
And the help message becomes
--fizz /path/to/file.txt [/path/to/file.txt ...] A word.
Non-Standard Arguments
Non-standard arguments. e.g. `--foo`, `++bar`, `-f`, etc
The difference between a position parameter and a flag / named parameter is
just the prefix. Position parameters must start with alphanumeric text. But
this means that anything else can be a flag. e.g. ++foo is a valid flag name
and so is --bar. It's all allowed.
local cmdparse = require("mega.cmdparse")
local parser = cmdparse.ParameterParser.new({ name = "Test", help = "Position, flag, and named arguments support." })
parser:add_parameter({ name = "--fizz", action="store_true", help="A word." })
parser:add_parameter({ name = "++buzz", help="Some argument." })
parser:set_execute(function(data)
print(string.format('--fizz: %s', data.namespace.fizz))
print(string.format('++buzz: "%s"', data.namespace.buzz))
end)
cmdparse.create_user_command(parser)
Run: :Test --fizz ++buzz "some text here"
Unicode Parameters
Unicode Parameters
You can use unicode for position / flag / named parameters if you want to.
local cmdparse = require("mega.cmdparse")
local parser = cmdparse.ParameterParser.new({ name = "Test", help = "Unicode Parameters." })
parser:add_parameter({ name = "𝒻ⓡ𝓊𝒾🅃🆂", nargs="+", help = "Test." })
parser:add_parameter({ name = "--😊", help = "Test." })
parser:set_execute(function(data)
print(vim.fn.join(data.namespace["𝒻ⓡ𝓊𝒾🅃🆂"], ", "))
print(data.namespace["--😊"])
end)
cmdparse.create_user_command(parser)
Run: :Test apple 🄱🄰🄽🄰🄽🄰 --😊=ttt
🧰 API
Most people will use mega.cmdparse to create Neovim user commands but if you
want to use the Lua API directly, here are the most common cases.
get_completions
You can query the available auto-complete values whenever you want.
Expand to show more
local cmdparse = require("mega.cmdparse")
local parser = cmdparse.ParameterParser.new(
{ name = "Test", help = "Unicode Parameters." }
)
parser:add_parameter(
{ name = "--foo", choices = {"apple", "apply", "banana"}, help = "Test." }
)
print(vim.inspect(parser:get_completion("-")))
print(vim.inspect(parser:get_completion("--")))
print(vim.inspect(parser:get_completion("--f")))
print(vim.inspect(parser:get_completion("--fo")))
-- Result: {"--foo="}
print(vim.inspect(parser:get_completion("--foo=")))
-- Result: { "--foo=apple", "--foo=apply", "--foo=banana" }
print(vim.inspect(parser:get_completion("--foo=appl")))
-- Result: { "--foo=apple", "--foo=apply" }
print(vim.inspect(parser:get_completion("--foo appl")))
-- Result: { "apple", "apply" }
This also supports a cursor column position (starting at 1-or-more).
print(vim.inspect(parser:get_completion("--foo=appl", 4)))
-- Result: { "--foo=" }
parse_arguments
You can compute the final values with parse_arguments.
Expand to show more
local cmdparse = require("mega.cmdparse")
local parser = cmdparse.ParameterParser.new({ name = "Test", help = "Unicode Parameters." })
parser:add_parameter({ name = "--foo", choices = {"apple", "apply", "banana"}, help = "Test." })
print(vim.inspect(parser:parse_arguments("--foo=apple")))
-- Result: { foo = "apple" }
📋 Installation
{
"ColinKennedy/mega.cmdparse",
dependencies = { "ColinKennedy/mega.logging" },
version = "v1.*",
}
⚙ Configuration
(These are default values)
{
"ColinKennedy/mega.cmdparse",
config = function()
vim.g.cmdparse_configuration = {
cmdparse = {
auto_complete = { display = { help_flag = true } }, -- If `false`, don't show the `--help` flag anywhere.
},
logging = {
level = "info", -- "trace" | "debug" | "info" | "warning" | "error" | "fatal"
use_console = false, -- Print to Neovim as the user is working
use_file = false, -- Write to-disk as loggers run
},
}
end
}
✅ Tests
Initialization
Run this line once before calling any busted command
eval $(luarocks path --lua-version 5.1 --bin)
Running
Run all tests
luarocks test --test-type busted
# Or manually
busted .
# Or with Make
make test
Run test based on tags
busted . --tags=simple
👂 Tracking Updates
See doc/news.txt for updates.
You can watch this plugin for changes by adding this URL to your RSS feed:
https://github.com/ColinKennedy/mega.cmdparse/commits/main/doc/news.txt.atom