commit 612b2e78508757be5b7a3ef0e9bf1ed0f0b27cd2
parent 7e09fedf43fa191dcebbe131928aa8df3930888d
Author: Rob Pilling <robpilling@gmail.com>
Date: Wed, 26 Nov 2025 05:12:39 +0000
feat(api): nvim_get_commands returns function fields #36415
Problem:
nvim_get_commands does not return callbacks defined for
"preview", "complete", or the command itself.
Solution:
- Return Lua function as "callback" field in a Lua context.
- Return "preview" function in a Lua context.
- BREAKING: Return "complete" as a function instead of a boolean.
Diffstat:
3 files changed, 69 insertions(+), 16 deletions(-)
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
@@ -52,6 +52,9 @@ These changes may require adaptations in your config or plugins.
API
• Decoration provider has `on_range()` callback.
+• nvim_get_commands()'s dictionaries' `complete` member is now a Lua function,
+ if that is how the command was created. `preview` and `callback`
+ have also been added, if they were specified in `nvim_create_user_command()`
BUILD
diff --git a/src/nvim/usercmd.c b/src/nvim/usercmd.c
@@ -1777,7 +1777,7 @@ Dict commands_array(buf_T *buf, Arena *arena)
Dict rv = arena_dict(arena, (size_t)gap->ga_len);
for (int i = 0; i < gap->ga_len; i++) {
char arg[2] = { 0, 0 };
- Dict d = arena_dict(arena, 14);
+ Dict d = arena_dict(arena, 16);
ucmd_T *cmd = USER_CMD_GA(gap, i);
PUT_C(d, "name", CSTR_AS_OBJ(cmd->uc_name));
@@ -1787,7 +1787,14 @@ Dict commands_array(buf_T *buf, Arena *arena)
PUT_C(d, "bar", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_TRLBAR)));
PUT_C(d, "register", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_REGSTR)));
PUT_C(d, "keepscript", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_KEEPSCRIPT)));
- PUT_C(d, "preview", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_PREVIEW)));
+
+ if (cmd->uc_preview_luaref != LUA_NOREF) {
+ PUT_C(d, "preview", LUAREF_OBJ(api_new_luaref(cmd->uc_preview_luaref)));
+ }
+
+ if (cmd->uc_luaref != LUA_NOREF) {
+ PUT_C(d, "callback", LUAREF_OBJ(api_new_luaref(cmd->uc_luaref)));
+ }
switch (cmd->uc_argt & (EX_EXTRA | EX_NOSPC | EX_NEEDARG)) {
case 0:
@@ -1803,9 +1810,14 @@ Dict commands_array(buf_T *buf, Arena *arena)
}
PUT_C(d, "nargs", CSTR_TO_ARENA_OBJ(arena, arg));
- char *cmd_compl = get_command_complete(cmd->uc_compl);
- PUT_C(d, "complete", (cmd_compl == NULL
- ? NIL : CSTR_AS_OBJ(cmd_compl)));
+ if (cmd->uc_compl_luaref != LUA_NOREF) {
+ PUT_C(d, "complete", LUAREF_OBJ(api_new_luaref(cmd->uc_compl_luaref)));
+ } else {
+ char *cmd_compl = get_command_complete(cmd->uc_compl);
+
+ PUT_C(d, "complete", (cmd_compl == NULL
+ ? NIL : CSTR_AS_OBJ(cmd_compl)));
+ }
PUT_C(d, "complete_arg", cmd->uc_compl_arg == NULL
? NIL : CSTR_AS_OBJ(cmd->uc_compl_arg));
diff --git a/test/functional/api/command_spec.lua b/test/functional/api/command_spec.lua
@@ -25,7 +25,6 @@ describe('nvim_get_commands', function()
definition = 'echo "Hello World"',
name = 'Hello',
nargs = '1',
- preview = false,
range = NIL,
register = false,
keepscript = false,
@@ -41,7 +40,6 @@ describe('nvim_get_commands', function()
definition = 'pwd',
name = 'Pwd',
nargs = '?',
- preview = false,
range = NIL,
register = false,
keepscript = false,
@@ -96,7 +94,6 @@ describe('nvim_get_commands', function()
definition = 'pwd <args>',
name = 'TestCmd',
nargs = '1',
- preview = false,
range = '10',
register = false,
keepscript = false,
@@ -112,7 +109,6 @@ describe('nvim_get_commands', function()
definition = '!finger <args>',
name = 'Finger',
nargs = '+',
- preview = false,
range = NIL,
register = false,
keepscript = false,
@@ -128,7 +124,6 @@ describe('nvim_get_commands', function()
definition = 'call \128\253R2_foo(<q-args>)',
name = 'Cmd2',
nargs = '*',
- preview = false,
range = NIL,
register = false,
keepscript = false,
@@ -144,7 +139,6 @@ describe('nvim_get_commands', function()
definition = 'call \128\253R3_ohyeah()',
name = 'Cmd3',
nargs = '0',
- preview = false,
range = NIL,
register = false,
keepscript = false,
@@ -160,7 +154,6 @@ describe('nvim_get_commands', function()
definition = 'call \128\253R4_just_great()',
name = 'Cmd4',
nargs = '0',
- preview = false,
range = NIL,
register = true,
keepscript = false,
@@ -189,11 +182,56 @@ describe('nvim_get_commands', function()
endfunction
command -register Cmd4 call <SID>just_great()
]])
+ source([[
+ function! s:cpt() abort
+ return 1
+ endfunction
+ command -nargs=1 -complete=customlist,s:cpt CmdWithPreview
+ ]])
+ source([[
+ lua << EOF
+ vim.api.nvim_create_user_command(
+ 'CmdWithPreviewLua',
+ function() end,
+ {
+ nargs = 1,
+ complete = function() return 3 end,
+ preview = function() return 4 end,
+ }
+ )
+ EOF
+ ]])
-- TODO(justinmk): Order is stable but undefined. Sort before return?
- eq(
- { Cmd2 = cmd2, Cmd3 = cmd3, Cmd4 = cmd4, Finger = cmd1, TestCmd = cmd0 },
- api.nvim_get_commands({ builtin = false })
- )
+ local commands = api.nvim_get_commands({ builtin = false })
+ local cmd_with_preview = commands.CmdWithPreview
+ commands.CmdWithPreview = nil
+ local cmd_with_preview_lua = commands.CmdWithPreviewLua
+ commands.CmdWithPreviewLua = nil
+ eq({ Cmd2 = cmd2, Cmd3 = cmd3, Cmd4 = cmd4, Finger = cmd1, TestCmd = cmd0 }, commands)
+
+ eq(cmd_with_preview.complete, 'customlist')
+ eq(cmd_with_preview.preview, nil)
+
+ -- user data (NIL), because these are passed through RPC:
+ eq(cmd_with_preview_lua.complete, NIL)
+ eq(cmd_with_preview_lua.preview, NIL)
+ end)
+
+ it('gets callbacks defined as Lua functions', function()
+ exec_lua [[
+ vim.api.nvim_create_user_command('CommandWithLuaCallback', function(opts)
+ return 3
+ end, {
+ nargs = 1,
+ preview = function() return 4 end,
+ complete = function() return 5 end,
+ })
+
+ local cmd = vim.api.nvim_get_commands({})["CommandWithLuaCallback"]
+ assert(cmd["callback"]() == 3)
+ assert(cmd["preview"]() == 4)
+ assert(cmd["complete"]() == 5)
+ ]]
end)
end)