neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

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:
Mruntime/doc/news.txt | 3+++
Msrc/nvim/usercmd.c | 22+++++++++++++++++-----
Mtest/functional/api/command_spec.lua | 60+++++++++++++++++++++++++++++++++++++++++++++++++-----------
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)