From 1787965d77ae81510357683becb51bb2e9b557fd Mon Sep 17 00:00:00 2001 From: glepnir Date: Wed, 6 May 2026 18:36:39 +0800 Subject: [PATCH] feat(api): nvim_get_commands returns desc #39623 Problem: Can't get a command's description from nvim_get_commands when cmd is string. Solution: Returns "desc" field in nvim_get_commands. `definition` is now empty when cmd is function type. --- runtime/doc/news.txt | 1 + runtime/lua/vim/_meta/api_keysets_extra.lua | 1 + src/nvim/api/command.c | 16 +++++--- src/nvim/usercmd.c | 9 ++++- src/nvim/usercmd.h | 1 + test/functional/api/command_spec.lua | 41 +++++++++++++++++++++ 6 files changed, 61 insertions(+), 8 deletions(-) diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 0c2de7730f..3496a0fe14 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -118,6 +118,7 @@ API shape when an unfocused float is on top of the cursor. • |nvim_echo()| distinguishes zero percent from omitted percent for Progress events. +• |nvim_create_user_command()| accepts `desc` for Vimscript commands. BUILD diff --git a/runtime/lua/vim/_meta/api_keysets_extra.lua b/runtime/lua/vim/_meta/api_keysets_extra.lua index 3224c33b76..7ba00bed31 100644 --- a/runtime/lua/vim/_meta/api_keysets_extra.lua +++ b/runtime/lua/vim/_meta/api_keysets_extra.lua @@ -130,6 +130,7 @@ error('Cannot require a meta file') --- @field range? string --- @field addr? string --- @field callback? function +--- @field desc? string --- @class vim.api.keyset.hl_info.base --- @field reverse? true diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c index 22b163a69b..cd93856e59 100644 --- a/src/nvim/api/command.c +++ b/src/nvim/api/command.c @@ -1257,14 +1257,18 @@ void create_user_command(uint64_t channel_id, String name, Union(String, LuaRef) opts->preview.data.luaref = LUA_NOREF; } + const char *desc = NULL; + if (HAS_KEY(opts, user_command, desc)) { + VALIDATE_T("desc", kObjectTypeString, opts->desc.type, { + goto err; + }); + desc = opts->desc.data.string.data; + } + switch (cmd.type) { case kObjectTypeLuaRef: luaref = api_new_luaref(cmd.data.luaref); - if (opts->desc.type == kObjectTypeString) { - rep = opts->desc.data.string.data; - } else { - rep = ""; - } + rep = ""; break; case kObjectTypeString: rep = cmd.data.string.data; @@ -1277,7 +1281,7 @@ void create_user_command(uint64_t channel_id, String name, Union(String, LuaRef) WITH_SCRIPT_CONTEXT(channel_id, { if (uc_add_command(name.data, name.size, rep, argt, def, flags, context, compl_arg, - compl_luaref, preview_luaref, addr_type_arg, luaref, force) != OK) { + compl_luaref, preview_luaref, addr_type_arg, luaref, desc, force) != OK) { api_set_error(err, kErrorTypeException, "Failed to create user command"); // Do not goto err, since uc_add_command now owns luaref, compl_luaref, preview_luaref, // and compl_arg diff --git a/src/nvim/usercmd.c b/src/nvim/usercmd.c index b9d3ef30e8..d7d40e7dfc 100644 --- a/src/nvim/usercmd.c +++ b/src/nvim/usercmd.c @@ -890,7 +890,8 @@ char *uc_validate_name(char *name) /// @return OK if the command is created, FAIL otherwise. int uc_add_command(char *name, size_t name_len, const char *rep, uint32_t argt, int64_t def, int flags, int context, char *compl_arg, LuaRef compl_luaref, - LuaRef preview_luaref, cmd_addr_T addr_type, LuaRef luaref, bool force) + LuaRef preview_luaref, cmd_addr_T addr_type, LuaRef luaref, const char *desc, + bool force) FUNC_ATTR_NONNULL_ARG(1, 3) { ucmd_T *cmd = NULL; @@ -942,6 +943,7 @@ int uc_add_command(char *name, size_t name_len, const char *rep, uint32_t argt, XFREE_CLEAR(cmd->uc_rep); XFREE_CLEAR(cmd->uc_compl_arg); + XFREE_CLEAR(cmd->uc_desc); NLUA_CLEAR_REF(cmd->uc_luaref); NLUA_CLEAR_REF(cmd->uc_compl_luaref); NLUA_CLEAR_REF(cmd->uc_preview_luaref); @@ -969,6 +971,7 @@ int uc_add_command(char *name, size_t name_len, const char *rep, uint32_t argt, } cmd->uc_rep = rep_buf; + cmd->uc_desc = (desc != NULL && *desc != NUL) ? xstrdup(desc) : NULL; cmd->uc_argt = argt; cmd->uc_def = def; cmd->uc_compl = context; @@ -1039,7 +1042,7 @@ void ex_command(exarg_T *eap) emsg(_(e_complete_used_without_allowing_arguments)); } else { uc_add_command(name, name_len, p, argt, def, flags, context, compl_arg, LUA_NOREF, LUA_NOREF, - addr_type_arg, LUA_NOREF, eap->forceit); + addr_type_arg, LUA_NOREF, NULL, eap->forceit); return; // success } @@ -1063,6 +1066,7 @@ void free_ucmd(ucmd_T *cmd) xfree(cmd->uc_name); xfree(cmd->uc_rep); xfree(cmd->uc_compl_arg); + xfree(cmd->uc_desc); NLUA_CLEAR_REF(cmd->uc_compl_luaref); NLUA_CLEAR_REF(cmd->uc_luaref); NLUA_CLEAR_REF(cmd->uc_preview_luaref); @@ -1782,6 +1786,7 @@ Dict commands_array(buf_T *buf, Arena *arena) PUT_C(d, "name", CSTR_AS_OBJ(cmd->uc_name)); PUT_C(d, "definition", CSTR_AS_OBJ(cmd->uc_rep)); + PUT_C(d, "desc", CSTR_AS_OBJ(cmd->uc_desc)); PUT_C(d, "script_id", INTEGER_OBJ(cmd->uc_script_ctx.sc_sid)); PUT_C(d, "bang", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_BANG))); PUT_C(d, "bar", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_TRLBAR))); diff --git a/src/nvim/usercmd.h b/src/nvim/usercmd.h index af4fbae157..52b15a9cc3 100644 --- a/src/nvim/usercmd.h +++ b/src/nvim/usercmd.h @@ -22,6 +22,7 @@ typedef struct { LuaRef uc_compl_luaref; ///< Reference to Lua completion function LuaRef uc_preview_luaref; ///< Reference to Lua preview function LuaRef uc_luaref; ///< Reference to Lua function + char *uc_desc; ///< Command description } ucmd_T; enum { UC_BUFFER = 1, }; ///< -buffer: local to current buffer diff --git a/test/functional/api/command_spec.lua b/test/functional/api/command_spec.lua index e4564dcf17..cc328cf2e0 100644 --- a/test/functional/api/command_spec.lua +++ b/test/functional/api/command_spec.lua @@ -23,6 +23,7 @@ describe('nvim_get_commands', function() complete_arg = NIL, count = NIL, definition = 'echo "Hello World"', + desc = '', name = 'Hello', nargs = '1', range = NIL, @@ -38,6 +39,7 @@ describe('nvim_get_commands', function() complete_arg = NIL, count = NIL, definition = 'pwd', + desc = '', name = 'Pwd', nargs = '?', range = NIL, @@ -54,6 +56,15 @@ describe('nvim_get_commands', function() it('validation', function() eq('builtin=true not implemented', pcall_err(api.nvim_get_commands, { builtin = true })) eq("Invalid key: 'foo'", pcall_err(api.nvim_get_commands, { foo = 'blah' })) + matches( + "Invalid 'desc'", + pcall_err( + exec_lua, + [[ + vim.api.nvim_create_user_command('Bad', 'echo "hi"', { desc = 123 }) + ]] + ) + ) end) it('gets global user-defined commands', function() @@ -92,6 +103,7 @@ describe('nvim_get_commands', function() complete_arg = NIL, count = '10', definition = 'pwd ', + desc = '', name = 'TestCmd', nargs = '1', range = '10', @@ -107,6 +119,7 @@ describe('nvim_get_commands', function() complete_arg = 'ListUsers', count = NIL, definition = '!finger ', + desc = '', name = 'Finger', nargs = '+', range = NIL, @@ -122,6 +135,7 @@ describe('nvim_get_commands', function() complete_arg = NIL, count = NIL, definition = 'call \128\253R2_foo()', + desc = '', name = 'Cmd2', nargs = '*', range = NIL, @@ -137,6 +151,7 @@ describe('nvim_get_commands', function() complete_arg = NIL, count = NIL, definition = 'call \128\253R3_ohyeah()', + desc = '', name = 'Cmd3', nargs = '0', range = NIL, @@ -152,6 +167,7 @@ describe('nvim_get_commands', function() complete_arg = NIL, count = NIL, definition = 'call \128\253R4_just_great()', + desc = '', name = 'Cmd4', nargs = '0', range = NIL, @@ -167,6 +183,7 @@ describe('nvim_get_commands', function() complete_arg = 's:cpt', count = NIL, definition = '', + desc = '', name = 'PreviewCmd', nargs = '1', range = NIL, @@ -184,6 +201,7 @@ describe('nvim_get_commands', function() complete_arg = NIL, count = NIL, definition = '', + desc = 'Preview Lua Cmd', name = 'PreviewLuaCmd', nargs = '1', range = NIL, @@ -191,6 +209,22 @@ describe('nvim_get_commands', function() keepscript = false, script_id = -8, -- Lua } + local withDesc = { + addr = vim.NIL, + bang = false, + bar = false, + complete = vim.NIL, + complete_arg = vim.NIL, + count = vim.NIL, + definition = 'echo "hi"', + desc = 'Says hi', + keepscript = false, + name = 'WithDesc', + nargs = '0', + range = vim.NIL, + register = false, + script_id = -8, + } source([[ let s:foo = 1 @@ -230,8 +264,14 @@ describe('nvim_get_commands', function() nargs = 1, complete = function() return 3 end, preview = function() return 4 end, + desc = 'Preview Lua Cmd' } ) + vim.api.nvim_create_user_command( + 'WithDesc', + 'echo "hi"', + { desc = 'Says hi' } + ) EOF ]]) -- TODO(justinmk): Order is stable but undefined. Sort before return? @@ -244,6 +284,7 @@ describe('nvim_get_commands', function() TestCmd = cmd0, PreviewCmd = previewCmd, PreviewLuaCmd = previewLuaCmd, + WithDesc = withDesc, }, commands) end)