mirror of
https://github.com/neovim/neovim.git
synced 2025-10-03 00:18:33 +00:00
feat(api): implement nvim_{add,del}_user_command
Add support for adding and removing custom user commands with the Nvim API.
This commit is contained in:
@@ -1273,6 +1273,63 @@ Object nvim_buf_call(Buffer buffer, LuaRef fun, Error *err)
|
||||
return res;
|
||||
}
|
||||
|
||||
/// Create a new user command |user-commands| in the given buffer.
|
||||
///
|
||||
/// @param buffer Buffer handle, or 0 for current buffer.
|
||||
/// @param[out] err Error details, if any.
|
||||
/// @see nvim_add_user_command
|
||||
void nvim_buf_add_user_command(Buffer buffer, String name, Object command,
|
||||
Dict(user_command) *opts, Error *err)
|
||||
FUNC_API_SINCE(9)
|
||||
{
|
||||
buf_T *target_buf = find_buffer_by_handle(buffer, err);
|
||||
if (ERROR_SET(err)) {
|
||||
return;
|
||||
}
|
||||
|
||||
buf_T *save_curbuf = curbuf;
|
||||
curbuf = target_buf;
|
||||
add_user_command(name, command, opts, UC_BUFFER, err);
|
||||
curbuf = save_curbuf;
|
||||
}
|
||||
|
||||
/// Delete a buffer-local user-defined command.
|
||||
///
|
||||
/// Only commands created with |:command-buffer| or
|
||||
/// |nvim_buf_add_user_command()| can be deleted with this function.
|
||||
///
|
||||
/// @param buffer Buffer handle, or 0 for current buffer.
|
||||
/// @param name Name of the command to delete.
|
||||
/// @param[out] err Error details, if any.
|
||||
void nvim_buf_del_user_command(Buffer buffer, String name, Error *err)
|
||||
FUNC_API_SINCE(9)
|
||||
{
|
||||
garray_T *gap;
|
||||
if (buffer == -1) {
|
||||
gap = &ucmds;
|
||||
} else {
|
||||
buf_T *buf = find_buffer_by_handle(buffer, err);
|
||||
gap = &buf->b_ucmds;
|
||||
}
|
||||
|
||||
for (int i = 0; i < gap->ga_len; i++) {
|
||||
ucmd_T *cmd = USER_CMD_GA(gap, i);
|
||||
if (!STRCMP(name.data, cmd->uc_name)) {
|
||||
free_ucmd(cmd);
|
||||
|
||||
gap->ga_len -= 1;
|
||||
|
||||
if (i < gap->ga_len) {
|
||||
memmove(cmd, cmd + 1, (size_t)(gap->ga_len - i) * sizeof(ucmd_T));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
api_set_error(err, kErrorTypeException, "No such user-defined command: %s", name.data);
|
||||
}
|
||||
|
||||
Dictionary nvim__buf_stats(Buffer buffer, Error *err)
|
||||
{
|
||||
Dictionary rv = ARRAY_DICT_INIT;
|
||||
|
@@ -33,6 +33,18 @@ return {
|
||||
get_commands = {
|
||||
"builtin";
|
||||
};
|
||||
user_command = {
|
||||
"addr";
|
||||
"bang";
|
||||
"bar";
|
||||
"complete";
|
||||
"count";
|
||||
"desc";
|
||||
"force";
|
||||
"nargs";
|
||||
"range";
|
||||
"register";
|
||||
};
|
||||
float_config = {
|
||||
"row";
|
||||
"col";
|
||||
|
@@ -961,6 +961,10 @@ Object copy_object(Object obj)
|
||||
|
||||
case kObjectTypeDictionary:
|
||||
return DICTIONARY_OBJ(copy_dictionary(obj.data.dictionary));
|
||||
|
||||
case kObjectTypeLuaRef:
|
||||
return LUAREF_OBJ(api_new_luaref(obj.data.luaref));
|
||||
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
@@ -1342,3 +1346,184 @@ const char *get_default_stl_hl(win_T *wp)
|
||||
return "StatusLineNC";
|
||||
}
|
||||
}
|
||||
|
||||
void add_user_command(String name, Object command, Dict(user_command) *opts, int flags, Error *err)
|
||||
{
|
||||
uint32_t argt = 0;
|
||||
long def = -1;
|
||||
cmd_addr_T addr_type_arg = ADDR_NONE;
|
||||
int compl = EXPAND_NOTHING;
|
||||
char *compl_arg = NULL;
|
||||
char *rep = NULL;
|
||||
LuaRef luaref = LUA_NOREF;
|
||||
LuaRef compl_luaref = LUA_NOREF;
|
||||
|
||||
if (HAS_KEY(opts->range) && HAS_KEY(opts->count)) {
|
||||
api_set_error(err, kErrorTypeValidation, "'range' and 'count' are mutually exclusive");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (opts->nargs.type == kObjectTypeInteger) {
|
||||
switch (opts->nargs.data.integer) {
|
||||
case 0:
|
||||
// Default value, nothing to do
|
||||
break;
|
||||
case 1:
|
||||
argt |= EX_EXTRA | EX_NOSPC | EX_NEEDARG;
|
||||
break;
|
||||
default:
|
||||
api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
|
||||
goto err;
|
||||
}
|
||||
} else if (opts->nargs.type == kObjectTypeString) {
|
||||
if (opts->nargs.data.string.size > 1) {
|
||||
api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
|
||||
goto err;
|
||||
}
|
||||
|
||||
switch (opts->nargs.data.string.data[0]) {
|
||||
case '*':
|
||||
argt |= EX_EXTRA;
|
||||
break;
|
||||
case '?':
|
||||
argt |= EX_EXTRA | EX_NOSPC;
|
||||
break;
|
||||
case '+':
|
||||
argt |= EX_EXTRA | EX_NEEDARG;
|
||||
break;
|
||||
default:
|
||||
api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
|
||||
goto err;
|
||||
}
|
||||
} else if (HAS_KEY(opts->nargs)) {
|
||||
api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (HAS_KEY(opts->complete) && !argt) {
|
||||
api_set_error(err, kErrorTypeValidation, "'complete' used without 'nargs'");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (opts->range.type == kObjectTypeBoolean) {
|
||||
if (opts->range.data.boolean) {
|
||||
argt |= EX_RANGE;
|
||||
addr_type_arg = ADDR_LINES;
|
||||
}
|
||||
} else if (opts->range.type == kObjectTypeString) {
|
||||
if (opts->range.data.string.data[0] == '%' && opts->range.data.string.size == 1) {
|
||||
argt |= EX_RANGE | EX_DFLALL;
|
||||
addr_type_arg = ADDR_LINES;
|
||||
} else {
|
||||
api_set_error(err, kErrorTypeValidation, "Invalid value for 'range'");
|
||||
goto err;
|
||||
}
|
||||
} else if (opts->range.type == kObjectTypeInteger) {
|
||||
argt |= EX_RANGE | EX_ZEROR;
|
||||
def = opts->range.data.integer;
|
||||
addr_type_arg = ADDR_LINES;
|
||||
} else if (HAS_KEY(opts->range)) {
|
||||
api_set_error(err, kErrorTypeValidation, "Invalid value for 'range'");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (opts->count.type == kObjectTypeBoolean) {
|
||||
if (opts->count.data.boolean) {
|
||||
argt |= EX_COUNT | EX_ZEROR | EX_RANGE;
|
||||
addr_type_arg = ADDR_OTHER;
|
||||
def = 0;
|
||||
}
|
||||
} else if (opts->count.type == kObjectTypeInteger) {
|
||||
argt |= EX_COUNT | EX_ZEROR | EX_RANGE;
|
||||
addr_type_arg = ADDR_OTHER;
|
||||
def = opts->count.data.integer;
|
||||
} else if (HAS_KEY(opts->count)) {
|
||||
api_set_error(err, kErrorTypeValidation, "Invalid value for 'count'");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (opts->addr.type == kObjectTypeString) {
|
||||
if (parse_addr_type_arg((char_u *)opts->addr.data.string.data, (int)opts->addr.data.string.size,
|
||||
&addr_type_arg) != OK) {
|
||||
api_set_error(err, kErrorTypeValidation, "Invalid value for 'addr'");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (addr_type_arg != ADDR_LINES) {
|
||||
argt |= EX_ZEROR;
|
||||
}
|
||||
} else if (HAS_KEY(opts->addr)) {
|
||||
api_set_error(err, kErrorTypeValidation, "Invalid value for 'addr'");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (api_object_to_bool(opts->bang, "bang", false, err)) {
|
||||
argt |= EX_BANG;
|
||||
} else if (ERROR_SET(err)) {
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (api_object_to_bool(opts->bar, "bar", false, err)) {
|
||||
argt |= EX_TRLBAR;
|
||||
} else if (ERROR_SET(err)) {
|
||||
goto err;
|
||||
}
|
||||
|
||||
|
||||
if (api_object_to_bool(opts->register_, "register", false, err)) {
|
||||
argt |= EX_REGSTR;
|
||||
} else if (ERROR_SET(err)) {
|
||||
goto err;
|
||||
}
|
||||
|
||||
bool force = api_object_to_bool(opts->force, "force", false, err);
|
||||
if (ERROR_SET(err)) {
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (opts->complete.type == kObjectTypeLuaRef) {
|
||||
compl = EXPAND_USER_LUA;
|
||||
compl_luaref = api_new_luaref(opts->complete.data.luaref);
|
||||
} else if (opts->complete.type == kObjectTypeString) {
|
||||
if (parse_compl_arg((char_u *)opts->complete.data.string.data,
|
||||
(int)opts->complete.data.string.size, &compl, &argt,
|
||||
(char_u **)&compl_arg) != OK) {
|
||||
api_set_error(err, kErrorTypeValidation, "Invalid value for 'complete'");
|
||||
goto err;
|
||||
}
|
||||
} else if (HAS_KEY(opts->complete)) {
|
||||
api_set_error(err, kErrorTypeValidation, "Invalid value for 'complete'");
|
||||
goto err;
|
||||
}
|
||||
|
||||
switch (command.type) {
|
||||
case kObjectTypeLuaRef:
|
||||
luaref = api_new_luaref(command.data.luaref);
|
||||
if (opts->desc.type == kObjectTypeString) {
|
||||
rep = opts->desc.data.string.data;
|
||||
} else {
|
||||
snprintf((char *)IObuff, IOSIZE, "<Lua function %d>", luaref);
|
||||
rep = (char *)IObuff;
|
||||
}
|
||||
break;
|
||||
case kObjectTypeString:
|
||||
rep = command.data.string.data;
|
||||
break;
|
||||
default:
|
||||
api_set_error(err, kErrorTypeValidation, "'command' must be a string or Lua function");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (uc_add_command((char_u *)name.data, name.size, (char_u *)rep, argt, def, flags,
|
||||
compl, (char_u *)compl_arg, compl_luaref, addr_type_arg, luaref,
|
||||
force) != OK) {
|
||||
api_set_error(err, kErrorTypeException, "Failed to create user command");
|
||||
goto err;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
err:
|
||||
NLUA_CLEAR_REF(luaref);
|
||||
NLUA_CLEAR_REF(compl_luaref);
|
||||
}
|
||||
|
@@ -2363,3 +2363,52 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Create a new user command |user-commands|
|
||||
///
|
||||
/// {name} is the name of the new command. The name must begin with an uppercase letter.
|
||||
///
|
||||
/// {command} is the replacement text or Lua function to execute.
|
||||
///
|
||||
/// Example:
|
||||
/// <pre>
|
||||
/// :call nvim_add_user_command('SayHello', 'echo "Hello world!"', {})
|
||||
/// :SayHello
|
||||
/// Hello world!
|
||||
/// </pre>
|
||||
///
|
||||
/// @param name Name of the new user command. Must begin with an uppercase letter.
|
||||
/// @param command Replacement command to execute when this user command is executed. When called
|
||||
/// from Lua, the command can also be a Lua function. The function is called with a
|
||||
/// single table argument that contains the following keys:
|
||||
/// - args: (string) The args passed to the command, if any |<args>|
|
||||
/// - bang: (boolean) "true" if the command was executed with a ! modifier |<bang>|
|
||||
/// - line1: (number) The starting line of the command range |<line1>|
|
||||
/// - line2: (number) The final line of the command range |<line2>|
|
||||
/// - range: (number) The number of items in the command range: 0, 1, or 2 |<range>|
|
||||
/// - count: (number) Any count supplied |<count>|
|
||||
/// - reg: (string) The optional register, if specified |<reg>|
|
||||
/// - mods: (string) Command modifiers, if any |<mods>|
|
||||
/// @param opts Optional command attributes. See |command-attributes| for more details. To use
|
||||
/// boolean attributes (such as |:command-bang| or |:command-bar|) set the value to
|
||||
/// "true". When using a Lua function for {command} you can also provide a "desc"
|
||||
/// key that will be displayed when listing commands. In addition to the string
|
||||
/// options listed in |:command-complete|, the "complete" key also accepts a Lua
|
||||
/// function which works like the "customlist" completion mode
|
||||
/// |:command-complete-customlist|.
|
||||
/// @param[out] err Error details, if any.
|
||||
void nvim_add_user_command(String name, Object command, Dict(user_command) *opts, Error *err)
|
||||
FUNC_API_SINCE(9)
|
||||
{
|
||||
add_user_command(name, command, opts, 0, err);
|
||||
}
|
||||
|
||||
/// Delete a user-defined command.
|
||||
///
|
||||
/// @param name Name of the command to delete.
|
||||
/// @param[out] err Error details, if any.
|
||||
void nvim_del_user_command(String name, Error *err)
|
||||
FUNC_API_SINCE(9)
|
||||
{
|
||||
nvim_buf_del_user_command(-1, name, err);
|
||||
}
|
||||
|
Reference in New Issue
Block a user