mirror of
https://github.com/neovim/neovim.git
synced 2025-10-01 23:48:32 +00:00
Merge pull request #16752 from gpanders/lua-user-commands
feat(api): implement nvim_{add,del}_user_command
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);
|
||||
}
|
||||
|
@@ -192,6 +192,7 @@ struct expand {
|
||||
int xp_context; // type of expansion
|
||||
size_t xp_pattern_len; // bytes in xp_pattern before cursor
|
||||
char_u *xp_arg; // completion function
|
||||
LuaRef xp_luaref; // Ref to Lua completion function
|
||||
sctx_T xp_script_ctx; // SCTX for completion function
|
||||
int xp_backslash; // one of the XP_BS_ values
|
||||
#ifndef BACKSLASH_IN_FILENAME
|
||||
|
@@ -81,23 +81,7 @@
|
||||
static int quitmore = 0;
|
||||
static bool ex_pressedreturn = false;
|
||||
|
||||
typedef struct ucmd {
|
||||
char_u *uc_name; // The command name
|
||||
uint32_t uc_argt; // The argument type
|
||||
char_u *uc_rep; // The command's replacement string
|
||||
long uc_def; // The default value for a range/count
|
||||
int uc_compl; // completion type
|
||||
cmd_addr_T uc_addr_type; // The command's address type
|
||||
sctx_T uc_script_ctx; // SCTX where the command was defined
|
||||
char_u *uc_compl_arg; // completion argument if any
|
||||
} ucmd_T;
|
||||
|
||||
#define UC_BUFFER 1 // -buffer: local to current buffer
|
||||
|
||||
static garray_T ucmds = { 0, 0, sizeof(ucmd_T), 4, NULL };
|
||||
|
||||
#define USER_CMD(i) (&((ucmd_T *)(ucmds.ga_data))[i])
|
||||
#define USER_CMD_GA(gap, i) (&((ucmd_T *)((gap)->ga_data))[i])
|
||||
garray_T ucmds = { 0, 0, sizeof(ucmd_T), 4, NULL };
|
||||
|
||||
// Whether a command index indicates a user command.
|
||||
#define IS_USER_CMDIDX(idx) ((int)(idx) < 0)
|
||||
@@ -2761,6 +2745,7 @@ static char_u *find_ucmd(exarg_T *eap, char_u *p, int *full, expand_T *xp, int *
|
||||
*complp = uc->uc_compl;
|
||||
}
|
||||
if (xp != NULL) {
|
||||
xp->xp_luaref = uc->uc_compl_luaref;
|
||||
xp->xp_arg = uc->uc_compl_arg;
|
||||
xp->xp_script_ctx = uc->uc_script_ctx;
|
||||
xp->xp_script_ctx.sc_lnum += sourcing_lnum;
|
||||
@@ -5171,8 +5156,9 @@ char_u *get_command_name(expand_T *xp, int idx)
|
||||
return cmdnames[idx].cmd_name;
|
||||
}
|
||||
|
||||
static int uc_add_command(char_u *name, size_t name_len, char_u *rep, uint32_t argt, long def,
|
||||
int flags, int compl, char_u *compl_arg, cmd_addr_T addr_type, bool force)
|
||||
int uc_add_command(char_u *name, size_t name_len, char_u *rep, uint32_t argt, long def, int flags,
|
||||
int compl, char_u *compl_arg, LuaRef compl_luaref, cmd_addr_T addr_type,
|
||||
LuaRef luaref, bool force)
|
||||
FUNC_ATTR_NONNULL_ARG(1, 3)
|
||||
{
|
||||
ucmd_T *cmd = NULL;
|
||||
@@ -5226,6 +5212,8 @@ static int uc_add_command(char_u *name, size_t name_len, char_u *rep, uint32_t a
|
||||
|
||||
XFREE_CLEAR(cmd->uc_rep);
|
||||
XFREE_CLEAR(cmd->uc_compl_arg);
|
||||
NLUA_CLEAR_REF(cmd->uc_luaref);
|
||||
NLUA_CLEAR_REF(cmd->uc_compl_luaref);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -5256,13 +5244,17 @@ static int uc_add_command(char_u *name, size_t name_len, char_u *rep, uint32_t a
|
||||
cmd->uc_script_ctx = current_sctx;
|
||||
cmd->uc_script_ctx.sc_lnum += sourcing_lnum;
|
||||
cmd->uc_compl_arg = compl_arg;
|
||||
cmd->uc_compl_luaref = compl_luaref;
|
||||
cmd->uc_addr_type = addr_type;
|
||||
cmd->uc_luaref = luaref;
|
||||
|
||||
return OK;
|
||||
|
||||
fail:
|
||||
xfree(rep_buf);
|
||||
xfree(compl_arg);
|
||||
NLUA_CLEAR_REF(luaref);
|
||||
NLUA_CLEAR_REF(compl_luaref);
|
||||
return FAIL;
|
||||
}
|
||||
|
||||
@@ -5301,6 +5293,7 @@ static const char *command_complete[] =
|
||||
[EXPAND_CSCOPE] = "cscope",
|
||||
[EXPAND_USER_DEFINED] = "custom",
|
||||
[EXPAND_USER_LIST] = "customlist",
|
||||
[EXPAND_USER_LUA] = "<Lua function>",
|
||||
[EXPAND_DIFF_BUFFERS] = "diff_buffer",
|
||||
[EXPAND_DIRECTORIES] = "dir",
|
||||
[EXPAND_ENV_VARS] = "environment",
|
||||
@@ -5702,8 +5695,8 @@ static void ex_command(exarg_T *eap)
|
||||
} else if (compl > 0 && (argt & EX_EXTRA) == 0) {
|
||||
emsg(_(e_complete_used_without_nargs));
|
||||
} else {
|
||||
uc_add_command(name, end - name, p, argt, def, flags, compl, compl_arg,
|
||||
addr_type_arg, eap->forceit);
|
||||
uc_add_command(name, end - name, p, argt, def, flags, compl, compl_arg, LUA_NOREF,
|
||||
addr_type_arg, LUA_NOREF, eap->forceit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5717,11 +5710,13 @@ void ex_comclear(exarg_T *eap)
|
||||
uc_clear(&curbuf->b_ucmds);
|
||||
}
|
||||
|
||||
static void free_ucmd(ucmd_T *cmd)
|
||||
void free_ucmd(ucmd_T *cmd)
|
||||
{
|
||||
xfree(cmd->uc_name);
|
||||
xfree(cmd->uc_rep);
|
||||
xfree(cmd->uc_compl_arg);
|
||||
NLUA_CLEAR_REF(cmd->uc_compl_luaref);
|
||||
NLUA_CLEAR_REF(cmd->uc_luaref);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -5759,9 +5754,7 @@ static void ex_delcommand(exarg_T *eap)
|
||||
return;
|
||||
}
|
||||
|
||||
xfree(cmd->uc_name);
|
||||
xfree(cmd->uc_rep);
|
||||
xfree(cmd->uc_compl_arg);
|
||||
free_ucmd(cmd);
|
||||
|
||||
--gap->ga_len;
|
||||
|
||||
@@ -5843,7 +5836,7 @@ static char_u *uc_split_args(char_u *arg, size_t *lenp)
|
||||
return buf;
|
||||
}
|
||||
|
||||
static size_t add_cmd_modifier(char_u *buf, char *mod_str, bool *multi_mods)
|
||||
static size_t add_cmd_modifier(char *buf, char *mod_str, bool *multi_mods)
|
||||
{
|
||||
size_t result = STRLEN(mod_str);
|
||||
if (*multi_mods) {
|
||||
@@ -6044,70 +6037,8 @@ static size_t uc_check_code(char_u *code, size_t len, char_u *buf, ucmd_T *cmd,
|
||||
*buf = '\0';
|
||||
}
|
||||
|
||||
bool multi_mods = false;
|
||||
result += uc_mods((char *)buf);
|
||||
|
||||
// :aboveleft and :leftabove
|
||||
if (cmdmod.split & WSP_ABOVE) {
|
||||
result += add_cmd_modifier(buf, "aboveleft", &multi_mods);
|
||||
}
|
||||
// :belowright and :rightbelow
|
||||
if (cmdmod.split & WSP_BELOW) {
|
||||
result += add_cmd_modifier(buf, "belowright", &multi_mods);
|
||||
}
|
||||
// :botright
|
||||
if (cmdmod.split & WSP_BOT) {
|
||||
result += add_cmd_modifier(buf, "botright", &multi_mods);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
bool *set;
|
||||
char *name;
|
||||
} mod_entry_T;
|
||||
static mod_entry_T mod_entries[] = {
|
||||
{ &cmdmod.browse, "browse" },
|
||||
{ &cmdmod.confirm, "confirm" },
|
||||
{ &cmdmod.hide, "hide" },
|
||||
{ &cmdmod.keepalt, "keepalt" },
|
||||
{ &cmdmod.keepjumps, "keepjumps" },
|
||||
{ &cmdmod.keepmarks, "keepmarks" },
|
||||
{ &cmdmod.keeppatterns, "keeppatterns" },
|
||||
{ &cmdmod.lockmarks, "lockmarks" },
|
||||
{ &cmdmod.noswapfile, "noswapfile" }
|
||||
};
|
||||
// the modifiers that are simple flags
|
||||
for (size_t i = 0; i < ARRAY_SIZE(mod_entries); i++) {
|
||||
if (*mod_entries[i].set) {
|
||||
result += add_cmd_modifier(buf, mod_entries[i].name, &multi_mods);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(vim): How to support :noautocmd?
|
||||
// TODO(vim): How to support :sandbox?
|
||||
|
||||
// :silent
|
||||
if (msg_silent > 0) {
|
||||
result += add_cmd_modifier(buf, emsg_silent > 0 ? "silent!" : "silent",
|
||||
&multi_mods);
|
||||
}
|
||||
// :tab
|
||||
if (cmdmod.tab > 0) {
|
||||
result += add_cmd_modifier(buf, "tab", &multi_mods);
|
||||
}
|
||||
// :topleft
|
||||
if (cmdmod.split & WSP_TOP) {
|
||||
result += add_cmd_modifier(buf, "topleft", &multi_mods);
|
||||
}
|
||||
|
||||
// TODO(vim): How to support :unsilent?
|
||||
|
||||
// :verbose
|
||||
if (p_verbose > 0) {
|
||||
result += add_cmd_modifier(buf, "verbose", &multi_mods);
|
||||
}
|
||||
// :vertical
|
||||
if (cmdmod.split & WSP_VERT) {
|
||||
result += add_cmd_modifier(buf, "vertical", &multi_mods);
|
||||
}
|
||||
if (quote && buf != NULL) {
|
||||
buf += result - 2;
|
||||
*buf = '"';
|
||||
@@ -6152,6 +6083,76 @@ static size_t uc_check_code(char_u *code, size_t len, char_u *buf, ucmd_T *cmd,
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t uc_mods(char *buf)
|
||||
{
|
||||
size_t result = 0;
|
||||
bool multi_mods = false;
|
||||
|
||||
// :aboveleft and :leftabove
|
||||
if (cmdmod.split & WSP_ABOVE) {
|
||||
result += add_cmd_modifier(buf, "aboveleft", &multi_mods);
|
||||
}
|
||||
// :belowright and :rightbelow
|
||||
if (cmdmod.split & WSP_BELOW) {
|
||||
result += add_cmd_modifier(buf, "belowright", &multi_mods);
|
||||
}
|
||||
// :botright
|
||||
if (cmdmod.split & WSP_BOT) {
|
||||
result += add_cmd_modifier(buf, "botright", &multi_mods);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
bool *set;
|
||||
char *name;
|
||||
} mod_entry_T;
|
||||
static mod_entry_T mod_entries[] = {
|
||||
{ &cmdmod.browse, "browse" },
|
||||
{ &cmdmod.confirm, "confirm" },
|
||||
{ &cmdmod.hide, "hide" },
|
||||
{ &cmdmod.keepalt, "keepalt" },
|
||||
{ &cmdmod.keepjumps, "keepjumps" },
|
||||
{ &cmdmod.keepmarks, "keepmarks" },
|
||||
{ &cmdmod.keeppatterns, "keeppatterns" },
|
||||
{ &cmdmod.lockmarks, "lockmarks" },
|
||||
{ &cmdmod.noswapfile, "noswapfile" }
|
||||
};
|
||||
// the modifiers that are simple flags
|
||||
for (size_t i = 0; i < ARRAY_SIZE(mod_entries); i++) {
|
||||
if (*mod_entries[i].set) {
|
||||
result += add_cmd_modifier(buf, mod_entries[i].name, &multi_mods);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(vim): How to support :noautocmd?
|
||||
// TODO(vim): How to support :sandbox?
|
||||
|
||||
// :silent
|
||||
if (msg_silent > 0) {
|
||||
result += add_cmd_modifier(buf, emsg_silent > 0 ? "silent!" : "silent", &multi_mods);
|
||||
}
|
||||
// :tab
|
||||
if (cmdmod.tab > 0) {
|
||||
result += add_cmd_modifier(buf, "tab", &multi_mods);
|
||||
}
|
||||
// :topleft
|
||||
if (cmdmod.split & WSP_TOP) {
|
||||
result += add_cmd_modifier(buf, "topleft", &multi_mods);
|
||||
}
|
||||
|
||||
// TODO(vim): How to support :unsilent?
|
||||
|
||||
// :verbose
|
||||
if (p_verbose > 0) {
|
||||
result += add_cmd_modifier(buf, "verbose", &multi_mods);
|
||||
}
|
||||
// :vertical
|
||||
if (cmdmod.split & WSP_VERT) {
|
||||
result += add_cmd_modifier(buf, "vertical", &multi_mods);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void do_ucmd(exarg_T *eap)
|
||||
{
|
||||
char_u *buf;
|
||||
@@ -6174,6 +6175,11 @@ static void do_ucmd(exarg_T *eap)
|
||||
cmd = USER_CMD_GA(&curbuf->b_ucmds, eap->useridx);
|
||||
}
|
||||
|
||||
if (cmd->uc_luaref > 0) {
|
||||
nlua_do_ucmd(cmd, eap);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Replace <> in the command by the arguments.
|
||||
* First round: "buf" is NULL, compute length, allocate "buf".
|
||||
|
@@ -32,6 +32,26 @@ typedef struct {
|
||||
tasave_T tabuf;
|
||||
} save_state_T;
|
||||
|
||||
typedef struct ucmd {
|
||||
char_u *uc_name; // The command name
|
||||
uint32_t uc_argt; // The argument type
|
||||
char_u *uc_rep; // The command's replacement string
|
||||
long uc_def; // The default value for a range/count
|
||||
int uc_compl; // completion type
|
||||
cmd_addr_T uc_addr_type; // The command's address type
|
||||
sctx_T uc_script_ctx; // SCTX where the command was defined
|
||||
char_u *uc_compl_arg; // completion argument if any
|
||||
LuaRef uc_compl_luaref; // Reference to Lua completion function
|
||||
LuaRef uc_luaref; // Reference to Lua function
|
||||
} ucmd_T;
|
||||
|
||||
#define UC_BUFFER 1 // -buffer: local to current buffer
|
||||
|
||||
extern garray_T ucmds;
|
||||
|
||||
#define USER_CMD(i) (&((ucmd_T *)(ucmds.ga_data))[i])
|
||||
#define USER_CMD_GA(gap, i) (&((ucmd_T *)((gap)->ga_data))[i])
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "ex_docmd.h.generated.h"
|
||||
#endif
|
||||
|
@@ -4983,6 +4983,9 @@ static int ExpandFromContext(expand_T *xp, char_u *pat, int *num_file, char_u **
|
||||
if (xp->xp_context == EXPAND_USER_LIST) {
|
||||
return ExpandUserList(xp, num_file, file);
|
||||
}
|
||||
if (xp->xp_context == EXPAND_USER_LUA) {
|
||||
return ExpandUserLua(xp, num_file, file);
|
||||
}
|
||||
if (xp->xp_context == EXPAND_PACKADD) {
|
||||
return ExpandPackAddDir(pat, num_file, file);
|
||||
}
|
||||
@@ -5411,6 +5414,35 @@ static int ExpandUserList(expand_T *xp, int *num_file, char_u ***file)
|
||||
return OK;
|
||||
}
|
||||
|
||||
static int ExpandUserLua(expand_T *xp, int *num_file, char_u ***file)
|
||||
{
|
||||
typval_T rettv;
|
||||
nlua_call_user_expand_func(xp, &rettv);
|
||||
if (rettv.v_type != VAR_LIST) {
|
||||
tv_clear(&rettv);
|
||||
return FAIL;
|
||||
}
|
||||
|
||||
list_T *const retlist = rettv.vval.v_list;
|
||||
|
||||
garray_T ga;
|
||||
ga_init(&ga, (int)sizeof(char *), 3);
|
||||
// Loop over the items in the list.
|
||||
TV_LIST_ITER_CONST(retlist, li, {
|
||||
if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING
|
||||
|| TV_LIST_ITEM_TV(li)->vval.v_string == NULL) {
|
||||
continue; // Skip non-string items and empty strings.
|
||||
}
|
||||
|
||||
GA_APPEND(char *, &ga, xstrdup((const char *)TV_LIST_ITEM_TV(li)->vval.v_string));
|
||||
});
|
||||
tv_list_unref(retlist);
|
||||
|
||||
*file = ga.ga_data;
|
||||
*num_file = ga.ga_len;
|
||||
return OK;
|
||||
}
|
||||
|
||||
/// Expand color scheme, compiler or filetype names.
|
||||
/// Search from 'runtimepath':
|
||||
/// 'runtimepath'/{dirnames}/{pat}.vim
|
||||
|
@@ -441,8 +441,8 @@ local function process_function(fn)
|
||||
local cparam = string.format('arg%u', j)
|
||||
local param_type = real_type(param[1])
|
||||
local lc_param_type = real_type(param[1]):lower()
|
||||
local extra = ((param_type == "Object" or param_type == "Dictionary") and "false, ") or ""
|
||||
if param[1] == "DictionaryOf(LuaRef)" then
|
||||
local extra = param_type == "Dictionary" and "false, " or ""
|
||||
if param[1] == "Object" or param[1] == "DictionaryOf(LuaRef)" then
|
||||
extra = "true, "
|
||||
end
|
||||
local errshift = 0
|
||||
|
@@ -26,6 +26,17 @@ local defspipe = io.open(defs_file, 'wb')
|
||||
|
||||
local keysets = require'api.keysets'
|
||||
|
||||
local keywords = {
|
||||
register = true,
|
||||
}
|
||||
|
||||
local function sanitize(key)
|
||||
if keywords[key] then
|
||||
return key .. "_"
|
||||
end
|
||||
return key
|
||||
end
|
||||
|
||||
for name, keys in pairs(keysets) do
|
||||
local neworder, hashfun = hashy.hashy_hash(name, keys, function (idx)
|
||||
return name.."_table["..idx.."].str"
|
||||
@@ -33,7 +44,7 @@ for name, keys in pairs(keysets) do
|
||||
|
||||
defspipe:write("typedef struct {\n")
|
||||
for _, key in ipairs(neworder) do
|
||||
defspipe:write(" Object "..key..";\n")
|
||||
defspipe:write(" Object "..sanitize(key)..";\n")
|
||||
end
|
||||
defspipe:write("} KeyDict_"..name..";\n\n")
|
||||
|
||||
@@ -41,7 +52,7 @@ for name, keys in pairs(keysets) do
|
||||
|
||||
funcspipe:write("KeySetLink "..name.."_table[] = {\n")
|
||||
for _, key in ipairs(neworder) do
|
||||
funcspipe:write(' {"'..key..'", offsetof(KeyDict_'..name..", "..key..")},\n")
|
||||
funcspipe:write(' {"'..key..'", offsetof(KeyDict_'..name..", "..sanitize(key)..")},\n")
|
||||
end
|
||||
funcspipe:write(' {NULL, 0},\n')
|
||||
funcspipe:write("};\n\n")
|
||||
|
@@ -18,6 +18,7 @@
|
||||
#include "nvim/event/loop.h"
|
||||
#include "nvim/event/time.h"
|
||||
#include "nvim/ex_cmds2.h"
|
||||
#include "nvim/ex_docmd.h"
|
||||
#include "nvim/ex_getln.h"
|
||||
#include "nvim/extmark.h"
|
||||
#include "nvim/func_attr.h"
|
||||
@@ -914,6 +915,24 @@ void nlua_typval_call(const char *str, size_t len, typval_T *const args, int arg
|
||||
}
|
||||
}
|
||||
|
||||
void nlua_call_user_expand_func(expand_T *xp, typval_T *ret_tv)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
lua_State *const lstate = global_lstate;
|
||||
|
||||
nlua_pushref(lstate, xp->xp_luaref);
|
||||
lua_pushstring(lstate, (char *)xp->xp_pattern);
|
||||
lua_pushstring(lstate, (char *)xp->xp_line);
|
||||
lua_pushinteger(lstate, xp->xp_col);
|
||||
|
||||
if (nlua_pcall(lstate, 3, 1)) {
|
||||
nlua_error(lstate, _("E5108: Error executing Lua function: %.*s"));
|
||||
return;
|
||||
}
|
||||
|
||||
nlua_pop_typval(lstate, ret_tv);
|
||||
}
|
||||
|
||||
static void nlua_typval_exec(const char *lcmd, size_t lcmd_len, const char *name,
|
||||
typval_T *const args, int argcount, bool special, typval_T *ret_tv)
|
||||
{
|
||||
@@ -1432,3 +1451,48 @@ void nlua_execute_on_key(int c)
|
||||
#endif
|
||||
}
|
||||
|
||||
void nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap)
|
||||
{
|
||||
lua_State *const lstate = global_lstate;
|
||||
|
||||
nlua_pushref(lstate, cmd->uc_luaref);
|
||||
|
||||
lua_newtable(lstate);
|
||||
lua_pushboolean(lstate, eap->forceit == 1);
|
||||
lua_setfield(lstate, -2, "bang");
|
||||
|
||||
lua_pushinteger(lstate, eap->line1);
|
||||
lua_setfield(lstate, -2, "line1");
|
||||
|
||||
lua_pushinteger(lstate, eap->line2);
|
||||
lua_setfield(lstate, -2, "line2");
|
||||
|
||||
lua_pushstring(lstate, (const char *)eap->arg);
|
||||
lua_setfield(lstate, -2, "args");
|
||||
|
||||
lua_pushstring(lstate, (const char *)&eap->regname);
|
||||
lua_setfield(lstate, -2, "reg");
|
||||
|
||||
lua_pushinteger(lstate, eap->addr_count);
|
||||
lua_setfield(lstate, -2, "range");
|
||||
|
||||
if (eap->addr_count > 0) {
|
||||
lua_pushinteger(lstate, eap->line2);
|
||||
} else {
|
||||
lua_pushinteger(lstate, cmd->uc_def);
|
||||
}
|
||||
lua_setfield(lstate, -2, "count");
|
||||
|
||||
// The size of this buffer is chosen empirically to be large enough to hold
|
||||
// every possible modifier (with room to spare). If the list of possible
|
||||
// modifiers grows this may need to be updated.
|
||||
char buf[200] = { 0 };
|
||||
(void)uc_mods(buf);
|
||||
lua_pushstring(lstate, buf);
|
||||
lua_setfield(lstate, -2, "mods");
|
||||
|
||||
if (nlua_pcall(lstate, 1, 0)) {
|
||||
nlua_error(lstate, _("Error executing Lua callback: %.*s"));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -7,6 +7,7 @@
|
||||
#include "nvim/api/private/defs.h"
|
||||
#include "nvim/eval/typval.h"
|
||||
#include "nvim/ex_cmds_defs.h"
|
||||
#include "nvim/ex_docmd.h"
|
||||
#include "nvim/func_attr.h"
|
||||
#include "nvim/lua/converter.h"
|
||||
|
||||
|
@@ -143,6 +143,7 @@ enum {
|
||||
EXPAND_COMPILER,
|
||||
EXPAND_USER_DEFINED,
|
||||
EXPAND_USER_LIST,
|
||||
EXPAND_USER_LUA,
|
||||
EXPAND_SHELLCMD,
|
||||
EXPAND_CSCOPE,
|
||||
EXPAND_SIGN,
|
||||
|
Reference in New Issue
Block a user