refactor(api): move command related API to separate file

This commit is contained in:
bfredl
2022-06-12 16:38:31 +02:00
parent a907d6f517
commit 0d63fafcda
9 changed files with 1146 additions and 1118 deletions

View File

@@ -959,7 +959,6 @@ ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf, bool from_lua)
return mappings;
}
/// Force obj to bool.
/// If it fails, returns false and sets err
/// @param obj The object to coerce to a boolean
@@ -1108,212 +1107,6 @@ const char *get_default_stl_hl(win_T *wp, bool use_winbar)
}
}
void create_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;
LuaRef preview_luaref = LUA_NOREF;
if (!uc_validate_name(name.data)) {
api_set_error(err, kErrorTypeValidation, "Invalid command name");
goto err;
}
if (mb_islower(name.data[0])) {
api_set_error(err, kErrorTypeValidation, "'name' must begin with an uppercase letter");
goto err;
}
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(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;
}
if (api_object_to_bool(opts->keepscript, "keepscript", false, err)) {
argt |= EX_KEEPSCRIPT;
} else if (ERROR_SET(err)) {
goto err;
}
bool force = api_object_to_bool(opts->force, "force", true, 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(opts->complete.data.string.data,
(int)opts->complete.data.string.size, &compl, &argt,
&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;
}
if (opts->preview.type == kObjectTypeLuaRef) {
argt |= EX_PREVIEW;
preview_luaref = api_new_luaref(opts->preview.data.luaref);
} else if (HAS_KEY(opts->preview)) {
api_set_error(err, kErrorTypeValidation, "Invalid value for 'preview'");
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(name.data, name.size, rep, argt, def, flags, compl, compl_arg, compl_luaref,
preview_luaref, addr_type_arg, luaref, 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, and compl_arg
}
return;
err:
NLUA_CLEAR_REF(luaref);
NLUA_CLEAR_REF(compl_luaref);
xfree(compl_arg);
}
int find_sid(uint64_t channel_id)
{
switch (channel_id) {
@@ -1343,136 +1136,3 @@ sctx_T api_set_sctx(uint64_t channel_id)
}
return old_current_sctx;
}
/// Check if a string contains only whitespace characters.
bool string_iswhite(String str)
{
for (size_t i = 0; i < str.size; i++) {
if (!ascii_iswhite(str.data[i])) {
// Found a non-whitespace character
return false;
} else if (str.data[i] == NUL) {
// Terminate at first occurence of a NUL character
break;
}
}
return true;
}
/// Build cmdline string for command, used by `nvim_cmd()`.
///
/// @return OK or FAIL.
void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdinfo, char **args,
size_t argc)
{
StringBuilder cmdline = KV_INITIAL_VALUE;
// Add command modifiers
if (cmdinfo->cmdmod.tab != 0) {
kv_printf(cmdline, "%dtab ", cmdinfo->cmdmod.tab - 1);
}
if (cmdinfo->verbose != -1) {
kv_printf(cmdline, "%ldverbose ", cmdinfo->verbose);
}
if (cmdinfo->emsg_silent) {
kv_concat(cmdline, "silent! ");
} else if (cmdinfo->silent) {
kv_concat(cmdline, "silent ");
}
switch (cmdinfo->cmdmod.split & (WSP_ABOVE | WSP_BELOW | WSP_TOP | WSP_BOT)) {
case WSP_ABOVE:
kv_concat(cmdline, "aboveleft ");
break;
case WSP_BELOW:
kv_concat(cmdline, "belowright ");
break;
case WSP_TOP:
kv_concat(cmdline, "topleft ");
break;
case WSP_BOT:
kv_concat(cmdline, "botright ");
break;
default:
break;
}
#define CMDLINE_APPEND_IF(cond, str) \
do { \
if (cond) { \
kv_concat(cmdline, str); \
} \
} while (0)
CMDLINE_APPEND_IF(cmdinfo->cmdmod.split & WSP_VERT, "vertical ");
CMDLINE_APPEND_IF(cmdinfo->sandbox, "sandbox ");
CMDLINE_APPEND_IF(cmdinfo->noautocmd, "noautocmd ");
CMDLINE_APPEND_IF(cmdinfo->cmdmod.browse, "browse ");
CMDLINE_APPEND_IF(cmdinfo->cmdmod.confirm, "confirm ");
CMDLINE_APPEND_IF(cmdinfo->cmdmod.hide, "hide ");
CMDLINE_APPEND_IF(cmdinfo->cmdmod.keepalt, "keepalt ");
CMDLINE_APPEND_IF(cmdinfo->cmdmod.keepjumps, "keepjumps ");
CMDLINE_APPEND_IF(cmdinfo->cmdmod.keepmarks, "keepmarks ");
CMDLINE_APPEND_IF(cmdinfo->cmdmod.keeppatterns, "keeppatterns ");
CMDLINE_APPEND_IF(cmdinfo->cmdmod.lockmarks, "lockmarks ");
CMDLINE_APPEND_IF(cmdinfo->cmdmod.noswapfile, "noswapfile ");
#undef CMDLINE_APPEND_IF
// Command range / count.
if (eap->argt & EX_RANGE) {
if (eap->addr_count == 1) {
kv_printf(cmdline, "%" PRIdLINENR, eap->line2);
} else if (eap->addr_count > 1) {
kv_printf(cmdline, "%" PRIdLINENR ",%" PRIdLINENR, eap->line1, eap->line2);
eap->addr_count = 2; // Make sure address count is not greater than 2
}
}
// Keep the index of the position where command name starts, so eap->cmd can point to it.
size_t cmdname_idx = cmdline.size;
kv_printf(cmdline, "%s", eap->cmd);
// Command bang.
if (eap->argt & EX_BANG && eap->forceit) {
kv_printf(cmdline, "!");
}
// Command register.
if (eap->argt & EX_REGSTR && eap->regname) {
kv_printf(cmdline, " %c", eap->regname);
}
// Iterate through each argument and store the starting index and length of each argument
size_t *argidx = xcalloc(argc, sizeof(size_t));
eap->argc = argc;
eap->arglens = xcalloc(argc, sizeof(size_t));
for (size_t i = 0; i < argc; i++) {
argidx[i] = cmdline.size + 1; // add 1 to account for the space.
eap->arglens[i] = STRLEN(args[i]);
kv_printf(cmdline, " %s", args[i]);
}
// Now that all the arguments are appended, use the command index and argument indices to set the
// values of eap->cmd, eap->arg and eap->args.
eap->cmd = cmdline.items + cmdname_idx;
eap->args = xcalloc(argc, sizeof(char *));
for (size_t i = 0; i < argc; i++) {
eap->args[i] = cmdline.items + argidx[i];
}
// If there isn't an argument, make eap->arg point to end of cmdline.
eap->arg = argc > 0 ? eap->args[0] : cmdline.items + cmdline.size;
// Finally, make cmdlinep point to the cmdline string.
*cmdlinep = cmdline.items;
xfree(argidx);
// Replace, :make and :grep with 'makeprg' and 'grepprg'.
char *p = replace_makeprg(eap, eap->arg, cmdlinep);
if (p != eap->arg) {
// If replace_makeprg modified the cmdline string, correct the argument pointers.
assert(argc == 1);
eap->arg = p;
eap->args[0] = p;
}
}