diff options
author | Josh Rahm <joshuarahm@gmail.com> | 2022-08-07 16:01:34 -0600 |
---|---|---|
committer | Josh Rahm <joshuarahm@gmail.com> | 2022-08-07 16:01:34 -0600 |
commit | a5f27a311fb28797a72b8aa16ec7122c5a1b15e4 (patch) | |
tree | 3732f7339e29431f31310aef6ffc802cf4f6255d /src | |
parent | 6c909fedc924d9f4257aa204b0168c6177cc5d28 (diff) | |
parent | 629169462a82f0fbb7a8911a4554894537d6776c (diff) | |
download | rneovim-a5f27a311fb28797a72b8aa16ec7122c5a1b15e4.tar.gz rneovim-a5f27a311fb28797a72b8aa16ec7122c5a1b15e4.tar.bz2 rneovim-a5f27a311fb28797a72b8aa16ec7122c5a1b15e4.zip |
Merge branch 'master' of https://github.com/neovim/neovim into rahm
Diffstat (limited to 'src')
40 files changed, 2936 insertions, 2274 deletions
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 017883a913..b743e9923f 100755 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -77,7 +77,7 @@ set(LINT_SUPPRESSES_ROOT ${PROJECT_BINARY_DIR}/errors) set(LINT_SUPPRESSES_URL "${LINT_SUPPRESS_URL_BASE}/errors.tar.gz") set(LINT_SUPPRESSES_ARCHIVE ${LINT_SUPPRESSES_ROOT}/errors.tar.gz) set(LINT_SUPPRESSES_TOUCH_FILE "${TOUCHES_DIR}/unpacked-clint-errors-archive") -set(LINT_SUPPRESSES_INSTALL_SCRIPT "${PROJECT_SOURCE_DIR}/cmake/InstallClintErrors.cmake") +set(CLINT_REPORT_PATH ${LINT_SUPPRESSES_ROOT}/src/home/runner/work/doc/doc/gh-pages/reports/clint) glob_wrapper(UNICODE_FILES ${UNICODE_DIR}/*.txt) glob_wrapper(API_HEADERS api/*.h) @@ -225,7 +225,7 @@ foreach(sfile ${NVIM_SOURCES} "${GENERATED_KEYSETS}" "${GENERATED_UI_EVENTS_CLIENT}" ) - get_filename_component(full_d ${sfile} PATH) + get_filename_component(full_d ${sfile} DIRECTORY) file(RELATIVE_PATH d "${CMAKE_CURRENT_LIST_DIR}" "${full_d}") if(${d} MATCHES "^[.][.]|auto/") file(RELATIVE_PATH d "${GENERATED_DIR}" "${full_d}") @@ -597,10 +597,7 @@ set_target_properties( POSITION_INDEPENDENT_CODE ON OUTPUT_NAME ${LIBNVIM_NAME} ) -set_property( - TARGET libnvim - APPEND_STRING PROPERTY COMPILE_FLAGS " -DMAKE_LIB " -) +target_compile_options(libnvim PRIVATE -DMAKE_LIB) if(NOT LUAJIT_FOUND) message(STATUS "luajit not found, skipping nvim-test (unit tests) target") @@ -626,10 +623,7 @@ else() PROPERTIES POSITION_INDEPENDENT_CODE ON ) - set_property( - TARGET nvim-test - APPEND_STRING PROPERTY COMPILE_FLAGS " -DUNIT_TESTING " - ) + target_compile_options(nvim-test PRIVATE -DUNIT_TESTING) endif() if(CLANG_ASAN_UBSAN) @@ -664,7 +658,7 @@ elseif(CLANG_TSAN) endif() function(get_test_target prefix sfile relative_path_var target_var) - get_filename_component(full_d "${sfile}" PATH) + get_filename_component(full_d "${sfile}" DIRECTORY) file(RELATIVE_PATH d "${PROJECT_SOURCE_DIR}/src/nvim" "${full_d}") if(d MATCHES "^[.][.]") file(RELATIVE_PATH d "${GENERATED_DIR}" "${full_d}") @@ -730,13 +724,9 @@ add_custom_command( OUTPUT ${LINT_SUPPRESSES_TOUCH_FILE} WORKING_DIRECTORY ${LINT_SUPPRESSES_ROOT}/src COMMAND ${CMAKE_COMMAND} -E tar xfz ${LINT_SUPPRESSES_ARCHIVE} - COMMAND - ${CMAKE_COMMAND} - -DTARGET=${LINT_SUPPRESSES_ROOT} - -P ${LINT_SUPPRESSES_INSTALL_SCRIPT} + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CLINT_REPORT_PATH} "${LINT_SUPPRESSES_ROOT}" COMMAND ${CMAKE_COMMAND} -E touch ${LINT_SUPPRESSES_TOUCH_FILE} - DEPENDS - ${LINT_SUPPRESSES_ARCHIVE} ${LINT_SUPPRESSES_INSTALL_SCRIPT} + DEPENDS ${LINT_SUPPRESSES_ARCHIVE} ) add_download(${LINT_SUPPRESS_FILE} ${LINT_SUPPRESS_URL} off) diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 1b1a161226..d3895d31cf 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -565,27 +565,33 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In return; } - char *str_at_start = (char *)ml_get_buf(buf, (linenr_T)start_row, false); - if (start_col < 0 || (size_t)start_col > strlen(str_at_start)) { + char *str_at_start = NULL; + char *str_at_end = NULL; + + // Another call to ml_get_buf() may free the line, so make a copy. + str_at_start = xstrdup((char *)ml_get_buf(buf, (linenr_T)start_row, false)); + size_t len_at_start = strlen(str_at_start); + if (start_col < 0 || (size_t)start_col > len_at_start) { api_set_error(err, kErrorTypeValidation, "start_col out of bounds"); - return; + goto early_end; } - char *str_at_end = (char *)ml_get_buf(buf, (linenr_T)end_row, false); + // Another call to ml_get_buf() may free the line, so make a copy. + str_at_end = xstrdup((char *)ml_get_buf(buf, (linenr_T)end_row, false)); size_t len_at_end = strlen(str_at_end); if (end_col < 0 || (size_t)end_col > len_at_end) { api_set_error(err, kErrorTypeValidation, "end_col out of bounds"); - return; + goto early_end; } if (start_row > end_row || (end_row == start_row && start_col > end_col)) { api_set_error(err, kErrorTypeValidation, "start is higher than end"); - return; + goto early_end; } bool disallow_nl = (channel_id != VIML_INTERNAL_CALL); if (!check_string_array(replacement, disallow_nl, err)) { - return; + goto early_end; } size_t new_len = replacement.size; @@ -597,7 +603,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In if (start_row == end_row) { old_byte = (bcount_t)end_col - start_col; } else { - old_byte += (bcount_t)strlen(str_at_start) - start_col; + old_byte += (bcount_t)len_at_start - start_col; for (int64_t i = 1; i < end_row - start_row; i++) { int64_t lnum = start_row + i; @@ -611,7 +617,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In String last_item = replacement.items[replacement.size - 1].data.string; size_t firstlen = (size_t)start_col + first_item.size; - size_t last_part_len = strlen(str_at_end) - (size_t)end_col; + size_t last_part_len = len_at_end - (size_t)end_col; if (replacement.size == 1) { firstlen += last_part_len; } @@ -751,6 +757,10 @@ end: xfree(lines); aucmd_restbuf(&aco); try_end(err); + +early_end: + xfree(str_at_start); + xfree(str_at_end); } /// Gets a range from the buffer. diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c index 33efa6b326..bc766ff39c 100644 --- a/src/nvim/api/command.c +++ b/src/nvim/api/command.c @@ -14,6 +14,7 @@ #include "nvim/lua/executor.h" #include "nvim/ops.h" #include "nvim/regexp.h" +#include "nvim/usercmd.h" #include "nvim/window.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -505,6 +506,11 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error OBJ_TO_BOOL(cmdinfo.magic.file, magic.file, ea.argt & EX_XFILE, "'magic.file'"); OBJ_TO_BOOL(cmdinfo.magic.bar, magic.bar, ea.argt & EX_TRLBAR, "'magic.bar'"); + if (cmdinfo.magic.file) { + ea.argt |= EX_XFILE; + } else { + ea.argt &= ~EX_XFILE; + } } else { cmdinfo.magic.file = ea.argt & EX_XFILE; cmdinfo.magic.bar = ea.argt & EX_TRLBAR; @@ -939,7 +945,7 @@ void create_user_command(String name, Object command, Dict(user_command) *opts, cmd_addr_T addr_type_arg = ADDR_NONE; int compl = EXPAND_NOTHING; char *compl_arg = NULL; - char *rep = NULL; + const char *rep = NULL; LuaRef luaref = LUA_NOREF; LuaRef compl_luaref = LUA_NOREF; LuaRef preview_luaref = LUA_NOREF; @@ -1111,8 +1117,7 @@ void create_user_command(String name, Object command, Dict(user_command) *opts, if (opts->desc.type == kObjectTypeString) { rep = opts->desc.data.string.data; } else { - snprintf((char *)IObuff, IOSIZE, "<Lua function %d>", luaref); - rep = (char *)IObuff; + rep = ""; } break; case kObjectTypeString: diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index 2b4c9c5b9c..bbb044fba3 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -196,9 +196,16 @@ static void aupat_show(AutoPat *ap, event_T event, int previous_group) if (ac->desc != NULL) { size_t msglen = 100; char *msg = (char *)xmallocz(msglen); - snprintf(msg, msglen, "%s [%s]", exec_to_string, ac->desc); + if (ac->exec.type == CALLABLE_CB) { + msg_puts_attr(exec_to_string, HL_ATTR(HLF_8)); + snprintf(msg, msglen, " [%s]", ac->desc); + } else { + snprintf(msg, msglen, "%s [%s]", exec_to_string, ac->desc); + } msg_outtrans(msg); XFREE_CLEAR(msg); + } else if (ac->exec.type == CALLABLE_CB) { + msg_puts_attr(exec_to_string, HL_ATTR(HLF_8)); } else { msg_outtrans(exec_to_string); } diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 6dd71e92a6..f23a1caf8b 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -76,6 +76,7 @@ #include "nvim/syntax.h" #include "nvim/ui.h" #include "nvim/undo.h" +#include "nvim/usercmd.h" #include "nvim/version.h" #include "nvim/vim.h" #include "nvim/window.h" @@ -968,7 +969,7 @@ char *do_bufdel(int command, char *arg, int addr_count, int start_bnr, int end_b } else { if (addr_count == 2) { if (*arg) { // both range and argument is not allowed - return _(e_trailing); + return ex_errmsg(e_trailing_arg, arg); } bnr = start_bnr; } else { // addr_count == 1 @@ -5639,7 +5640,9 @@ void wipe_buffer(buf_T *buf, bool aucmd) void buf_open_scratch(handle_T bufnr, char *bufname) { (void)do_ecmd((int)bufnr, NULL, NULL, NULL, ECMD_ONE, ECMD_HIDE, NULL); + apply_autocmds(EVENT_BUFFILEPRE, NULL, NULL, false, curbuf); (void)setfname(curbuf, bufname, NULL, true); + apply_autocmds(EVENT_BUFFILEPOST, NULL, NULL, false, curbuf); set_option_value("bh", 0L, "hide", OPT_LOCAL); set_option_value("bt", 0L, "nofile", OPT_LOCAL); set_option_value("swf", 0L, NULL, OPT_LOCAL); diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 9cc92cb62e..7cb8bdee19 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -544,9 +544,9 @@ int var_redir_start(char *name, int append) clear_lval(redir_lval); if (redir_endp != NULL && *redir_endp != NUL) { // Trailing characters are present after the variable name - emsg(_(e_trailing)); + semsg(_(e_trailing_arg), redir_endp); } else { - emsg(_(e_invarg)); + semsg(_(e_invarg2), name); } redir_endp = NULL; // don't store a value, only cleanup var_redir_stop(); @@ -1326,7 +1326,7 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const // Don't expand the name when we already know there is an error. if (unlet && !ascii_iswhite(*p) && !ends_excmd(*p) && *p != '[' && *p != '.') { - emsg(_(e_trailing)); + semsg(_(e_trailing_arg), p); return NULL; } diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index c8eb0334fa..6d8776d08b 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -264,6 +264,7 @@ return { matchstrpos={args={2,4}, base=1}, max={args=1, base=1}, menu_get={args={1, 2}}, + menu_info={args={1, 2}, base=1}, min={args=1, base=1}, mkdir={args={1, 3}, base=1}, mode={args={0, 1}, base=1}, diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 691ccfe535..f24285063e 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -1839,7 +1839,7 @@ static void f_eval(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; } else if (*s != NUL) { - emsg(_(e_trailing)); + semsg(_(e_trailing_arg), s); } } @@ -4705,7 +4705,7 @@ static void f_islocked(typval_T *argvars, typval_T *rettv, FunPtr fptr) FNE_CHECK_START); if (end != NULL && lv.ll_name != NULL) { if (*end != NUL) { - emsg(_(e_trailing)); + semsg(_(e_trailing_arg), end); } else { if (lv.ll_tv == NULL) { di = find_var(lv.ll_name, lv.ll_name_len, NULL, true); @@ -9917,7 +9917,11 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) // at this point the buffer has no terminal instance associated yet, so unset // the 'swapfile' option to ensure no swap file will be created curbuf->b_p_swf = false; + + apply_autocmds(EVENT_BUFFILEPRE, NULL, NULL, false, curbuf); (void)setfname(curbuf, (char *)NameBuff, NULL, true); + apply_autocmds(EVENT_BUFFILEPOST, NULL, NULL, false, curbuf); + // Save the job id and pid in b:terminal_job_{id,pid} Error err = ERROR_INIT; // deprecated: use 'channel' buffer option diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index fd57b45e86..ff1808ed91 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1657,13 +1657,14 @@ void callback_copy(Callback *dest, Callback *src) /// Generate a string description of a callback char *callback_to_string(Callback *cb) { - size_t msglen = 100; + if (cb->type == kCallbackLua) { + return nlua_funcref_str(cb->data.luaref); + } + + const size_t msglen = 100; char *msg = (char *)xmallocz(msglen); switch (cb->type) { - case kCallbackLua: - snprintf(msg, msglen, "<lua: %d>", cb->data.luaref); - break; case kCallbackFuncref: // TODO(tjdevries): Is this enough space for this? snprintf(msg, msglen, "<vim function: %s>", cb->data.funcref); @@ -1672,7 +1673,7 @@ char *callback_to_string(Callback *cb) snprintf(msg, msglen, "<vim partial: %s>", cb->data.partial->pt_name); break; default: - snprintf(msg, msglen, "%s", ""); + *msg = '\0'; break; } return msg; diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index a90148bf23..2f4799db57 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -2039,7 +2039,7 @@ void ex_function(exarg_T *eap) // if (!paren) { if (!ends_excmd(*skipwhite((char *)p))) { - emsg(_(e_trailing)); + semsg(_(e_trailing_arg), p); goto ret_free; } eap->nextcmd = (char *)check_nextcmd(p); @@ -2163,7 +2163,7 @@ void ex_function(exarg_T *eap) if (*p == '\n') { line_arg = p + 1; } else if (*p != NUL && *p != '"' && !eap->skip && !did_emsg) { - emsg(_(e_trailing)); + semsg(_(e_trailing_arg), p); } /* @@ -2703,7 +2703,7 @@ void ex_delfunction(exarg_T *eap) } if (!ends_excmd(*skipwhite((char *)p))) { xfree(name); - emsg(_(e_trailing)); + semsg(_(e_trailing_arg), p); return; } eap->nextcmd = (char *)check_nextcmd(p); @@ -3021,7 +3021,7 @@ void ex_call(exarg_T *eap) if (!ends_excmd(*arg)) { if (!failed && !aborting()) { emsg_severe = true; - emsg(_(e_trailing)); + semsg(_(e_trailing_arg), arg); } } else { eap->nextcmd = (char *)check_nextcmd(arg); diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c index d01fff6b94..ea1c3a8c4e 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -80,7 +80,7 @@ static list_T *heredoc_get(exarg_T *eap, char *cmd) marker = skipwhite(cmd); p = (char *)skiptowhite((char_u *)marker); if (*skipwhite(p) != NUL && *skipwhite(p) != '"') { - emsg(_(e_trailing)); + semsg(_(e_trailing_arg), p); return NULL; } *p = NUL; @@ -460,7 +460,7 @@ static const char *list_arg_vars(exarg_T *eap, const char *arg, int *first) arg = find_name_end(arg, NULL, NULL, FNE_INCL_BR | FNE_CHECK_START); if (!ascii_iswhite(*arg) && !ends_excmd(*arg)) { emsg_severe = true; - emsg(_(e_trailing)); + semsg(_(e_trailing_arg), arg); break; } } else { @@ -678,8 +678,11 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo if (!failed) { if (opt_type != gov_string || s != NULL) { - set_option_value(arg, n, s, opt_flags); + char *err = set_option_value(arg, n, s, opt_flags); arg_end = p; + if (err != NULL) { + emsg(_(err)); + } } else { emsg(_(e_stringreq)); } @@ -812,7 +815,7 @@ static void ex_unletlock(exarg_T *eap, char *argstart, int deep, ex_unletlock_ca || (!ascii_iswhite(*name_end) && !ends_excmd(*name_end))) { if (name_end != NULL) { emsg_severe = true; - emsg(_(e_trailing)); + semsg(_(e_trailing_arg), name_end); } if (!(eap->skip || error)) { clear_lval(&lv); diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 23e7660606..9d1ac185d4 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -3616,7 +3616,7 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T if (*cmd && *cmd != '"') { // if not end-of-line or comment eap->nextcmd = (char *)check_nextcmd((char_u *)cmd); if (eap->nextcmd == NULL) { - emsg(_(e_trailing)); + semsg(_(e_trailing_arg), cmd); return 0; } } diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h index 052926fa1f..e80e47bcff 100644 --- a/src/nvim/ex_cmds_defs.h +++ b/src/nvim/ex_cmds_defs.h @@ -37,34 +37,34 @@ // 4. Add documentation in ../doc/xxx.txt. Add a tag for both the short and // long name of the command. -#define EX_RANGE 0x001 // allow a linespecs -#define EX_BANG 0x002 // allow a ! after the command name -#define EX_EXTRA 0x004 // allow extra args after command name -#define EX_XFILE 0x008 // expand wildcards in extra part -#define EX_NOSPC 0x010 // no spaces allowed in the extra part -#define EX_DFLALL 0x020 // default file range is 1,$ -#define EX_WHOLEFOLD 0x040 // extend range to include whole fold also - // when less than two numbers given -#define EX_NEEDARG 0x080 // argument required -#define EX_TRLBAR 0x100 // check for trailing vertical bar -#define EX_REGSTR 0x200 // allow "x for register designation -#define EX_COUNT 0x400 // allow count in argument, after command -#define EX_NOTRLCOM 0x800 // no trailing comment allowed -#define EX_ZEROR 0x1000 // zero line number allowed -#define EX_CTRLV 0x2000 // do not remove CTRL-V from argument -#define EX_CMDARG 0x4000 // allow "+command" argument -#define EX_BUFNAME 0x8000 // accepts buffer name -#define EX_BUFUNL 0x10000 // accepts unlisted buffer too -#define EX_ARGOPT 0x20000 // allow "++opt=val" argument -#define EX_SBOXOK 0x40000 // allowed in the sandbox -#define EX_CMDWIN 0x80000 // allowed in cmdline window -#define EX_MODIFY 0x100000 // forbidden in non-'modifiable' buffer -#define EX_FLAGS 0x200000 // allow flags after count in argument -#define EX_LOCK_OK 0x1000000 // command can be executed when textlock is - // set; when missing disallows editing another - // buffer when current buffer is locked -#define EX_KEEPSCRIPT 0x4000000 // keep sctx of where command was invoked -#define EX_PREVIEW 0x8000000 // allow incremental command preview +#define EX_RANGE 0x001u // allow a linespecs +#define EX_BANG 0x002u // allow a ! after the command name +#define EX_EXTRA 0x004u // allow extra args after command name +#define EX_XFILE 0x008u // expand wildcards in extra part +#define EX_NOSPC 0x010u // no spaces allowed in the extra part +#define EX_DFLALL 0x020u // default file range is 1,$ +#define EX_WHOLEFOLD 0x040u // extend range to include whole fold also + // when less than two numbers given +#define EX_NEEDARG 0x080u // argument required +#define EX_TRLBAR 0x100u // check for trailing vertical bar +#define EX_REGSTR 0x200u // allow "x for register designation +#define EX_COUNT 0x400u // allow count in argument, after command +#define EX_NOTRLCOM 0x800u // no trailing comment allowed +#define EX_ZEROR 0x1000u // zero line number allowed +#define EX_CTRLV 0x2000u // do not remove CTRL-V from argument +#define EX_CMDARG 0x4000u // allow "+command" argument +#define EX_BUFNAME 0x8000u // accepts buffer name +#define EX_BUFUNL 0x10000u // accepts unlisted buffer too +#define EX_ARGOPT 0x20000u // allow "++opt=val" argument +#define EX_SBOXOK 0x40000u // allowed in the sandbox +#define EX_CMDWIN 0x80000u // allowed in cmdline window +#define EX_MODIFY 0x100000u // forbidden in non-'modifiable' buffer +#define EX_FLAGS 0x200000u // allow flags after count in argument +#define EX_LOCK_OK 0x1000000u // command can be executed when textlock is + // set; when missing disallows editing another + // buffer when current buffer is locked +#define EX_KEEPSCRIPT 0x4000000u // keep sctx of where command was invoked +#define EX_PREVIEW 0x8000000u // allow incremental command preview #define EX_FILES (EX_XFILE | EX_EXTRA) // multiple extra files allowed #define EX_FILE1 (EX_FILES | EX_NOSPC) // 1 file, defaults to current file #define EX_WORD1 (EX_EXTRA | EX_NOSPC) // one extra word allowed diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index a7d91a47d7..4ac9847e53 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -9,7 +9,6 @@ #include <stdlib.h> #include <string.h> -#include "nvim/api/private/helpers.h" #include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/change.h" @@ -80,24 +79,19 @@ #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/undo_defs.h" +#include "nvim/usercmd.h" #include "nvim/version.h" #include "nvim/vim.h" #include "nvim/window.h" -static char e_no_such_user_defined_command_str[] - = N_("E184: No such user-defined command: %s"); static char e_ambiguous_use_of_user_defined_command[] = N_("E464: Ambiguous use of user-defined command"); static char e_not_an_editor_command[] = N_("E492: Not an editor command"); -static char e_no_such_user_defined_command_in_current_buffer_str[] - = N_("E1237: No such user-defined command in current buffer: %s"); static int quitmore = 0; static bool ex_pressedreturn = false; -garray_T ucmds = { 0, 0, sizeof(ucmd_T), 4, NULL }; - // Struct for storing a line inside a while/for loop typedef struct { char *line; // command line @@ -1563,69 +1557,12 @@ err: return false; } -/// Execute an Ex command using parsed command line information. -/// Does not do any validation of the Ex command arguments. -/// -/// @param eap Ex-command arguments -/// @param cmdinfo Command parse information -/// @param preview Execute command preview callback instead of actual command -int execute_cmd(exarg_T *eap, CmdParseInfo *cmdinfo, bool preview) +static int execute_cmd0(int *retv, exarg_T *eap, char **errormsg, bool preview) { - char *errormsg = NULL; - int retv = 0; - -#define ERROR(msg) \ - do { \ - errormsg = msg; \ - goto end; \ - } while (0) - - cmdmod_T save_cmdmod = cmdmod; - cmdmod = cmdinfo->cmdmod; - - // Apply command modifiers - apply_cmdmod(&cmdmod); - - if (!MODIFIABLE(curbuf) && (eap->argt & EX_MODIFY) - // allow :put in terminals - && !(curbuf->terminal && eap->cmdidx == CMD_put)) { - ERROR(_(e_modifiable)); - } - if (!IS_USER_CMDIDX(eap->cmdidx)) { - if (cmdwin_type != 0 && !(eap->argt & EX_CMDWIN)) { - // Command not allowed in the command line window - ERROR(_(e_cmdwin)); - } - if (text_locked() && !(eap->argt & EX_LOCK_OK)) { - // Command not allowed when text is locked - ERROR(_(get_text_locked_msg())); - } - } - // Disallow editing another buffer when "curbuf->b_ro_locked" is set. - // Do allow ":checktime" (it is postponed). - // Do allow ":edit" (check for an argument later). - // Do allow ":file" with no arguments - if (!(eap->argt & EX_CMDWIN) - && eap->cmdidx != CMD_checktime - && eap->cmdidx != CMD_edit - && !(eap->cmdidx == CMD_file && *eap->arg == NUL) - && !IS_USER_CMDIDX(eap->cmdidx) - && curbuf_locked()) { - ERROR(_(e_cannot_edit_other_buf)); - } - - if (((eap->argt & EX_WHOLEFOLD) || eap->addr_count >= 2) && !global_busy - && eap->addr_type == ADDR_LINES) { - // Put the first line at the start of a closed fold, put the last line - // at the end of a closed fold. - (void)hasFolding(eap->line1, &eap->line1, NULL); - (void)hasFolding(eap->line2, NULL, &eap->line2); - } - // If filename expansion is enabled, expand filenames - if (cmdinfo->magic.file) { - if (expand_filename(eap, eap->cmdlinep, &errormsg) == FAIL) { - goto end; + if (eap->argt & EX_XFILE) { + if (expand_filename(eap, eap->cmdlinep, errormsg) == FAIL) { + return FAIL; } } @@ -1670,29 +1607,105 @@ int execute_cmd(exarg_T *eap, CmdParseInfo *cmdinfo, bool preview) eap->arg = eap->args[0]; } if (eap->line2 < 0) { // failed - goto end; + return FAIL; } } + // The :try command saves the emsg_silent flag, reset it here when + // ":silent! try" was used, it should only apply to :try itself. + if (eap->cmdidx == CMD_try && cmdmod.cmod_did_esilent > 0) { + emsg_silent -= cmdmod.cmod_did_esilent; + if (emsg_silent < 0) { + emsg_silent = 0; + } + cmdmod.cmod_did_esilent = 0; + } + // Execute the command if (IS_USER_CMDIDX(eap->cmdidx)) { // Execute a user-defined command. - retv = do_ucmd(eap, preview); + *retv = do_ucmd(eap, preview); } else { - // Call the function to execute the command or the preview callback. + // Call the function to execute the builtin command or the preview callback. eap->errmsg = NULL; - if (preview) { - retv = (cmdnames[eap->cmdidx].cmd_preview_func)(eap, cmdpreview_get_ns(), - cmdpreview_get_bufnr()); + *retv = (cmdnames[eap->cmdidx].cmd_preview_func)(eap, cmdpreview_get_ns(), + cmdpreview_get_bufnr()); } else { (cmdnames[eap->cmdidx].cmd_func)(eap); } if (eap->errmsg != NULL) { - errormsg = _(eap->errmsg); + *errormsg = eap->errmsg; } } + return OK; +} + +/// Execute an Ex command using parsed command line information. +/// Does not do any validation of the Ex command arguments. +/// +/// @param eap Ex-command arguments +/// @param cmdinfo Command parse information +/// @param preview Execute command preview callback instead of actual command +int execute_cmd(exarg_T *eap, CmdParseInfo *cmdinfo, bool preview) +{ + char *errormsg = NULL; + int retv = 0; + +#define ERROR(msg) \ + do { \ + errormsg = msg; \ + goto end; \ + } while (0) + + cmdmod_T save_cmdmod = cmdmod; + cmdmod = cmdinfo->cmdmod; + + // Apply command modifiers + apply_cmdmod(&cmdmod); + + if (!MODIFIABLE(curbuf) && (eap->argt & EX_MODIFY) + // allow :put in terminals + && !(curbuf->terminal && eap->cmdidx == CMD_put)) { + ERROR(_(e_modifiable)); + } + if (!IS_USER_CMDIDX(eap->cmdidx)) { + if (cmdwin_type != 0 && !(eap->argt & EX_CMDWIN)) { + // Command not allowed in the command line window + ERROR(_(e_cmdwin)); + } + if (text_locked() && !(eap->argt & EX_LOCK_OK)) { + // Command not allowed when text is locked + ERROR(_(get_text_locked_msg())); + } + } + // Disallow editing another buffer when "curbuf->b_ro_locked" is set. + // Do allow ":checktime" (it is postponed). + // Do allow ":edit" (check for an argument later). + // Do allow ":file" with no arguments + if (!(eap->argt & EX_CMDWIN) + && eap->cmdidx != CMD_checktime + && eap->cmdidx != CMD_edit + && !(eap->cmdidx == CMD_file && *eap->arg == NUL) + && !IS_USER_CMDIDX(eap->cmdidx) + && curbuf_locked()) { + ERROR(_(e_cannot_edit_other_buf)); + } + + correct_range(eap); + + if (((eap->argt & EX_WHOLEFOLD) || eap->addr_count >= 2) && !global_busy + && eap->addr_type == ADDR_LINES) { + // Put the first line at the start of a closed fold, put the last line + // at the end of a closed fold. + (void)hasFolding(eap->line1, &eap->line1, NULL); + (void)hasFolding(eap->line2, NULL, &eap->line2); + } + + // Execute the command + execute_cmd0(&retv, eap, &errormsg, preview); + end: if (errormsg != NULL && *errormsg != NUL) { emsg(errormsg); @@ -1704,6 +1717,142 @@ end: #undef ERROR } +static void profile_cmd(const exarg_T *eap, cstack_T *cstack, LineGetter fgetline, void *cookie) +{ + // Count this line for profiling if skip is TRUE. + if (do_profiling == PROF_YES + && (!eap->skip || cstack->cs_idx == 0 + || (cstack->cs_idx > 0 + && (cstack->cs_flags[cstack->cs_idx - 1] & CSF_ACTIVE)))) { + int skip = did_emsg || got_int || current_exception; + + if (eap->cmdidx == CMD_catch) { + skip = !skip && !(cstack->cs_idx >= 0 + && (cstack->cs_flags[cstack->cs_idx] & CSF_THROWN) + && !(cstack->cs_flags[cstack->cs_idx] & CSF_CAUGHT)); + } else if (eap->cmdidx == CMD_else || eap->cmdidx == CMD_elseif) { + skip = skip || !(cstack->cs_idx >= 0 + && !(cstack->cs_flags[cstack->cs_idx] + & (CSF_ACTIVE | CSF_TRUE))); + } else if (eap->cmdidx == CMD_finally) { + skip = false; + } else if (eap->cmdidx != CMD_endif + && eap->cmdidx != CMD_endfor + && eap->cmdidx != CMD_endtry + && eap->cmdidx != CMD_endwhile) { + skip = eap->skip; + } + + if (!skip) { + if (getline_equal(fgetline, cookie, get_func_line)) { + func_line_exec(getline_cookie(fgetline, cookie)); + } else if (getline_equal(fgetline, cookie, getsourceline)) { + script_line_exec(); + } + } + } +} + +static bool skip_cmd(const exarg_T *eap) +{ + // Skip the command when it's not going to be executed. + // The commands like :if, :endif, etc. always need to be executed. + // Also make an exception for commands that handle a trailing command + // themselves. + if (eap->skip) { + switch (eap->cmdidx) { + // commands that need evaluation + case CMD_while: + case CMD_endwhile: + case CMD_for: + case CMD_endfor: + case CMD_if: + case CMD_elseif: + case CMD_else: + case CMD_endif: + case CMD_try: + case CMD_catch: + case CMD_finally: + case CMD_endtry: + case CMD_function: + break; + + // Commands that handle '|' themselves. Check: A command should + // either have the EX_TRLBAR flag, appear in this list or appear in + // the list at ":help :bar". + case CMD_aboveleft: + case CMD_and: + case CMD_belowright: + case CMD_botright: + case CMD_browse: + case CMD_call: + case CMD_confirm: + case CMD_const: + case CMD_delfunction: + case CMD_djump: + case CMD_dlist: + case CMD_dsearch: + case CMD_dsplit: + case CMD_echo: + case CMD_echoerr: + case CMD_echomsg: + case CMD_echon: + case CMD_eval: + case CMD_execute: + case CMD_filter: + case CMD_help: + case CMD_hide: + case CMD_ijump: + case CMD_ilist: + case CMD_isearch: + case CMD_isplit: + case CMD_keepalt: + case CMD_keepjumps: + case CMD_keepmarks: + case CMD_keeppatterns: + case CMD_leftabove: + case CMD_let: + case CMD_lockmarks: + case CMD_lockvar: + case CMD_lua: + case CMD_match: + case CMD_mzscheme: + case CMD_noautocmd: + case CMD_noswapfile: + case CMD_perl: + case CMD_psearch: + case CMD_python: + case CMD_py3: + case CMD_python3: + case CMD_pythonx: + case CMD_pyx: + case CMD_return: + case CMD_rightbelow: + case CMD_ruby: + case CMD_silent: + case CMD_smagic: + case CMD_snomagic: + case CMD_substitute: + case CMD_syntax: + case CMD_tab: + case CMD_tcl: + case CMD_throw: + case CMD_tilde: + case CMD_topleft: + case CMD_unlet: + case CMD_unlockvar: + case CMD_verbose: + case CMD_vertical: + case CMD_wincmd: + break; + + default: + return true; + } + } + return false; +} + /// Execute one Ex command. /// /// If 'sourcing' is TRUE, the command will be included in the error message. @@ -1724,16 +1873,11 @@ end: static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter fgetline, void *cookie) { - char *p; - linenr_T lnum; char *errormsg = NULL; // error message - char *after_modifier = NULL; - exarg_T ea; - cmdmod_T save_cmdmod; const int save_reg_executing = reg_executing; const bool save_pending_end_reg_executing = pending_end_reg_executing; - char *cmd; + exarg_T ea; memset(&ea, 0, sizeof(ea)); ea.line1 = 1; ea.line2 = 1; @@ -1749,11 +1893,9 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter --quitmore; } - /* - * Reset browse, confirm, etc.. They are restored when returning, for - * recursive calls. - */ - save_cmdmod = cmdmod; + // Reset browse, confirm, etc.. They are restored when returning, for + // recursive calls. + cmdmod_T save_cmdmod = cmdmod; // "#!anything" is handled like a comment. if ((*cmdlinep)[0] == '#' && (*cmdlinep)[1] == '!') { @@ -1775,7 +1917,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter } apply_cmdmod(&cmdmod); - after_modifier = ea.cmd; + char *after_modifier = ea.cmd; ea.skip = (did_emsg || got_int @@ -1786,45 +1928,14 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter // 3. Skip over the range to find the command. Let "p" point to after it. // // We need the command to know what kind of range it uses. - cmd = ea.cmd; + char *cmd = ea.cmd; ea.cmd = skip_range(ea.cmd, NULL); if (*ea.cmd == '*') { ea.cmd = skipwhite(ea.cmd + 1); } - p = find_ex_command(&ea, NULL); - - // Count this line for profiling if skip is TRUE. - if (do_profiling == PROF_YES - && (!ea.skip || cstack->cs_idx == 0 - || (cstack->cs_idx > 0 - && (cstack->cs_flags[cstack->cs_idx - 1] & CSF_ACTIVE)))) { - int skip = did_emsg || got_int || current_exception; - - if (ea.cmdidx == CMD_catch) { - skip = !skip && !(cstack->cs_idx >= 0 - && (cstack->cs_flags[cstack->cs_idx] & CSF_THROWN) - && !(cstack->cs_flags[cstack->cs_idx] & CSF_CAUGHT)); - } else if (ea.cmdidx == CMD_else || ea.cmdidx == CMD_elseif) { - skip = skip || !(cstack->cs_idx >= 0 - && !(cstack->cs_flags[cstack->cs_idx] - & (CSF_ACTIVE | CSF_TRUE))); - } else if (ea.cmdidx == CMD_finally) { - skip = false; - } else if (ea.cmdidx != CMD_endif - && ea.cmdidx != CMD_endfor - && ea.cmdidx != CMD_endtry - && ea.cmdidx != CMD_endwhile) { - skip = ea.skip; - } + char *p = find_ex_command(&ea, NULL); - if (!skip) { - if (getline_equal(fgetline, cookie, get_func_line)) { - func_line_exec(getline_cookie(fgetline, cookie)); - } else if (getline_equal(fgetline, cookie, getsourceline)) { - script_line_exec(); - } - } - } + profile_cmd(&ea, cstack, fgetline, cookie); // May go to debug mode. If this happens and the ">quit" debug command is // used, throw an interrupt exception and skip the next command. @@ -1855,19 +1966,13 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter goto doend; } - /* - * 5. Parse the command. - */ + // 5. Parse the command. - /* - * Skip ':' and any white space - */ + // Skip ':' and any white space ea.cmd = skip_colon_white(ea.cmd, true); - /* - * If we got a line, but no command, then go to the line. - * If we find a '|' or '\n' we set ea.nextcmd. - */ + // If we got a line, but no command, then go to the line. + // If we find a '|' or '\n' we set ea.nextcmd. if (*ea.cmd == NUL || *ea.cmd == '"' || (ea.nextcmd = (char *)check_nextcmd((char_u *)ea.cmd)) != NULL) { // strange vi behaviour: @@ -1926,6 +2031,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter } goto doend; } + // Check for wrong commands. if (ea.cmdidx == CMD_SIZE) { if (!ea.skip) { @@ -1947,12 +2053,12 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter const int ni = is_cmd_ni(ea.cmdidx); // Forced commands. - if (*p == '!' && ea.cmdidx != CMD_substitute - && ea.cmdidx != CMD_smagic && ea.cmdidx != CMD_snomagic) { + ea.forceit = *p == '!' + && ea.cmdidx != CMD_substitute + && ea.cmdidx != CMD_smagic + && ea.cmdidx != CMD_snomagic; + if (ea.forceit) { p++; - ea.forceit = true; - } else { - ea.forceit = false; } // 6. Parse arguments. Then check for errors. @@ -2012,10 +2118,8 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter goto doend; } - /* - * Don't complain about the range if it is not used - * (could happen if line_count is accidentally set to 0). - */ + // Don't complain about the range if it is not used + // (could happen if line_count is accidentally set to 0). if (!ea.skip && !ni && (ea.argt & EX_RANGE)) { // If the range is backwards, ask for confirmation and, if given, swap // ea.line1 & ea.line2 so it's forwards again. @@ -2030,7 +2134,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter goto doend; } } - lnum = ea.line1; + linenr_T lnum = ea.line1; ea.line1 = ea.line2; ea.line2 = lnum; } @@ -2054,34 +2158,24 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter (void)hasFolding(ea.line2, NULL, &ea.line2); } - /* - * For the ":make" and ":grep" commands we insert the 'makeprg'/'grepprg' - * option here, so things like % get expanded. - */ + // For the ":make" and ":grep" commands we insert the 'makeprg'/'grepprg' + // option here, so things like % get expanded. p = replace_makeprg(&ea, p, cmdlinep); if (p == NULL) { goto doend; } - /* - * Skip to start of argument. - * Don't do this for the ":!" command, because ":!! -l" needs the space. - */ - if (ea.cmdidx == CMD_bang) { - ea.arg = p; - } else { - ea.arg = skipwhite(p); - } + // Skip to start of argument. + // Don't do this for the ":!" command, because ":!! -l" needs the space. + ea.arg = ea.cmdidx == CMD_bang ? p : skipwhite(p); // ":file" cannot be run with an argument when "curbuf->b_ro_locked" is set if (ea.cmdidx == CMD_file && *ea.arg != NUL && curbuf_locked()) { goto doend; } - /* - * Check for "++opt=val" argument. - * Must be first, allow ":w ++enc=utf8 !cmd" - */ + // Check for "++opt=val" argument. + // Must be first, allow ":w ++enc=utf8 !cmd" if (ea.argt & EX_ARGOPT) { while (ea.arg[0] == '+' && ea.arg[1] == '+') { if (getargopt(&ea) == FAIL && !ni) { @@ -2103,9 +2197,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter ++ea.arg; ea.usefilter = TRUE; } - } - - if (ea.cmdidx == CMD_read) { + } else if (ea.cmdidx == CMD_read) { if (ea.forceit) { ea.usefilter = TRUE; // :r! filter if ea.forceit ea.forceit = FALSE; @@ -2113,9 +2205,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter ++ea.arg; ea.usefilter = TRUE; } - } - - if (ea.cmdidx == CMD_lshift || ea.cmdidx == CMD_rshift) { + } else if (ea.cmdidx == CMD_lshift || ea.cmdidx == CMD_rshift) { ea.amount = 1; while (*ea.arg == *ea.cmd) { // count number of '>' or '<' ea.arg++; @@ -2124,18 +2214,14 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter ea.arg = skipwhite(ea.arg); } - /* - * Check for "+command" argument, before checking for next command. - * Don't do this for ":read !cmd" and ":write !cmd". - */ + // Check for "+command" argument, before checking for next command. + // Don't do this for ":read !cmd" and ":write !cmd". if ((ea.argt & EX_CMDARG) && !ea.usefilter) { ea.do_ecmd_cmd = getargcmd(&ea.arg); } - /* - * Check for '|' to separate commands and '"' to start comments. - * Don't do this for ":read !cmd" and ":write !cmd". - */ + // Check for '|' to separate commands and '"' to start comments. + // Don't do this for ":read !cmd" and ":write !cmd". if ((ea.argt & EX_TRLBAR) && !ea.usefilter) { separate_nextcmd(&ea); } else if (ea.cmdidx == CMD_bang @@ -2146,18 +2232,18 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter // Check for <newline> to end a shell command. // Also do this for ":read !cmd", ":write !cmd" and ":global". // Any others? - for (p = ea.arg; *p; p++) { + for (char *s = ea.arg; *s; s++) { // Remove one backslash before a newline, so that it's possible to // pass a newline to the shell and also a newline that is preceded // with a backslash. This makes it impossible to end a shell // command in a backslash, but that doesn't appear useful. // Halving the number of backslashes is incompatible with previous // versions. - if (*p == '\\' && p[1] == '\n') { - STRMOVE(p, p + 1); - } else if (*p == '\n') { - ea.nextcmd = p + 1; - *p = NUL; + if (*s == '\\' && s[1] == '\n') { + STRMOVE(s, s + 1); + } else if (*s == '\n') { + ea.nextcmd = s + 1; + *s = NUL; break; } } @@ -2173,16 +2259,14 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter goto doend; } - /* - * Check for flags: 'l', 'p' and '#'. - */ + // Check for flags: 'l', 'p' and '#'. if (ea.argt & EX_FLAGS) { get_flags(&ea); } if (!ni && !(ea.argt & EX_EXTRA) && *ea.arg != NUL && *ea.arg != '"' && (*ea.arg != '|' || (ea.argt & EX_TRLBAR) == 0)) { // no arguments allowed but there is something - errormsg = _(e_trailing); + errormsg = ex_errmsg(e_trailing_arg, ea.arg); goto doend; } @@ -2191,173 +2275,21 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter goto doend; } - /* - * Skip the command when it's not going to be executed. - * The commands like :if, :endif, etc. always need to be executed. - * Also make an exception for commands that handle a trailing command - * themselves. - */ - if (ea.skip) { - switch (ea.cmdidx) { - // commands that need evaluation - case CMD_while: - case CMD_endwhile: - case CMD_for: - case CMD_endfor: - case CMD_if: - case CMD_elseif: - case CMD_else: - case CMD_endif: - case CMD_try: - case CMD_catch: - case CMD_finally: - case CMD_endtry: - case CMD_function: - break; - - // Commands that handle '|' themselves. Check: A command should - // either have the EX_TRLBAR flag, appear in this list or appear in - // the list at ":help :bar". - case CMD_aboveleft: - case CMD_and: - case CMD_belowright: - case CMD_botright: - case CMD_browse: - case CMD_call: - case CMD_confirm: - case CMD_const: - case CMD_delfunction: - case CMD_djump: - case CMD_dlist: - case CMD_dsearch: - case CMD_dsplit: - case CMD_echo: - case CMD_echoerr: - case CMD_echomsg: - case CMD_echon: - case CMD_eval: - case CMD_execute: - case CMD_filter: - case CMD_help: - case CMD_hide: - case CMD_ijump: - case CMD_ilist: - case CMD_isearch: - case CMD_isplit: - case CMD_keepalt: - case CMD_keepjumps: - case CMD_keepmarks: - case CMD_keeppatterns: - case CMD_leftabove: - case CMD_let: - case CMD_lockmarks: - case CMD_lockvar: - case CMD_lua: - case CMD_match: - case CMD_mzscheme: - case CMD_noautocmd: - case CMD_noswapfile: - case CMD_perl: - case CMD_psearch: - case CMD_python: - case CMD_py3: - case CMD_python3: - case CMD_pythonx: - case CMD_pyx: - case CMD_return: - case CMD_rightbelow: - case CMD_ruby: - case CMD_silent: - case CMD_smagic: - case CMD_snomagic: - case CMD_substitute: - case CMD_syntax: - case CMD_tab: - case CMD_tcl: - case CMD_throw: - case CMD_tilde: - case CMD_topleft: - case CMD_unlet: - case CMD_unlockvar: - case CMD_verbose: - case CMD_vertical: - case CMD_wincmd: - break; - - default: - goto doend; - } - } - - if (ea.argt & EX_XFILE) { - if (expand_filename(&ea, cmdlinep, &errormsg) == FAIL) { - goto doend; - } - } - - /* - * Accept buffer name. Cannot be used at the same time with a buffer - * number. Don't do this for a user command. - */ - if ((ea.argt & EX_BUFNAME) && *ea.arg != NUL && ea.addr_count == 0 - && !IS_USER_CMDIDX(ea.cmdidx)) { - /* - * :bdelete, :bwipeout and :bunload take several arguments, separated - * by spaces: find next space (skipping over escaped characters). - * The others take one argument: ignore trailing spaces. - */ - if (ea.cmdidx == CMD_bdelete || ea.cmdidx == CMD_bwipeout - || ea.cmdidx == CMD_bunload) { - p = skiptowhite_esc(ea.arg); - } else { - p = ea.arg + STRLEN(ea.arg); - while (p > ea.arg && ascii_iswhite(p[-1])) { - p--; - } - } - ea.line2 = buflist_findpat(ea.arg, p, (ea.argt & EX_BUFUNL) != 0, - false, false); - if (ea.line2 < 0) { // failed - goto doend; - } - ea.addr_count = 1; - ea.arg = skipwhite(p); - } - - // The :try command saves the emsg_silent flag, reset it here when - // ":silent! try" was used, it should only apply to :try itself. - if (ea.cmdidx == CMD_try && cmdmod.cmod_did_esilent > 0) { - emsg_silent -= cmdmod.cmod_did_esilent; - if (emsg_silent < 0) { - emsg_silent = 0; - } - cmdmod.cmod_did_esilent = 0; + if (skip_cmd(&ea)) { + goto doend; } // 7. Execute the command. - if (IS_USER_CMDIDX(ea.cmdidx)) { - /* - * Execute a user-defined command. - */ - do_ucmd(&ea, false); - } else { - /* - * Call the function to execute the command. - */ - ea.errmsg = NULL; - (cmdnames[ea.cmdidx].cmd_func)(&ea); - if (ea.errmsg != NULL) { - errormsg = _(ea.errmsg); - } + int retv = 0; + if (execute_cmd0(&retv, &ea, &errormsg, false) == FAIL) { + goto doend; } - /* - * If the command just executed called do_cmdline(), any throw or ":return" - * or ":finish" encountered there must also check the cstack of the still - * active do_cmdline() that called this do_one_cmd(). Rethrow an uncaught - * exception, or reanimate a returned function or finished script file and - * return or finish it again. - */ + // If the command just executed called do_cmdline(), any throw or ":return" + // or ":finish" encountered there must also check the cstack of the still + // active do_cmdline() that called this do_one_cmd(). Rethrow an uncaught + // exception, or reanimate a returned function or finished script file and + // return or finish it again. if (need_rethrow) { do_throw(cstack); } else if (check_cstack) { @@ -2383,7 +2315,7 @@ doend: STRCPY(IObuff, errormsg); errormsg = (char *)IObuff; } - append_command(*cmdlinep); + append_command(*ea.cmdlinep); } emsg(errormsg); } @@ -3083,115 +3015,6 @@ char *find_ex_command(exarg_T *eap, int *full) return p; } -/// Search for a user command that matches "eap->cmd". -/// Return cmdidx in "eap->cmdidx", flags in "eap->argt", idx in "eap->useridx". -/// Return a pointer to just after the command. -/// Return NULL if there is no matching command. -/// -/// @param *p end of the command (possibly including count) -/// @param full set to TRUE for a full match -/// @param xp used for completion, NULL otherwise -/// @param complp completion flags or NULL -static char *find_ucmd(exarg_T *eap, char *p, int *full, expand_T *xp, int *complp) -{ - int len = (int)(p - eap->cmd); - int j, k, matchlen = 0; - ucmd_T *uc; - bool found = false; - bool possible = false; - char *cp, *np; // Point into typed cmd and test name - garray_T *gap; - bool amb_local = false; // Found ambiguous buffer-local command, - // only full match global is accepted. - - // Look for buffer-local user commands first, then global ones. - gap = &prevwin_curwin()->w_buffer->b_ucmds; - for (;;) { - for (j = 0; j < gap->ga_len; j++) { - uc = USER_CMD_GA(gap, j); - cp = eap->cmd; - np = (char *)uc->uc_name; - k = 0; - while (k < len && *np != NUL && *cp++ == *np++) { - k++; - } - if (k == len || (*np == NUL && ascii_isdigit(eap->cmd[k]))) { - /* If finding a second match, the command is ambiguous. But - * not if a buffer-local command wasn't a full match and a - * global command is a full match. */ - if (k == len && found && *np != NUL) { - if (gap == &ucmds) { - return NULL; - } - amb_local = true; - } - - if (!found || (k == len && *np == NUL)) { - /* If we matched up to a digit, then there could - * be another command including the digit that we - * should use instead. - */ - if (k == len) { - found = true; - } else { - possible = true; - } - - if (gap == &ucmds) { - eap->cmdidx = CMD_USER; - } else { - eap->cmdidx = CMD_USER_BUF; - } - eap->argt = uc->uc_argt; - eap->useridx = j; - eap->addr_type = uc->uc_addr_type; - - if (complp != NULL) { - *complp = uc->uc_compl; - } - if (xp != NULL) { - xp->xp_luaref = uc->uc_compl_luaref; - xp->xp_arg = (char *)uc->uc_compl_arg; - xp->xp_script_ctx = uc->uc_script_ctx; - xp->xp_script_ctx.sc_lnum += sourcing_lnum; - } - /* Do not search for further abbreviations - * if this is an exact match. */ - matchlen = k; - if (k == len && *np == NUL) { - if (full != NULL) { - *full = TRUE; - } - amb_local = false; - break; - } - } - } - } - - // Stop if we found a full match or searched all. - if (j < gap->ga_len || gap == &ucmds) { - break; - } - gap = &ucmds; - } - - // Only found ambiguous matches. - if (amb_local) { - if (xp != NULL) { - xp->xp_context = EXPAND_UNSUCCESSFUL; - } - return NULL; - } - - /* The match we found may be followed immediately by a number. Move "p" - * back to point to it. */ - if (found || possible) { - return p + (matchlen - len); - } - return p; -} - static struct cmdmod { char *name; int minlen; @@ -3755,50 +3578,7 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff) */ case CMD_command: - // Check for attributes - while (*arg == '-') { - arg++; // Skip "-". - p = (const char *)skiptowhite((const char_u *)arg); - if (*p == NUL) { - // Cursor is still in the attribute. - p = strchr(arg, '='); - if (p == NULL) { - // No "=", so complete attribute names. - xp->xp_context = EXPAND_USER_CMD_FLAGS; - xp->xp_pattern = (char *)arg; - return NULL; - } - - // For the -complete, -nargs and -addr attributes, we complete - // their arguments as well. - if (STRNICMP(arg, "complete", p - arg) == 0) { - xp->xp_context = EXPAND_USER_COMPLETE; - xp->xp_pattern = (char *)p + 1; - return NULL; - } else if (STRNICMP(arg, "nargs", p - arg) == 0) { - xp->xp_context = EXPAND_USER_NARGS; - xp->xp_pattern = (char *)p + 1; - return NULL; - } else if (STRNICMP(arg, "addr", p - arg) == 0) { - xp->xp_context = EXPAND_USER_ADDR_TYPE; - xp->xp_pattern = (char *)p + 1; - return NULL; - } - return NULL; - } - arg = (const char *)skipwhite(p); - } - - // After the attributes comes the new command name. - p = (const char *)skiptowhite((const char_u *)arg); - if (*p == NUL) { - xp->xp_context = EXPAND_USER_COMMANDS; - xp->xp_pattern = (char *)arg; - break; - } - - // And finally comes a normal command. - return (const char *)skipwhite(p); + return set_context_in_user_cmd(xp, arg); case CMD_delcommand: xp->xp_context = EXPAND_USER_COMMANDS; @@ -3994,6 +3774,7 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff) xp->xp_context = EXPAND_DIFF_BUFFERS; xp->xp_pattern = (char *)arg; break; + case CMD_USER: case CMD_USER_BUF: if (context != EXPAND_NOTHING) { @@ -4015,7 +3796,7 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff) // argument starts after a space arg = p + 1; } else if (*p == '\\' && *(p + 1) != NUL) { - p++; // skip over escaped character + p++; // skip over escaped character } MB_PTR_ADV(p); } @@ -4600,7 +4381,7 @@ static void get_flags(exarg_T *eap) void ex_ni(exarg_T *eap) { if (!eap->skip) { - eap->errmsg = N_("E319: The command is not available in this version"); + eap->errmsg = _("E319: The command is not available in this version"); } } @@ -5237,7 +5018,7 @@ static int get_tabpage_arg(exarg_T *eap) } else if (p == p_save || *p_save == '-' || *p != NUL || tab_number > LAST_TAB_NR) { // No numbers as argument. - eap->errmsg = e_invarg; + eap->errmsg = ex_errmsg(e_invarg2, eap->arg); goto theend; } } else { @@ -5245,20 +5026,20 @@ static int get_tabpage_arg(exarg_T *eap) tab_number = 1; } else if (p == p_save || *p_save == '-' || *p != NUL || tab_number == 0) { // No numbers as argument. - eap->errmsg = e_invarg; + eap->errmsg = ex_errmsg(e_invarg2, eap->arg); goto theend; } tab_number = tab_number * relative + tabpage_index(curtab); if (!unaccept_arg0 && relative == -1) { - --tab_number; + tab_number--; } } if (tab_number < unaccept_arg0 || tab_number > LAST_TAB_NR) { - eap->errmsg = e_invarg; + eap->errmsg = ex_errmsg(e_invarg2, eap->arg); } } else if (eap->addr_count > 0) { if (unaccept_arg0 && eap->line2 == 0) { - eap->errmsg = e_invrange; + eap->errmsg = _(e_invrange); tab_number = 0; } else { tab_number = (int)eap->line2; @@ -5267,7 +5048,7 @@ static int get_tabpage_arg(exarg_T *eap) if (!unaccept_arg0 && *cmdp == '-') { tab_number--; if (tab_number < unaccept_arg0) { - eap->errmsg = e_invarg; + eap->errmsg = _(e_invrange); } } } @@ -5297,7 +5078,7 @@ static void ex_autocmd(exarg_T *eap) // directory for security reasons. if (secure) { secure = 2; - eap->errmsg = e_curdir; + eap->errmsg = _(e_curdir); } else if (eap->cmdidx == CMD_autocmd) { do_autocmd(eap->arg, eap->forceit); } else { @@ -5338,7 +5119,7 @@ static void ex_bunload(exarg_T *eap) static void ex_buffer(exarg_T *eap) { if (*eap->arg) { - eap->errmsg = e_trailing; + eap->errmsg = ex_errmsg(e_trailing_arg, eap->arg); } else { if (eap->addr_count == 0) { // default is current buffer goto_buffer(eap, DOBUF_CURRENT, FORWARD, 0); @@ -5481,1464 +5262,6 @@ char *get_command_name(expand_T *xp, int idx) return cmdnames[idx].cmd_name; } -/// Check for a valid user command name -/// -/// If the given {name} is valid, then a pointer to the end of the valid name is returned. -/// Otherwise, returns NULL. -char *uc_validate_name(char *name) -{ - if (ASCII_ISALPHA(*name)) { - while (ASCII_ISALNUM(*name)) { - name++; - } - } - if (!ends_excmd(*name) && !ascii_iswhite(*name)) { - return NULL; - } - - return name; -} - -/// Create a new user command {name}, if one doesn't already exist. -/// -/// This function takes ownership of compl_arg, compl_luaref, and luaref. -/// -/// @return OK if the command is created, FAIL otherwise. -int uc_add_command(char *name, size_t name_len, char *rep, uint32_t argt, long def, int flags, - int compl, char *compl_arg, LuaRef compl_luaref, LuaRef preview_luaref, - cmd_addr_T addr_type, LuaRef luaref, bool force) - FUNC_ATTR_NONNULL_ARG(1, 3) -{ - ucmd_T *cmd = NULL; - int i; - int cmp = 1; - char *rep_buf = NULL; - garray_T *gap; - - replace_termcodes(rep, STRLEN(rep), &rep_buf, 0, NULL, CPO_TO_CPO_FLAGS); - if (rep_buf == NULL) { - // Can't replace termcodes - try using the string as is - rep_buf = xstrdup(rep); - } - - // get address of growarray: global or in curbuf - if (flags & UC_BUFFER) { - gap = &curbuf->b_ucmds; - if (gap->ga_itemsize == 0) { - ga_init(gap, (int)sizeof(ucmd_T), 4); - } - } else { - gap = &ucmds; - } - - // Search for the command in the already defined commands. - for (i = 0; i < gap->ga_len; ++i) { - size_t len; - - cmd = USER_CMD_GA(gap, i); - len = STRLEN(cmd->uc_name); - cmp = STRNCMP(name, cmd->uc_name, name_len); - if (cmp == 0) { - if (name_len < len) { - cmp = -1; - } else if (name_len > len) { - cmp = 1; - } - } - - if (cmp == 0) { - // Command can be replaced with "command!" and when sourcing the - // same script again, but only once. - if (!force - && (cmd->uc_script_ctx.sc_sid != current_sctx.sc_sid - || cmd->uc_script_ctx.sc_seq == current_sctx.sc_seq)) { - semsg(_("E174: Command already exists: add ! to replace it: %s"), - name); - goto fail; - } - - XFREE_CLEAR(cmd->uc_rep); - XFREE_CLEAR(cmd->uc_compl_arg); - NLUA_CLEAR_REF(cmd->uc_luaref); - NLUA_CLEAR_REF(cmd->uc_compl_luaref); - NLUA_CLEAR_REF(cmd->uc_preview_luaref); - break; - } - - // Stop as soon as we pass the name to add - if (cmp < 0) { - break; - } - } - - // Extend the array unless we're replacing an existing command - if (cmp != 0) { - ga_grow(gap, 1); - - char *const p = xstrnsave(name, name_len); - - cmd = USER_CMD_GA(gap, i); - memmove(cmd + 1, cmd, (size_t)(gap->ga_len - i) * sizeof(ucmd_T)); - - ++gap->ga_len; - - cmd->uc_name = (char_u *)p; - } - - cmd->uc_rep = (char_u *)rep_buf; - cmd->uc_argt = argt; - cmd->uc_def = def; - cmd->uc_compl = compl; - cmd->uc_script_ctx = current_sctx; - cmd->uc_script_ctx.sc_lnum += sourcing_lnum; - nlua_set_sctx(&cmd->uc_script_ctx); - cmd->uc_compl_arg = (char_u *)compl_arg; - cmd->uc_compl_luaref = compl_luaref; - cmd->uc_preview_luaref = preview_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); - NLUA_CLEAR_REF(preview_luaref); - return FAIL; -} - -static struct { - cmd_addr_T expand; - char *name; - char *shortname; -} addr_type_complete[] = -{ - { ADDR_ARGUMENTS, "arguments", "arg" }, - { ADDR_LINES, "lines", "line" }, - { ADDR_LOADED_BUFFERS, "loaded_buffers", "load" }, - { ADDR_TABS, "tabs", "tab" }, - { ADDR_BUFFERS, "buffers", "buf" }, - { ADDR_WINDOWS, "windows", "win" }, - { ADDR_QUICKFIX, "quickfix", "qf" }, - { ADDR_OTHER, "other", "?" }, - { ADDR_NONE, NULL, NULL } -}; - -/* - * List of names for completion for ":command" with the EXPAND_ flag. - * Must be alphabetical for completion. - */ -static const char *command_complete[] = -{ - [EXPAND_ARGLIST] = "arglist", - [EXPAND_AUGROUP] = "augroup", - [EXPAND_BEHAVE] = "behave", - [EXPAND_BUFFERS] = "buffer", - [EXPAND_CHECKHEALTH] = "checkhealth", - [EXPAND_COLORS] = "color", - [EXPAND_COMMANDS] = "command", - [EXPAND_COMPILER] = "compiler", - [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", - [EXPAND_EVENTS] = "event", - [EXPAND_EXPRESSION] = "expression", - [EXPAND_FILES] = "file", - [EXPAND_FILES_IN_PATH] = "file_in_path", - [EXPAND_FILETYPE] = "filetype", - [EXPAND_FUNCTIONS] = "function", - [EXPAND_HELP] = "help", - [EXPAND_HIGHLIGHT] = "highlight", - [EXPAND_HISTORY] = "history", -#ifdef HAVE_WORKING_LIBINTL - [EXPAND_LOCALES] = "locale", -#endif - [EXPAND_LUA] = "lua", - [EXPAND_MAPCLEAR] = "mapclear", - [EXPAND_MAPPINGS] = "mapping", - [EXPAND_MENUS] = "menu", - [EXPAND_MESSAGES] = "messages", - [EXPAND_OWNSYNTAX] = "syntax", - [EXPAND_SYNTIME] = "syntime", - [EXPAND_SETTINGS] = "option", - [EXPAND_PACKADD] = "packadd", - [EXPAND_SHELLCMD] = "shellcmd", - [EXPAND_SIGN] = "sign", - [EXPAND_TAGS] = "tag", - [EXPAND_TAGS_LISTFILES] = "tag_listfiles", - [EXPAND_USER] = "user", - [EXPAND_USER_VARS] = "var", -}; - -static char *get_command_complete(int arg) -{ - if (arg >= (int)(ARRAY_SIZE(command_complete))) { - return NULL; - } else { - return (char *)command_complete[arg]; - } -} - -static void uc_list(char *name, size_t name_len) -{ - int i, j; - bool found = false; - ucmd_T *cmd; - uint32_t a; - - // In cmdwin, the alternative buffer should be used. - const garray_T *gap = &prevwin_curwin()->w_buffer->b_ucmds; - for (;;) { - for (i = 0; i < gap->ga_len; i++) { - cmd = USER_CMD_GA(gap, i); - a = cmd->uc_argt; - - // Skip commands which don't match the requested prefix and - // commands filtered out. - if (STRNCMP(name, cmd->uc_name, name_len) != 0 - || message_filtered(cmd->uc_name)) { - continue; - } - - // Put out the title first time - if (!found) { - msg_puts_title(_("\n Name Args Address " - "Complete Definition")); - } - found = true; - msg_putchar('\n'); - if (got_int) { - break; - } - - // Special cases - int len = 4; - if (a & EX_BANG) { - msg_putchar('!'); - len--; - } - if (a & EX_REGSTR) { - msg_putchar('"'); - len--; - } - if (gap != &ucmds) { - msg_putchar('b'); - len--; - } - if (a & EX_TRLBAR) { - msg_putchar('|'); - len--; - } - while (len-- > 0) { - msg_putchar(' '); - } - - msg_outtrans_attr(cmd->uc_name, HL_ATTR(HLF_D)); - len = (int)STRLEN(cmd->uc_name) + 4; - - do { - msg_putchar(' '); - len++; - } while (len < 22); - - // "over" is how much longer the name is than the column width for - // the name, we'll try to align what comes after. - const int over = len - 22; - len = 0; - - // Arguments - switch (a & (EX_EXTRA | EX_NOSPC | EX_NEEDARG)) { - case 0: - IObuff[len++] = '0'; - break; - case (EX_EXTRA): - IObuff[len++] = '*'; - break; - case (EX_EXTRA | EX_NOSPC): - IObuff[len++] = '?'; - break; - case (EX_EXTRA | EX_NEEDARG): - IObuff[len++] = '+'; - break; - case (EX_EXTRA | EX_NOSPC | EX_NEEDARG): - IObuff[len++] = '1'; - break; - } - - do { - IObuff[len++] = ' '; - } while (len < 5 - over); - - // Address / Range - if (a & (EX_RANGE | EX_COUNT)) { - if (a & EX_COUNT) { - // -count=N - snprintf((char *)IObuff + len, IOSIZE, "%" PRId64 "c", - (int64_t)cmd->uc_def); - len += (int)STRLEN(IObuff + len); - } else if (a & EX_DFLALL) { - IObuff[len++] = '%'; - } else if (cmd->uc_def >= 0) { - // -range=N - snprintf((char *)IObuff + len, IOSIZE, "%" PRId64 "", - (int64_t)cmd->uc_def); - len += (int)STRLEN(IObuff + len); - } else { - IObuff[len++] = '.'; - } - } - - do { - IObuff[len++] = ' '; - } while (len < 8 - over); - - // Address Type - for (j = 0; addr_type_complete[j].expand != ADDR_NONE; j++) { - if (addr_type_complete[j].expand != ADDR_LINES - && addr_type_complete[j].expand == cmd->uc_addr_type) { - STRCPY(IObuff + len, addr_type_complete[j].shortname); - len += (int)STRLEN(IObuff + len); - break; - } - } - - do { - IObuff[len++] = ' '; - } while (len < 13 - over); - - // Completion - char *cmd_compl = get_command_complete(cmd->uc_compl); - if (cmd_compl != NULL) { - STRCPY(IObuff + len, get_command_complete(cmd->uc_compl)); - len += (int)STRLEN(IObuff + len); - } - - do { - IObuff[len++] = ' '; - } while (len < 25 - over); - - IObuff[len] = '\0'; - msg_outtrans((char *)IObuff); - - msg_outtrans_special(cmd->uc_rep, false, - name_len == 0 ? Columns - 47 : 0); - if (p_verbose > 0) { - last_set_msg(cmd->uc_script_ctx); - } - line_breakcheck(); - if (got_int) { - break; - } - } - if (gap == &ucmds || i < gap->ga_len) { - break; - } - gap = &ucmds; - } - - if (!found) { - msg(_("No user-defined commands found")); - } -} - -static int uc_scan_attr(char *attr, size_t len, uint32_t *argt, long *def, int *flags, int *complp, - char_u **compl_arg, cmd_addr_T *addr_type_arg) - FUNC_ATTR_NONNULL_ALL -{ - char *p; - - if (len == 0) { - emsg(_("E175: No attribute specified")); - return FAIL; - } - - // First, try the simple attributes (no arguments) - if (STRNICMP(attr, "bang", len) == 0) { - *argt |= EX_BANG; - } else if (STRNICMP(attr, "buffer", len) == 0) { - *flags |= UC_BUFFER; - } else if (STRNICMP(attr, "register", len) == 0) { - *argt |= EX_REGSTR; - } else if (STRNICMP(attr, "keepscript", len) == 0) { - *argt |= EX_KEEPSCRIPT; - } else if (STRNICMP(attr, "bar", len) == 0) { - *argt |= EX_TRLBAR; - } else { - int i; - char *val = NULL; - size_t vallen = 0; - size_t attrlen = len; - - // Look for the attribute name - which is the part before any '=' - for (i = 0; i < (int)len; i++) { - if (attr[i] == '=') { - val = &attr[i + 1]; - vallen = len - (size_t)i - 1; - attrlen = (size_t)i; - break; - } - } - - if (STRNICMP(attr, "nargs", attrlen) == 0) { - if (vallen == 1) { - if (*val == '0') { - // Do nothing - this is the default; - } else if (*val == '1') { - *argt |= (EX_EXTRA | EX_NOSPC | EX_NEEDARG); - } else if (*val == '*') { - *argt |= EX_EXTRA; - } else if (*val == '?') { - *argt |= (EX_EXTRA | EX_NOSPC); - } else if (*val == '+') { - *argt |= (EX_EXTRA | EX_NEEDARG); - } else { - goto wrong_nargs; - } - } else { -wrong_nargs: - emsg(_("E176: Invalid number of arguments")); - return FAIL; - } - } else if (STRNICMP(attr, "range", attrlen) == 0) { - *argt |= EX_RANGE; - if (vallen == 1 && *val == '%') { - *argt |= EX_DFLALL; - } else if (val != NULL) { - p = val; - if (*def >= 0) { -two_count: - emsg(_("E177: Count cannot be specified twice")); - return FAIL; - } - - *def = getdigits_long(&p, true, 0); - *argt |= EX_ZEROR; - - if (p != val + vallen || vallen == 0) { -invalid_count: - emsg(_("E178: Invalid default value for count")); - return FAIL; - } - } - // default for -range is using buffer lines - if (*addr_type_arg == ADDR_NONE) { - *addr_type_arg = ADDR_LINES; - } - } else if (STRNICMP(attr, "count", attrlen) == 0) { - *argt |= (EX_COUNT | EX_ZEROR | EX_RANGE); - // default for -count is using any number - if (*addr_type_arg == ADDR_NONE) { - *addr_type_arg = ADDR_OTHER; - } - - if (val != NULL) { - p = val; - if (*def >= 0) { - goto two_count; - } - - *def = getdigits_long(&p, true, 0); - - if (p != val + vallen) { - goto invalid_count; - } - } - - if (*def < 0) { - *def = 0; - } - } else if (STRNICMP(attr, "complete", attrlen) == 0) { - if (val == NULL) { - emsg(_("E179: argument required for -complete")); - return FAIL; - } - - if (parse_compl_arg(val, (int)vallen, complp, argt, (char **)compl_arg) - == FAIL) { - return FAIL; - } - } else if (STRNICMP(attr, "addr", attrlen) == 0) { - *argt |= EX_RANGE; - if (val == NULL) { - emsg(_("E179: argument required for -addr")); - return FAIL; - } - if (parse_addr_type_arg(val, (int)vallen, addr_type_arg) == FAIL) { - return FAIL; - } - if (*addr_type_arg != ADDR_LINES) { - *argt |= EX_ZEROR; - } - } else { - char ch = attr[len]; - attr[len] = '\0'; - semsg(_("E181: Invalid attribute: %s"), attr); - attr[len] = ch; - return FAIL; - } - } - - return OK; -} - -static char e_complete_used_without_nargs[] = N_("E1208: -complete used without -nargs"); - -/// ":command ..." -static void ex_command(exarg_T *eap) -{ - char *name; - char *end; - char *p; - uint32_t argt = 0; - long def = -1; - int flags = 0; - int compl = EXPAND_NOTHING; - char *compl_arg = NULL; - cmd_addr_T addr_type_arg = ADDR_NONE; - int has_attr = (eap->arg[0] == '-'); - size_t name_len; - - p = eap->arg; - - // Check for attributes - while (*p == '-') { - p++; - end = (char *)skiptowhite((char_u *)p); - if (uc_scan_attr(p, (size_t)(end - p), &argt, &def, &flags, &compl, (char_u **)&compl_arg, - &addr_type_arg) == FAIL) { - return; - } - p = skipwhite(end); - } - - // Get the name (if any) and skip to the following argument. - name = p; - end = uc_validate_name(name); - if (!end) { - emsg(_("E182: Invalid command name")); - return; - } - name_len = (size_t)(end - name); - - // If there is nothing after the name, and no attributes were specified, - // we are listing commands - p = skipwhite(end); - if (!has_attr && ends_excmd(*p)) { - uc_list(name, name_len); - } else if (!ASCII_ISUPPER(*name)) { - emsg(_("E183: User defined commands must start with an uppercase letter")); - } else if (name_len <= 4 && STRNCMP(name, "Next", name_len) == 0) { - emsg(_("E841: Reserved name, cannot be used for user defined command")); - } else if (compl > 0 && (argt & EX_EXTRA) == 0) { - emsg(_(e_complete_used_without_nargs)); - } else { - uc_add_command(name, name_len, p, argt, def, flags, compl, compl_arg, LUA_NOREF, LUA_NOREF, - addr_type_arg, LUA_NOREF, eap->forceit); - } -} - -/// ":comclear" -/// Clear all user commands, global and for current buffer. -void ex_comclear(exarg_T *eap) -{ - uc_clear(&ucmds); - uc_clear(&curbuf->b_ucmds); -} - -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); - NLUA_CLEAR_REF(cmd->uc_preview_luaref); -} - -/// Clear all user commands for "gap". -void uc_clear(garray_T *gap) -{ - GA_DEEP_CLEAR(gap, ucmd_T, free_ucmd); -} - -static void ex_delcommand(exarg_T *eap) -{ - int i = 0; - ucmd_T *cmd = NULL; - int res = -1; - garray_T *gap; - const char *arg = eap->arg; - bool buffer_only = false; - - if (STRNCMP(arg, "-buffer", 7) == 0 && ascii_iswhite(arg[7])) { - buffer_only = true; - arg = skipwhite(arg + 7); - } - - gap = &curbuf->b_ucmds; - for (;;) { - for (i = 0; i < gap->ga_len; i++) { - cmd = USER_CMD_GA(gap, i); - res = STRCMP(arg, cmd->uc_name); - if (res <= 0) { - break; - } - } - if (gap == &ucmds || res == 0 || buffer_only) { - break; - } - gap = &ucmds; - } - - if (res != 0) { - semsg(_(buffer_only - ? e_no_such_user_defined_command_in_current_buffer_str - : e_no_such_user_defined_command_str), - arg); - return; - } - - free_ucmd(cmd); - - --gap->ga_len; - - if (i < gap->ga_len) { - memmove(cmd, cmd + 1, (size_t)(gap->ga_len - i) * sizeof(ucmd_T)); - } -} - -/// Split a string by unescaped whitespace (space & tab), used for f-args on Lua commands callback. -/// Similar to uc_split_args(), but does not allocate, add quotes, add commas and is an iterator. -/// -/// @param[in] arg String to split -/// @param[in] arglen Length of {arg} -/// @param[inout] end Index of last character of previous iteration -/// @param[out] buf Buffer to copy string into -/// @param[out] len Length of string in {buf} -/// -/// @return true if iteration is complete, else false -bool uc_split_args_iter(const char *arg, size_t arglen, size_t *end, char *buf, size_t *len) -{ - if (!arglen) { - return true; - } - - size_t pos = *end; - while (pos < arglen && ascii_iswhite(arg[pos])) { - pos++; - } - - size_t l = 0; - for (; pos < arglen - 1; pos++) { - if (arg[pos] == '\\' && (arg[pos + 1] == '\\' || ascii_iswhite(arg[pos + 1]))) { - buf[l++] = arg[++pos]; - } else { - buf[l++] = arg[pos]; - if (ascii_iswhite(arg[pos + 1])) { - *end = pos + 1; - *len = l; - return false; - } - } - } - - if (pos < arglen && !ascii_iswhite(arg[pos])) { - buf[l++] = arg[pos]; - } - - *len = l; - return true; -} - -/// split and quote args for <f-args> -static char *uc_split_args(char *arg, char **args, size_t *arglens, size_t argc, size_t *lenp) -{ - char *buf; - char *p; - char *q; - int len; - - // Precalculate length - len = 2; // Initial and final quotes - if (args == NULL) { - p = arg; - - while (*p) { - if (p[0] == '\\' && p[1] == '\\') { - len += 2; - p += 2; - } else if (p[0] == '\\' && ascii_iswhite(p[1])) { - len += 1; - p += 2; - } else if (*p == '\\' || *p == '"') { - len += 2; - p += 1; - } else if (ascii_iswhite(*p)) { - p = skipwhite(p); - if (*p == NUL) { - break; - } - len += 3; // "," - } else { - const int charlen = utfc_ptr2len(p); - - len += charlen; - p += charlen; - } - } - } else { - for (size_t i = 0; i < argc; i++) { - p = args[i]; - const char *arg_end = args[i] + arglens[i]; - - while (p < arg_end) { - if (*p == '\\' || *p == '"') { - len += 2; - p += 1; - } else { - const int charlen = utfc_ptr2len(p); - - len += charlen; - p += charlen; - } - } - - if (i != argc - 1) { - len += 3; // "," - } - } - } - - buf = xmalloc((size_t)len + 1); - - q = buf; - *q++ = '"'; - - if (args == NULL) { - p = arg; - while (*p) { - if (p[0] == '\\' && p[1] == '\\') { - *q++ = '\\'; - *q++ = '\\'; - p += 2; - } else if (p[0] == '\\' && ascii_iswhite(p[1])) { - *q++ = p[1]; - p += 2; - } else if (*p == '\\' || *p == '"') { - *q++ = '\\'; - *q++ = *p++; - } else if (ascii_iswhite(*p)) { - p = skipwhite(p); - if (*p == NUL) { - break; - } - *q++ = '"'; - *q++ = ','; - *q++ = '"'; - } else { - mb_copy_char((const char_u **)&p, (char_u **)&q); - } - } - } else { - for (size_t i = 0; i < argc; i++) { - p = args[i]; - const char *arg_end = args[i] + arglens[i]; - - while (p < arg_end) { - if (*p == '\\' || *p == '"') { - *q++ = '\\'; - *q++ = *p++; - } else { - mb_copy_char((const char_u **)&p, (char_u **)&q); - } - } - if (i != argc - 1) { - *q++ = '"'; - *q++ = ','; - *q++ = '"'; - } - } - } - - *q++ = '"'; - *q = 0; - - *lenp = (size_t)len; - return buf; -} - -static size_t add_cmd_modifier(char *buf, char *mod_str, bool *multi_mods) -{ - size_t result = STRLEN(mod_str); - if (*multi_mods) { - result++; - } - - if (buf != NULL) { - if (*multi_mods) { - STRCAT(buf, " "); - } - STRCAT(buf, mod_str); - } - - *multi_mods = true; - return result; -} - -/// Check for a <> code in a user command. -/// -/// @param code points to the '<'. "len" the length of the <> (inclusive). -/// @param buf is where the result is to be added. -/// @param cmd the user command we're expanding -/// @param eap ex arguments -/// @param split_buf points to a buffer used for splitting, caller should free it. -/// @param split_len is the length of what "split_buf" contains. -/// -/// @return the length of the replacement, which has been added to "buf". -/// Return -1 if there was no match, and only the "<" has been copied. -static size_t uc_check_code(char *code, size_t len, char *buf, ucmd_T *cmd, exarg_T *eap, - char **split_buf, size_t *split_len) -{ - size_t result = 0; - char *p = code + 1; - size_t l = len - 2; - int quote = 0; - enum { - ct_ARGS, - ct_BANG, - ct_COUNT, - ct_LINE1, - ct_LINE2, - ct_RANGE, - ct_MODS, - ct_REGISTER, - ct_LT, - ct_NONE, - } type = ct_NONE; - - if ((vim_strchr("qQfF", *p) != NULL) && p[1] == '-') { - quote = (*p == 'q' || *p == 'Q') ? 1 : 2; - p += 2; - l -= 2; - } - - l++; - if (l <= 1) { - type = ct_NONE; - } else if (STRNICMP(p, "args>", l) == 0) { - type = ct_ARGS; - } else if (STRNICMP(p, "bang>", l) == 0) { - type = ct_BANG; - } else if (STRNICMP(p, "count>", l) == 0) { - type = ct_COUNT; - } else if (STRNICMP(p, "line1>", l) == 0) { - type = ct_LINE1; - } else if (STRNICMP(p, "line2>", l) == 0) { - type = ct_LINE2; - } else if (STRNICMP(p, "range>", l) == 0) { - type = ct_RANGE; - } else if (STRNICMP(p, "lt>", l) == 0) { - type = ct_LT; - } else if (STRNICMP(p, "reg>", l) == 0 || STRNICMP(p, "register>", l) == 0) { - type = ct_REGISTER; - } else if (STRNICMP(p, "mods>", l) == 0) { - type = ct_MODS; - } - - switch (type) { - case ct_ARGS: - // Simple case first - if (*eap->arg == NUL) { - if (quote == 1) { - result = 2; - if (buf != NULL) { - STRCPY(buf, "''"); - } - } else { - result = 0; - } - break; - } - - /* When specified there is a single argument don't split it. - * Works for ":Cmd %" when % is "a b c". */ - if ((eap->argt & EX_NOSPC) && quote == 2) { - quote = 1; - } - - switch (quote) { - case 0: // No quoting, no splitting - result = STRLEN(eap->arg); - if (buf != NULL) { - STRCPY(buf, eap->arg); - } - break; - case 1: // Quote, but don't split - result = STRLEN(eap->arg) + 2; - for (p = eap->arg; *p; p++) { - if (*p == '\\' || *p == '"') { - result++; - } - } - - if (buf != NULL) { - *buf++ = '"'; - for (p = eap->arg; *p; p++) { - if (*p == '\\' || *p == '"') { - *buf++ = '\\'; - } - *buf++ = *p; - } - *buf = '"'; - } - - break; - case 2: // Quote and split (<f-args>) - // This is hard, so only do it once, and cache the result - if (*split_buf == NULL) { - *split_buf = uc_split_args(eap->arg, eap->args, eap->arglens, eap->argc, split_len); - } - - result = *split_len; - if (buf != NULL && result != 0) { - STRCPY(buf, *split_buf); - } - - break; - } - break; - - case ct_BANG: - result = eap->forceit ? 1 : 0; - if (quote) { - result += 2; - } - if (buf != NULL) { - if (quote) { - *buf++ = '"'; - } - if (eap->forceit) { - *buf++ = '!'; - } - if (quote) { - *buf = '"'; - } - } - break; - - case ct_LINE1: - case ct_LINE2: - case ct_RANGE: - case ct_COUNT: { - char num_buf[20]; - long num = (type == ct_LINE1) ? eap->line1 : - (type == ct_LINE2) ? eap->line2 : - (type == ct_RANGE) ? eap->addr_count : - (eap->addr_count > 0) ? eap->line2 : cmd->uc_def; - size_t num_len; - - sprintf(num_buf, "%" PRId64, (int64_t)num); - num_len = STRLEN(num_buf); - result = num_len; - - if (quote) { - result += 2; - } - - if (buf != NULL) { - if (quote) { - *buf++ = '"'; - } - STRCPY(buf, num_buf); - buf += num_len; - if (quote) { - *buf = '"'; - } - } - - break; - } - - case ct_MODS: - result = uc_mods(buf, &cmdmod, quote); - break; - - case ct_REGISTER: - result = eap->regname ? 1 : 0; - if (quote) { - result += 2; - } - if (buf != NULL) { - if (quote) { - *buf++ = '\''; - } - if (eap->regname) { - *buf++ = (char)eap->regname; - } - if (quote) { - *buf = '\''; - } - } - break; - - case ct_LT: - result = 1; - if (buf != NULL) { - *buf = '<'; - } - break; - - default: - // Not recognized: just copy the '<' and return -1. - result = (size_t)-1; - if (buf != NULL) { - *buf = '<'; - } - break; - } - - return result; -} - -/// Add modifiers from "cmod->cmod_split" to "buf". Set "multi_mods" when one -/// was added. -/// -/// @return the number of bytes added -size_t add_win_cmd_modifers(char *buf, const cmdmod_T *cmod, bool *multi_mods) -{ - size_t result = 0; - - // :aboveleft and :leftabove - if (cmod->cmod_split & WSP_ABOVE) { - result += add_cmd_modifier(buf, "aboveleft", multi_mods); - } - // :belowright and :rightbelow - if (cmod->cmod_split & WSP_BELOW) { - result += add_cmd_modifier(buf, "belowright", multi_mods); - } - // :botright - if (cmod->cmod_split & WSP_BOT) { - result += add_cmd_modifier(buf, "botright", multi_mods); - } - - // :tab - if (cmod->cmod_tab > 0) { - result += add_cmd_modifier(buf, "tab", multi_mods); - } - // :topleft - if (cmod->cmod_split & WSP_TOP) { - result += add_cmd_modifier(buf, "topleft", multi_mods); - } - // :vertical - if (cmod->cmod_split & WSP_VERT) { - result += add_cmd_modifier(buf, "vertical", multi_mods); - } - return result; -} - -/// Generate text for the "cmod" command modifiers. -/// If "buf" is NULL just return the length. -size_t uc_mods(char *buf, const cmdmod_T *cmod, bool quote) -{ - size_t result = 0; - bool multi_mods = false; - - typedef struct { - int flag; - char *name; - } mod_entry_T; - static mod_entry_T mod_entries[] = { - { CMOD_BROWSE, "browse" }, - { CMOD_CONFIRM, "confirm" }, - { CMOD_HIDE, "hide" }, - { CMOD_KEEPALT, "keepalt" }, - { CMOD_KEEPJUMPS, "keepjumps" }, - { CMOD_KEEPMARKS, "keepmarks" }, - { CMOD_KEEPPATTERNS, "keeppatterns" }, - { CMOD_LOCKMARKS, "lockmarks" }, - { CMOD_NOSWAPFILE, "noswapfile" }, - { CMOD_UNSILENT, "unsilent" }, - { CMOD_NOAUTOCMD, "noautocmd" }, - { CMOD_SANDBOX, "sandbox" }, - }; - - result = quote ? 2 : 0; - if (buf != NULL) { - if (quote) { - *buf++ = '"'; - } - *buf = '\0'; - } - - // the modifiers that are simple flags - for (size_t i = 0; i < ARRAY_SIZE(mod_entries); i++) { - if (cmod->cmod_flags & mod_entries[i].flag) { - result += add_cmd_modifier(buf, mod_entries[i].name, &multi_mods); - } - } - - // :silent - if (cmod->cmod_flags & CMOD_SILENT) { - result += add_cmd_modifier(buf, - (cmod->cmod_flags & CMOD_ERRSILENT) ? "silent!" : "silent", - &multi_mods); - } - // :verbose - if (cmod->cmod_verbose > 0) { - int verbose_value = cmod->cmod_verbose - 1; - if (verbose_value == 1) { - result += add_cmd_modifier(buf, "verbose", &multi_mods); - } else { - char verbose_buf[NUMBUFLEN]; - snprintf(verbose_buf, NUMBUFLEN, "%dverbose", verbose_value); - result += add_cmd_modifier(buf, verbose_buf, &multi_mods); - } - } - // flags from cmod->cmod_split - result += add_win_cmd_modifers(buf, cmod, &multi_mods); - - if (quote && buf != NULL) { - buf += result - 2; - *buf = '"'; - } - return result; -} - -static int do_ucmd(exarg_T *eap, bool preview) -{ - char *buf; - char *p; - char *q; - - char *start; - char *end = NULL; - char *ksp; - size_t len, totlen; - - size_t split_len = 0; - char *split_buf = NULL; - ucmd_T *cmd; - - if (eap->cmdidx == CMD_USER) { - cmd = USER_CMD(eap->useridx); - } else { - cmd = USER_CMD_GA(&curbuf->b_ucmds, eap->useridx); - } - - if (preview) { - assert(cmd->uc_preview_luaref > 0); - return nlua_do_ucmd(cmd, eap, true); - } - - if (cmd->uc_luaref > 0) { - nlua_do_ucmd(cmd, eap, false); - return 0; - } - - /* - * Replace <> in the command by the arguments. - * First round: "buf" is NULL, compute length, allocate "buf". - * Second round: copy result into "buf". - */ - buf = NULL; - for (;;) { - p = (char *)cmd->uc_rep; // source - q = buf; // destination - totlen = 0; - - for (;;) { - start = vim_strchr(p, '<'); - if (start != NULL) { - end = vim_strchr(start + 1, '>'); - } - if (buf != NULL) { - for (ksp = p; *ksp != NUL && (char_u)(*ksp) != K_SPECIAL; ksp++) {} - if ((char_u)(*ksp) == K_SPECIAL - && (start == NULL || ksp < start || end == NULL) - && ((char_u)ksp[1] == KS_SPECIAL && ksp[2] == KE_FILLER)) { - // K_SPECIAL has been put in the buffer as K_SPECIAL - // KS_SPECIAL KE_FILLER, like for mappings, but - // do_cmdline() doesn't handle that, so convert it back. - len = (size_t)(ksp - p); - if (len > 0) { - memmove(q, p, len); - q += len; - } - *q++ = (char)K_SPECIAL; - p = ksp + 3; - continue; - } - } - - // break if no <item> is found - if (start == NULL || end == NULL) { - break; - } - - // Include the '>' - ++end; - - // Take everything up to the '<' - len = (size_t)(start - p); - if (buf == NULL) { - totlen += len; - } else { - memmove(q, p, len); - q += len; - } - - len = uc_check_code(start, (size_t)(end - start), q, cmd, eap, &split_buf, &split_len); - if (len == (size_t)-1) { - // no match, continue after '<' - p = start + 1; - len = 1; - } else { - p = end; - } - if (buf == NULL) { - totlen += len; - } else { - q += len; - } - } - if (buf != NULL) { // second time here, finished - STRCPY(q, p); - break; - } - - totlen += STRLEN(p); // Add on the trailing characters - buf = xmalloc(totlen + 1); - } - - sctx_T save_current_sctx; - bool restore_current_sctx = false; - if ((cmd->uc_argt & EX_KEEPSCRIPT) == 0) { - restore_current_sctx = true; - save_current_sctx = current_sctx; - current_sctx.sc_sid = cmd->uc_script_ctx.sc_sid; - } - (void)do_cmdline(buf, eap->getline, eap->cookie, - DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED); - - // Careful: Do not use "cmd" here, it may have become invalid if a user - // command was added. - if (restore_current_sctx) { - current_sctx = save_current_sctx; - } - xfree(buf); - xfree(split_buf); - - return 0; -} - -static char *expand_user_command_name(int idx) -{ - return get_user_commands(NULL, idx - CMD_SIZE); -} - -/// Function given to ExpandGeneric() to obtain the list of user address type names. -char *get_user_cmd_addr_type(expand_T *xp, int idx) -{ - return addr_type_complete[idx].name; -} - -/// Function given to ExpandGeneric() to obtain the list of user command names. -char *get_user_commands(expand_T *xp FUNC_ATTR_UNUSED, int idx) - FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT -{ - // In cmdwin, the alternative buffer should be used. - const buf_T *const buf = prevwin_curwin()->w_buffer; - - if (idx < buf->b_ucmds.ga_len) { - return (char *)USER_CMD_GA(&buf->b_ucmds, idx)->uc_name; - } - - idx -= buf->b_ucmds.ga_len; - if (idx < ucmds.ga_len) { - char *name = (char *)USER_CMD(idx)->uc_name; - - for (int i = 0; i < buf->b_ucmds.ga_len; i++) { - if (STRCMP(name, USER_CMD_GA(&buf->b_ucmds, i)->uc_name) == 0) { - // global command is overruled by buffer-local one - return ""; - } - } - return name; - } - return NULL; -} - -/// Get the name of user command "idx". "cmdidx" can be CMD_USER or -/// CMD_USER_BUF. -/// -/// @return NULL if the command is not found. -static char *get_user_command_name(int idx, int cmdidx) -{ - if (cmdidx == CMD_USER && idx < ucmds.ga_len) { - return (char *)USER_CMD(idx)->uc_name; - } - if (cmdidx == CMD_USER_BUF) { - // In cmdwin, the alternative buffer should be used. - const buf_T *const buf = prevwin_curwin()->w_buffer; - - if (idx < buf->b_ucmds.ga_len) { - return (char *)USER_CMD_GA(&buf->b_ucmds, idx)->uc_name; - } - } - return NULL; -} - -/// Function given to ExpandGeneric() to obtain the list of user command -/// attributes. -char *get_user_cmd_flags(expand_T *xp, int idx) -{ - static char *user_cmd_flags[] = { "addr", "bang", "bar", - "buffer", "complete", "count", - "nargs", "range", "register", - "keepscript" }; - - if (idx >= (int)ARRAY_SIZE(user_cmd_flags)) { - return NULL; - } - return user_cmd_flags[idx]; -} - -/// Function given to ExpandGeneric() to obtain the list of values for -nargs. -char *get_user_cmd_nargs(expand_T *xp, int idx) -{ - static char *user_cmd_nargs[] = { "0", "1", "*", "?", "+" }; - - if (idx >= (int)ARRAY_SIZE(user_cmd_nargs)) { - return NULL; - } - return user_cmd_nargs[idx]; -} - -/// Function given to ExpandGeneric() to obtain the list of values for -complete. -char *get_user_cmd_complete(expand_T *xp, int idx) -{ - if (idx >= (int)ARRAY_SIZE(command_complete)) { - return NULL; - } - char *cmd_compl = get_command_complete(idx); - if (cmd_compl == NULL) { - return ""; - } else { - return cmd_compl; - } -} - -/// Parse address type argument -int parse_addr_type_arg(char *value, int vallen, cmd_addr_T *addr_type_arg) - FUNC_ATTR_NONNULL_ALL -{ - int i, a, b; - - for (i = 0; addr_type_complete[i].expand != ADDR_NONE; i++) { - a = (int)STRLEN(addr_type_complete[i].name) == vallen; - b = STRNCMP(value, addr_type_complete[i].name, vallen) == 0; - if (a && b) { - *addr_type_arg = addr_type_complete[i].expand; - break; - } - } - - if (addr_type_complete[i].expand == ADDR_NONE) { - char *err = value; - - for (i = 0; err[i] != NUL && !ascii_iswhite(err[i]); i++) {} - err[i] = NUL; - semsg(_("E180: Invalid address type value: %s"), err); - return FAIL; - } - - return OK; -} - -/// Parse a completion argument "value[vallen]". -/// The detected completion goes in "*complp", argument type in "*argt". -/// When there is an argument, for function and user defined completion, it's -/// copied to allocated memory and stored in "*compl_arg". -/// -/// @return FAIL if something is wrong. -int parse_compl_arg(const char *value, int vallen, int *complp, uint32_t *argt, char **compl_arg) - FUNC_ATTR_NONNULL_ALL -{ - const char *arg = NULL; - size_t arglen = 0; - int i; - int valend = vallen; - - // Look for any argument part - which is the part after any ',' - for (i = 0; i < vallen; ++i) { - if (value[i] == ',') { - arg = (char *)&value[i + 1]; - arglen = (size_t)(vallen - i - 1); - valend = i; - break; - } - } - - for (i = 0; i < (int)ARRAY_SIZE(command_complete); i++) { - if (get_command_complete(i) == NULL) { - continue; - } - if ((int)STRLEN(command_complete[i]) == valend - && STRNCMP(value, command_complete[i], valend) == 0) { - *complp = i; - if (i == EXPAND_BUFFERS) { - *argt |= EX_BUFNAME; - } else if (i == EXPAND_DIRECTORIES || i == EXPAND_FILES) { - *argt |= EX_XFILE; - } - break; - } - } - - if (i == (int)ARRAY_SIZE(command_complete)) { - semsg(_("E180: Invalid complete value: %s"), value); - return FAIL; - } - - if (*complp != EXPAND_USER_DEFINED && *complp != EXPAND_USER_LIST - && arg != NULL) { - emsg(_("E468: Completion argument only allowed for custom completion")); - return FAIL; - } - - if ((*complp == EXPAND_USER_DEFINED || *complp == EXPAND_USER_LIST) - && arg == NULL) { - emsg(_("E467: Custom completion requires a function argument")); - return FAIL; - } - - if (arg != NULL) { - *compl_arg = xstrnsave(arg, arglen); - } - return OK; -} - -int cmdcomplete_str_to_type(const char *complete_str) -{ - for (int i = 0; i < (int)(ARRAY_SIZE(command_complete)); i++) { - char *cmd_compl = get_command_complete(i); - if (cmd_compl == NULL) { - continue; - } - if (strcmp(complete_str, command_complete[i]) == 0) { - return i; - } - } - - return EXPAND_NOTHING; -} - static void ex_colorscheme(exarg_T *eap) { if (*eap->arg == NUL) { @@ -7620,7 +5943,7 @@ static void ex_recover(exarg_T *eap) /// Command modifier used in a wrong way. static void ex_wrongmodifier(exarg_T *eap) { - eap->errmsg = e_invcmd; + eap->errmsg = _(e_invcmd); } /// :sview [+command] file split window with new file, read-only @@ -7729,7 +6052,7 @@ static void ex_tabnext(exarg_T *eap) if (p == p_save || *p_save == '-' || *p_save == '+' || *p != NUL || tab_number == 0) { // No numbers as argument. - eap->errmsg = e_invarg; + eap->errmsg = ex_errmsg(e_invarg2, eap->arg); return; } } else { @@ -7738,7 +6061,7 @@ static void ex_tabnext(exarg_T *eap) } else { tab_number = (int)eap->line2; if (tab_number < 1) { - eap->errmsg = e_invrange; + eap->errmsg = _(e_invrange); return; } } @@ -8016,7 +6339,7 @@ void do_exedit(exarg_T *eap, win_T *old_curwin) /// ":gui" and ":gvim" when there is no GUI. static void ex_nogui(exarg_T *eap) { - eap->errmsg = N_("E25: Nvim does not have a built-in GUI"); + eap->errmsg = _("E25: Nvim does not have a built-in GUI"); } static void ex_popup(exarg_T *eap) @@ -8951,7 +7274,7 @@ static void ex_mark(exarg_T *eap) if (*eap->arg == NUL) { // No argument? emsg(_(e_argreq)); } else if (eap->arg[1] != NUL) { // more than one character? - emsg(_(e_trailing)); + semsg(_(e_trailing_arg), eap->arg); } else { pos = curwin->w_cursor; // save curwin->w_cursor curwin->w_cursor.lnum = eap->line2; @@ -9236,7 +7559,7 @@ static void ex_findpat(exarg_T *eap) // Check for trailing illegal characters. if (!ends_excmd(*p)) { - eap->errmsg = e_trailing; + eap->errmsg = ex_errmsg(e_trailing_arg, p); } else { eap->nextcmd = (char *)check_nextcmd((char_u *)p); } @@ -10087,91 +8410,6 @@ static void ex_terminal(exarg_T *eap) do_cmdline_cmd(ex_cmd); } -/// Gets a map of maps describing user-commands defined for buffer `buf` or -/// defined globally if `buf` is NULL. -/// -/// @param buf Buffer to inspect, or NULL to get global commands. -/// -/// @return Map of maps describing commands -Dictionary commands_array(buf_T *buf) -{ - Dictionary rv = ARRAY_DICT_INIT; - char str[20]; - garray_T *gap = (buf == NULL) ? &ucmds : &buf->b_ucmds; - - for (int i = 0; i < gap->ga_len; i++) { - char arg[2] = { 0, 0 }; - Dictionary d = ARRAY_DICT_INIT; - ucmd_T *cmd = USER_CMD_GA(gap, i); - - PUT(d, "name", STRING_OBJ(cstr_to_string((char *)cmd->uc_name))); - PUT(d, "definition", STRING_OBJ(cstr_to_string((char *)cmd->uc_rep))); - PUT(d, "script_id", INTEGER_OBJ(cmd->uc_script_ctx.sc_sid)); - PUT(d, "bang", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_BANG))); - PUT(d, "bar", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_TRLBAR))); - PUT(d, "register", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_REGSTR))); - PUT(d, "keepscript", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_KEEPSCRIPT))); - PUT(d, "preview", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_PREVIEW))); - - switch (cmd->uc_argt & (EX_EXTRA | EX_NOSPC | EX_NEEDARG)) { - case 0: - arg[0] = '0'; break; - case (EX_EXTRA): - arg[0] = '*'; break; - case (EX_EXTRA | EX_NOSPC): - arg[0] = '?'; break; - case (EX_EXTRA | EX_NEEDARG): - arg[0] = '+'; break; - case (EX_EXTRA | EX_NOSPC | EX_NEEDARG): - arg[0] = '1'; break; - } - PUT(d, "nargs", STRING_OBJ(cstr_to_string(arg))); - - char *cmd_compl = get_command_complete(cmd->uc_compl); - PUT(d, "complete", (cmd_compl == NULL - ? NIL : STRING_OBJ(cstr_to_string(cmd_compl)))); - PUT(d, "complete_arg", cmd->uc_compl_arg == NULL - ? NIL : STRING_OBJ(cstr_to_string((char *)cmd->uc_compl_arg))); - - Object obj = NIL; - if (cmd->uc_argt & EX_COUNT) { - if (cmd->uc_def >= 0) { - snprintf(str, sizeof(str), "%" PRId64, (int64_t)cmd->uc_def); - obj = STRING_OBJ(cstr_to_string(str)); // -count=N - } else { - obj = STRING_OBJ(cstr_to_string("0")); // -count - } - } - PUT(d, "count", obj); - - obj = NIL; - if (cmd->uc_argt & EX_RANGE) { - if (cmd->uc_argt & EX_DFLALL) { - obj = STRING_OBJ(cstr_to_string("%")); // -range=% - } else if (cmd->uc_def >= 0) { - snprintf(str, sizeof(str), "%" PRId64, (int64_t)cmd->uc_def); - obj = STRING_OBJ(cstr_to_string(str)); // -range=N - } else { - obj = STRING_OBJ(cstr_to_string(".")); // -range - } - } - PUT(d, "range", obj); - - obj = NIL; - for (int j = 0; addr_type_complete[j].expand != ADDR_NONE; j++) { - if (addr_type_complete[j].expand != ADDR_LINES - && addr_type_complete[j].expand == cmd->uc_addr_type) { - obj = STRING_OBJ(cstr_to_string(addr_type_complete[j].name)); - break; - } - } - PUT(d, "addr", obj); - - PUT(rv, (char *)cmd->uc_name, DICTIONARY_OBJ(d)); - } - return rv; -} - void verify_command(char *cmd) { if (strcmp("smile", cmd)) { diff --git a/src/nvim/ex_docmd.h b/src/nvim/ex_docmd.h index ddb25c53e4..283501cf76 100644 --- a/src/nvim/ex_docmd.h +++ b/src/nvim/ex_docmd.h @@ -33,27 +33,6 @@ 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_preview_luaref; // Reference to Lua preview 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 diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c index 46b9528546..f67c8a6720 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -812,7 +812,7 @@ void ex_if(exarg_T *eap) cstack_T *const cstack = eap->cstack; if (cstack->cs_idx == CSTACK_LEN - 1) { - eap->errmsg = N_("E579: :if nesting too deep"); + eap->errmsg = _("E579: :if nesting too deep"); } else { ++cstack->cs_idx; cstack->cs_flags[cstack->cs_idx] = 0; @@ -840,7 +840,7 @@ void ex_endif(exarg_T *eap) if (eap->cstack->cs_idx < 0 || (eap->cstack->cs_flags[eap->cstack->cs_idx] & (CSF_WHILE | CSF_FOR | CSF_TRY))) { - eap->errmsg = N_("E580: :endif without :if"); + eap->errmsg = _("E580: :endif without :if"); } else { // When debugging or a breakpoint was encountered, display the debug // prompt (if not already done). This shows the user that an ":endif" @@ -870,17 +870,17 @@ void ex_else(exarg_T *eap) || (cstack->cs_flags[cstack->cs_idx] & (CSF_WHILE | CSF_FOR | CSF_TRY))) { if (eap->cmdidx == CMD_else) { - eap->errmsg = N_("E581: :else without :if"); + eap->errmsg = _("E581: :else without :if"); return; } - eap->errmsg = N_("E582: :elseif without :if"); + eap->errmsg = _("E582: :elseif without :if"); skip = true; } else if (cstack->cs_flags[cstack->cs_idx] & CSF_ELSE) { if (eap->cmdidx == CMD_else) { - eap->errmsg = N_("E583: multiple :else"); + eap->errmsg = _("E583: multiple :else"); return; } - eap->errmsg = N_("E584: :elseif after :else"); + eap->errmsg = _("E584: :elseif after :else"); skip = true; } @@ -940,7 +940,7 @@ void ex_while(exarg_T *eap) cstack_T *const cstack = eap->cstack; if (cstack->cs_idx == CSTACK_LEN - 1) { - eap->errmsg = N_("E585: :while/:for nesting too deep"); + eap->errmsg = _("E585: :while/:for nesting too deep"); } else { /* * The loop flag is set when we have jumped back from the matching @@ -1019,7 +1019,7 @@ void ex_continue(exarg_T *eap) cstack_T *const cstack = eap->cstack; if (cstack->cs_looplevel <= 0 || cstack->cs_idx < 0) { - eap->errmsg = N_("E586: :continue without :while or :for"); + eap->errmsg = _("E586: :continue without :while or :for"); } else { // Try to find the matching ":while". This might stop at a try // conditional not in its finally clause (which is then to be executed @@ -1051,7 +1051,7 @@ void ex_break(exarg_T *eap) cstack_T *const cstack = eap->cstack; if (cstack->cs_looplevel <= 0 || cstack->cs_idx < 0) { - eap->errmsg = N_("E587: :break without :while or :for"); + eap->errmsg = _("E587: :break without :while or :for"); } else { // Deactivate conditionals until the matching ":while" or a try // conditional not in its finally clause (which is then to be @@ -1083,7 +1083,7 @@ void ex_endwhile(exarg_T *eap) } if (cstack->cs_looplevel <= 0 || cstack->cs_idx < 0) { - eap->errmsg = err; + eap->errmsg = _(err); } else { fl = cstack->cs_flags[cstack->cs_idx]; if (!(fl & csf)) { @@ -1097,9 +1097,9 @@ void ex_endwhile(exarg_T *eap) } if (!(fl & (CSF_WHILE | CSF_FOR))) { if (!(fl & CSF_TRY)) { - eap->errmsg = e_endif; + eap->errmsg = _(e_endif); } else if (fl & CSF_FINALLY) { - eap->errmsg = e_endtry; + eap->errmsg = _(e_endtry); } // Try to find the matching ":while" and report what's missing. for (idx = cstack->cs_idx; idx > 0; --idx) { @@ -1107,7 +1107,7 @@ void ex_endwhile(exarg_T *eap) if ((fl & CSF_TRY) && !(fl & CSF_FINALLY)) { // Give up at a try conditional not in its finally clause. // Ignore the ":endwhile"/":endfor". - eap->errmsg = err; + eap->errmsg = _(err); return; } if (fl & csf) { @@ -1230,7 +1230,7 @@ void ex_try(exarg_T *eap) cstack_T *const cstack = eap->cstack; if (cstack->cs_idx == CSTACK_LEN - 1) { - eap->errmsg = N_("E601: :try nesting too deep"); + eap->errmsg = _("E601: :try nesting too deep"); } else { ++cstack->cs_idx; ++cstack->cs_trylevel; @@ -1289,7 +1289,7 @@ void ex_catch(exarg_T *eap) char *pat; if (cstack->cs_trylevel <= 0 || cstack->cs_idx < 0) { - eap->errmsg = N_("E603: :catch without :try"); + eap->errmsg = _("E603: :catch without :try"); give_up = true; } else { if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY)) { @@ -1306,7 +1306,7 @@ void ex_catch(exarg_T *eap) if (cstack->cs_flags[idx] & CSF_FINALLY) { // Give up for a ":catch" after ":finally" and ignore it. // Just parse. - eap->errmsg = N_("E604: :catch after :finally"); + eap->errmsg = _("E604: :catch after :finally"); give_up = true; } else { rewind_conditionals(cstack, idx, CSF_WHILE | CSF_FOR, @@ -1341,7 +1341,7 @@ void ex_catch(exarg_T *eap) if (!skip && (cstack->cs_flags[idx] & CSF_THROWN) && !(cstack->cs_flags[idx] & CSF_CAUGHT)) { if (end != NULL && *end != NUL && !ends_excmd(*skipwhite(end + 1))) { - emsg(_(e_trailing)); + semsg(_(e_trailing_arg), end); return; } @@ -1436,7 +1436,7 @@ void ex_finally(exarg_T *eap) cstack_T *const cstack = eap->cstack; if (cstack->cs_trylevel <= 0 || cstack->cs_idx < 0) { - eap->errmsg = N_("E606: :finally without :try"); + eap->errmsg = _("E606: :finally without :try"); } else { if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY)) { eap->errmsg = get_end_emsg(cstack); @@ -1455,7 +1455,7 @@ void ex_finally(exarg_T *eap) if (cstack->cs_flags[idx] & CSF_FINALLY) { // Give up for a multiple ":finally" and ignore it. - eap->errmsg = N_("E607: multiple :finally"); + eap->errmsg = _("E607: multiple :finally"); return; } rewind_conditionals(cstack, idx, CSF_WHILE | CSF_FOR, @@ -1559,7 +1559,7 @@ void ex_endtry(exarg_T *eap) cstack_T *const cstack = eap->cstack; if (cstack->cs_trylevel <= 0 || cstack->cs_idx < 0) { - eap->errmsg = N_("E602: :endtry without :try"); + eap->errmsg = _("E602: :endtry without :try"); } else { // Don't do something after an error, interrupt or throw in the try // block, catch clause, or finally clause preceding this ":endtry" or @@ -1998,12 +1998,12 @@ int cleanup_conditionals(cstack_T *cstack, int searched_cond, int inclusive) static char *get_end_emsg(cstack_T *cstack) { if (cstack->cs_flags[cstack->cs_idx] & CSF_WHILE) { - return e_endwhile; + return _(e_endwhile); } if (cstack->cs_flags[cstack->cs_idx] & CSF_FOR) { - return e_endfor; + return _(e_endfor); } - return e_endif; + return _(e_endif); } /// Rewind conditionals until index "idx" is reached. "cond_type" and diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 6240ac6b37..07a0e68884 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -6525,7 +6525,7 @@ void ex_history(exarg_T *eap) histype1 = 0; histype2 = HIST_COUNT - 1; } else { - emsg(_(e_trailing)); + semsg(_(e_trailing_arg), arg); return; } } else { @@ -6535,7 +6535,7 @@ void ex_history(exarg_T *eap) end = arg; } if (!get_list_range(&end, &hisidx1, &hisidx2) || *end != NUL) { - emsg(_(e_trailing)); + semsg(_(e_trailing_arg), end); return; } diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 28ff0cbd59..2b2889d4d6 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -17,6 +17,7 @@ #include "nvim/cursor.h" #include "nvim/edit.h" #include "nvim/event/loop.h" +#include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" #include "nvim/garray.h" #include "nvim/getchar.h" diff --git a/src/nvim/globals.h b/src/nvim/globals.h index a41836353a..317423ffa0 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -973,7 +973,7 @@ EXTERN char e_longname[] INIT(= N_("E75: Name too long")); EXTERN char e_toomsbra[] INIT(= N_("E76: Too many [")); EXTERN char e_toomany[] INIT(= N_("E77: Too many file names")); EXTERN char e_trailing[] INIT(= N_("E488: Trailing characters")); -EXTERN char e_trailing2[] INIT(= N_("E488: Trailing characters: %s")); +EXTERN char e_trailing_arg[] INIT(= N_("E488: Trailing characters: %s")); EXTERN char e_umark[] INIT(= N_("E78: Unknown mark")); EXTERN char e_wildexpand[] INIT(= N_("E79: Cannot expand wildcards")); EXTERN char e_winheight[] INIT(= N_("E591: 'winheight' cannot be smaller than 'winminheight'")); diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index 2fc8f1dadc..a64d8e8f00 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -825,7 +825,6 @@ static void ins_compl_longest_match(compl_T *match) /// Add an array of matches to the list of matches. /// Frees matches[]. static void ins_compl_add_matches(int num_matches, char **matches, int icase) - FUNC_ATTR_NONNULL_ALL { int add_r = OK; Direction dir = compl_direction; diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 17157ccdc2..661dbfc4c2 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -21,7 +21,6 @@ #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" @@ -40,6 +39,7 @@ #include "nvim/profile.h" #include "nvim/screen.h" #include "nvim/undo.h" +#include "nvim/usercmd.h" #include "nvim/version.h" #include "nvim/vim.h" #include "nvim/window.h" @@ -2076,3 +2076,34 @@ int nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap, bool preview) return retv; } + +/// String representation of a Lua function reference +/// +/// @return Allocated string +char *nlua_funcref_str(LuaRef ref) +{ + lua_State *const lstate = global_lstate; + StringBuilder str = KV_INITIAL_VALUE; + kv_resize(str, 16); + + if (!lua_checkstack(lstate, 1)) { + goto plain; + } + nlua_pushref(lstate, ref); + if (!lua_isfunction(lstate, -1)) { + lua_pop(lstate, 1); + goto plain; + } + + lua_Debug ar; + if (lua_getinfo(lstate, ">S", &ar) && *ar.source == '@' && ar.linedefined >= 0) { + char *src = home_replace_save(NULL, ar.source + 1); + kv_printf(str, "<Lua %d: %s:%d>", ref, src, ar.linedefined); + xfree(src); + return str.items; + } + +plain: + kv_printf(str, "<Lua %d>", ref); + return str.items; +} diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h index e96494ec5a..2afbbebfe7 100644 --- a/src/nvim/lua/executor.h +++ b/src/nvim/lua/executor.h @@ -8,9 +8,9 @@ #include "nvim/assert.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" +#include "nvim/usercmd.h" // Generated by msgpack-gen.lua void nlua_add_api_functions(lua_State *lstate) REAL_FATTR_NONNULL_ALL; diff --git a/src/nvim/mapping.c b/src/nvim/mapping.c index 1797bb0365..342b1b0d47 100644 --- a/src/nvim/mapping.c +++ b/src/nvim/mapping.c @@ -196,9 +196,9 @@ static void showmap(mapblock_T *mp, bool local) // Use false below if we only want things like <Up> to show up as such on // the rhs, and not M-x etc, true gets both -- webb if (mp->m_luaref != LUA_NOREF) { - char msg[100]; - snprintf(msg, sizeof(msg), "<Lua function %d>", mp->m_luaref); - msg_puts_attr(msg, HL_ATTR(HLF_8)); + char *str = nlua_funcref_str(mp->m_luaref); + msg_puts_attr(str, HL_ATTR(HLF_8)); + xfree(str); } else if (mp->m_str[0] == NUL) { msg_puts_attr("<Nop>", HL_ATTR(HLF_8)); } else { @@ -2110,10 +2110,7 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact) rettv->vval.v_string = str2special_save((char *)rhs, false, false); } } else if (rhs_lua != LUA_NOREF) { - size_t msglen = 100; - char *msg = (char *)xmalloc(msglen); - snprintf(msg, msglen, "<Lua function %d>", mp->m_luaref); - rettv->vval.v_string = msg; + rettv->vval.v_string = nlua_funcref_str(mp->m_luaref); } } else { tv_dict_alloc_ret(rettv); diff --git a/src/nvim/match.c b/src/nvim/match.c index 8c72b13bc2..ba587c4141 100644 --- a/src/nvim/match.c +++ b/src/nvim/match.c @@ -1193,7 +1193,7 @@ void ex_match(exarg_T *eap) if (!eap->skip) { if (*end != NUL && !ends_excmd(*skipwhite((char *)end + 1))) { xfree(g); - eap->errmsg = e_trailing; + eap->errmsg = ex_errmsg(e_trailing_arg, (const char *)end); return; } if (*end != *p) { diff --git a/src/nvim/menu.c b/src/nvim/menu.c index febb66081a..16802a4e50 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -175,7 +175,7 @@ void ex_menu(exarg_T *eap) show_menus(menu_path, modes); goto theend; } else if (*map_to != NUL && (unmenu || enable != kNone)) { - emsg(_(e_trailing)); + semsg(_(e_trailing_arg), map_to); goto theend; } @@ -810,17 +810,23 @@ static vimmenu_T *find_menu(vimmenu_T *menu, char *name, int modes) /// Show the mapping associated with a menu item or hierarchy in a sub-menu. static int show_menus(char *const path_name, int modes) { - // First, find the (sub)menu with the given name - vimmenu_T *menu = find_menu(*get_root_menu(path_name), path_name, modes); - if (!menu) { - return FAIL; + vimmenu_T *menu = *get_root_menu(path_name); + if (menu != NULL) { + // First, find the (sub)menu with the given name + menu = find_menu(menu, path_name, modes); + if (menu == NULL) { + return FAIL; + } } + // When there are no menus at all, the title still needs to be shown. // Now we have found the matching menu, and we list the mappings // Highlight title msg_puts_title(_("\n--- Menus ---")); - show_menus_recursive(menu->parent, modes, 0); + if (menu != NULL) { + show_menus_recursive(menu->parent, modes, 0); + } return OK; } @@ -1161,7 +1167,7 @@ char *menu_name_skip(char *const name) * Return TRUE when "name" matches with menu "menu". The name is compared in * two ways: raw menu name and menu name without '&'. ignore part after a TAB. */ -static bool menu_name_equal(const char *const name, vimmenu_T *const menu) +static bool menu_name_equal(const char *const name, const vimmenu_T *const menu) { if (menu->en_name != NULL && (menu_namecmp(name, menu->en_name) @@ -1256,6 +1262,58 @@ int get_menu_cmd_modes(const char *cmd, bool forceit, int *noremap, int *unmenu) return modes; } +/// Return the string representation of the menu modes. Does the opposite +/// of get_menu_cmd_modes(). +static char *get_menu_mode_str(int modes) +{ + if ((modes & (MENU_INSERT_MODE | MENU_CMDLINE_MODE | MENU_NORMAL_MODE | + MENU_VISUAL_MODE | MENU_SELECT_MODE | MENU_OP_PENDING_MODE)) + == (MENU_INSERT_MODE | MENU_CMDLINE_MODE | MENU_NORMAL_MODE | + MENU_VISUAL_MODE | MENU_SELECT_MODE | MENU_OP_PENDING_MODE)) { + return "a"; + } + if ((modes & (MENU_NORMAL_MODE | MENU_VISUAL_MODE | MENU_SELECT_MODE | + MENU_OP_PENDING_MODE)) + == (MENU_NORMAL_MODE | MENU_VISUAL_MODE | MENU_SELECT_MODE | + MENU_OP_PENDING_MODE)) { + return " "; + } + if ((modes & (MENU_INSERT_MODE | MENU_CMDLINE_MODE)) + == (MENU_INSERT_MODE | MENU_CMDLINE_MODE)) { + return "!"; + } + if ((modes & (MENU_VISUAL_MODE | MENU_SELECT_MODE)) + == (MENU_VISUAL_MODE | MENU_SELECT_MODE)) { + return "v"; + } + if (modes & MENU_VISUAL_MODE) { + return "x"; + } + if (modes & MENU_SELECT_MODE) { + return "s"; + } + if (modes & MENU_OP_PENDING_MODE) { + return "o"; + } + if (modes & MENU_INSERT_MODE) { + return "i"; + } + if (modes & MENU_TERMINAL_MODE) { + return "tl"; + } + if (modes & MENU_CMDLINE_MODE) { + return "c"; + } + if (modes & MENU_NORMAL_MODE) { + return "n"; + } + if (modes & MENU_TIP_MODE) { + return "t"; + } + + return ""; +} + /* * Modify a menu name starting with "PopUp" to include the mode character. * Returns the name in allocated memory. @@ -1547,8 +1605,52 @@ void execute_menu(const exarg_T *eap, vimmenu_T *menu, int mode_idx) } } -// Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy and -// execute it. +/// Lookup a menu by the descriptor name e.g. "File.New" +/// Returns NULL if the menu is not found +static vimmenu_T *menu_getbyname(char *name_arg) + FUNC_ATTR_NONNULL_ALL +{ + char *saved_name = xstrdup(name_arg); + vimmenu_T *menu = *get_root_menu(saved_name); + char *name = saved_name; + bool gave_emsg = false; + while (*name) { + // Find in the menu hierarchy + char *p = menu_name_skip(name); + + while (menu != NULL) { + if (menu_name_equal(name, menu)) { + if (*p == NUL && menu->children != NULL) { + emsg(_("E333: Menu path must lead to a menu item")); + gave_emsg = true; + menu = NULL; + } else if (*p != NUL && menu->children == NULL) { + emsg(_(e_notsubmenu)); + menu = NULL; + } + break; + } + menu = menu->next; + } + if (menu == NULL || *p == NUL) { + break; + } + menu = menu->children; + name = p; + } + xfree(saved_name); + if (menu == NULL) { + if (!gave_emsg) { + semsg(_("E334: Menu not found: %s"), name_arg); + } + return NULL; + } + + return menu; +} + +/// Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy and +/// execute it. void ex_emenu(exarg_T *eap) { char *arg = eap->arg; @@ -1584,39 +1686,8 @@ void ex_emenu(exarg_T *eap) arg = skipwhite(arg + 2); } - char *saved_name = xstrdup(arg); - vimmenu_T *menu = *get_root_menu(saved_name); - char *name = saved_name; - bool gave_emsg = false; - while (*name) { - // Find in the menu hierarchy - char *p = menu_name_skip(name); - - while (menu != NULL) { - if (menu_name_equal(name, menu)) { - if (*p == NUL && menu->children != NULL) { - emsg(_("E333: Menu path must lead to a menu item")); - gave_emsg = true; - menu = NULL; - } else if (*p != NUL && menu->children == NULL) { - emsg(_(e_notsubmenu)); - menu = NULL; - } - break; - } - menu = menu->next; - } - if (menu == NULL || *p == NUL) { - break; - } - menu = menu->children; - name = p; - } - xfree(saved_name); + vimmenu_T *menu = menu_getbyname(arg); if (menu == NULL) { - if (!gave_emsg) { - semsg(_("E334: Menu not found: %s"), arg); - } return; } @@ -1819,3 +1890,119 @@ static char *menu_translate_tab_and_shift(char *arg_start) return arg; } + +/// Get the information about a menu item in mode 'which' +static void menuitem_getinfo(const char *menu_name, const vimmenu_T *menu, int modes, dict_T *dict) + FUNC_ATTR_NONNULL_ALL +{ + if (*menu_name == NUL) { + // Return all the top-level menus + list_T *const l = tv_list_alloc(kListLenMayKnow); + tv_dict_add_list(dict, S_LEN("submenus"), l); + // get all the children. Skip PopUp[nvoci]. + for (const vimmenu_T *topmenu = menu; topmenu != NULL; topmenu = topmenu->next) { + if (!menu_is_hidden(topmenu->dname)) { + tv_list_append_string(l, topmenu->dname, -1); + } + } + return; + } + + tv_dict_add_str(dict, S_LEN("name"), menu->name); + tv_dict_add_str(dict, S_LEN("display"), menu->dname); + if (menu->actext != NULL) { + tv_dict_add_str(dict, S_LEN("accel"), menu->actext); + } + tv_dict_add_nr(dict, S_LEN("priority"), (int)menu->priority); + tv_dict_add_str(dict, S_LEN("modes"), get_menu_mode_str(menu->modes)); + + char buf[NUMBUFLEN]; + buf[utf_char2bytes(menu->mnemonic, buf)] = NUL; + tv_dict_add_str(dict, S_LEN("shortcut"), buf); + + if (menu->children == NULL) { // leaf menu + int bit; + + // Get the first mode in which the menu is available + for (bit = 0; (bit < MENU_MODES) && !((1 << bit) & modes); bit++) {} + + if (bit < MENU_MODES) { // just in case, avoid Coverity warning + if (menu->strings[bit] != NULL) { + tv_dict_add_allocated_str(dict, S_LEN("rhs"), + *menu->strings[bit] == NUL + ? xstrdup("<Nop>") + : str2special_save(menu->strings[bit], false, false)); + } + tv_dict_add_bool(dict, S_LEN("noremenu"), menu->noremap[bit] == REMAP_NONE); + tv_dict_add_bool(dict, S_LEN("script"), menu->noremap[bit] == REMAP_SCRIPT); + tv_dict_add_bool(dict, S_LEN("silent"), menu->silent[bit]); + tv_dict_add_bool(dict, S_LEN("enabled"), (menu->enabled & (1 << bit)) != 0); + } + } else { + // If there are submenus, add all the submenu display names + list_T *const l = tv_list_alloc(kListLenMayKnow); + tv_dict_add_list(dict, S_LEN("submenus"), l); + const vimmenu_T *child = menu->children; + while (child != NULL) { + tv_list_append_string(l, child->dname, -1); + child = child->next; + } + } +} + +/// "menu_info()" function +/// Return information about a menu (including all the child menus) +void f_menu_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tv_dict_alloc_ret(rettv); + dict_T *const retdict = rettv->vval.v_dict; + + const char *const menu_name = tv_get_string_chk(&argvars[0]); + if (menu_name == NULL) { + return; + } + + // menu mode + const char *which; + if (argvars[1].v_type != VAR_UNKNOWN) { + which = tv_get_string_chk(&argvars[1]); + } else { + which = ""; // Default is modes for "menu" + } + if (which == NULL) { + return; + } + + const int modes = get_menu_cmd_modes(which, *which == '!', NULL, NULL); + + // Locate the specified menu or menu item + const vimmenu_T *menu = *get_root_menu(menu_name); + char *const saved_name = xstrdup(menu_name); + if (*saved_name != NUL) { + char *name = saved_name; + while (*name) { + // Find in the menu hierarchy + char *p = menu_name_skip(name); + while (menu != NULL) { + if (menu_name_equal(name, menu)) { + break; + } + menu = menu->next; + } + if (menu == NULL || *p == NUL) { + break; + } + menu = menu->children; + name = p; + } + } + xfree(saved_name); + + if (menu == NULL) { // specified menu not found + return; + } + + if (menu->modes & modes) { + menuitem_getinfo(menu_name, menu, modes, retdict); + } +} diff --git a/src/nvim/message.c b/src/nvim/message.c index 621a9212df..7cccd046c9 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -1748,8 +1748,8 @@ const char *str2special(const char **const sp, const bool replace_spaces, const *sp = str + 1; } } else { - // single-byte character or illegal byte - *sp = str + 1; + // single-byte character, NUL or illegal byte + *sp = str + (*str == NUL ? 0 : 1); } // Make special keys and C0 control characters in <> form, also <M-Space>. diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index 9c93057fe7..eaa56ffe63 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -1143,7 +1143,7 @@ size_t home_replace(const buf_T *const buf, const char *src, char *const dst, si /// Like home_replace, store the replaced string in allocated memory. /// @param buf When not NULL, check for help files /// @param src Input file name -char *home_replace_save(buf_T *buf, char *src) +char *home_replace_save(buf_T *buf, const char *src) FUNC_ATTR_NONNULL_RET { size_t len = 3; // space for "~/" and trailing NUL diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 9b46fad67a..99129bd15e 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -3120,7 +3120,7 @@ void qf_list(exarg_T *eap) int idx1 = 1; int idx2 = -1; if (!get_list_range((char_u **)&arg, &idx1, &idx2) || *arg != NUL) { - emsg(_(e_trailing)); + semsg(_(e_trailing_arg), arg); return; } qf_list_T *qfl = qf_get_curlist(qi); diff --git a/src/nvim/sign.c b/src/nvim/sign.c index 1640d0167e..7b6b55fede 100644 --- a/src/nvim/sign.c +++ b/src/nvim/sign.c @@ -1395,7 +1395,7 @@ static int parse_sign_cmd_args(int cmd, char_u *arg, char_u **sign_name, int *si filename = arg; *buf = buflist_findnr(getdigits_int((char **)&arg, true, 0)); if (*skipwhite((char *)arg) != NUL) { - emsg(_(e_trailing)); + semsg(_(e_trailing_arg), arg); } break; } else { diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index c23aff00cb..eb7c83d317 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -367,7 +367,12 @@ void terminal_check_size(Terminal *term) vterm_get_size(term->vt, &curheight, &curwidth); uint16_t width = 0, height = 0; + // Check if there is a window that displays the terminal and find the maximum width and height. + // Skip the autocommand window which isn't actually displayed. FOR_ALL_TAB_WINDOWS(tp, wp) { + if (wp == aucmd_win) { + continue; + } if (wp->w_buffer && wp->w_buffer->terminal == term) { const uint16_t win_width = (uint16_t)(MAX(0, wp->w_width_inner - win_col_off(wp))); diff --git a/src/nvim/testdir/setup.vim b/src/nvim/testdir/setup.vim index 9d6fc5e526..6bc3607b69 100644 --- a/src/nvim/testdir/setup.vim +++ b/src/nvim/testdir/setup.vim @@ -39,9 +39,11 @@ if exists('s:did_load') endif let s:did_load = 1 -" Clear Nvim default mappings. +" Clear Nvim default mappings and menus. mapclear mapclear! +aunmenu * +tlunmenu * " Prevent Nvim log from writing to stderr. let $NVIM_LOG_FILE = exists($NVIM_LOG_FILE) ? $NVIM_LOG_FILE : 'Xnvim.log' diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index 966b8ef571..a83ef50abc 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -16,7 +16,6 @@ source test_ga.vim source test_glob2regpat.vim source test_global.vim source test_lispwords.vim -source test_menu.vim source test_move.vim source test_put.vim source test_reltime.vim diff --git a/src/nvim/testdir/test_menu.vim b/src/nvim/testdir/test_menu.vim index 4af75be514..75992d3313 100644 --- a/src/nvim/testdir/test_menu.vim +++ b/src/nvim/testdir/test_menu.vim @@ -19,6 +19,48 @@ func Test_load_menu() call assert_equal('', v:errmsg) endfunc +func Test_buffer_menu_special_buffers() + " Load in runtime menus + try + source $VIMRUNTIME/menu.vim + catch + call assert_report('error while loading menus: ' . v:exception) + endtry + + let v:errmsg = '' + doautocmd LoadBufferMenu VimEnter + call assert_equal('', v:errmsg) + + let orig_buffer_menus = execute("nmenu Buffers") + + " Test that regular new buffer results in a new buffer menu item. + new + let new_buffer_menus = execute('nmenu Buffers') + call assert_equal(len(split(orig_buffer_menus, "\n")) + 2, len(split(new_buffer_menus, "\n"))) + bwipe! + call assert_equal(orig_buffer_menus, execute("nmenu Buffers")) + + " Make a new command-line window, test that it does not create a new buffer + " menu. + call feedkeys("q::let cmdline_buffer_menus=execute('nmenu Buffers')\<CR>:q\<CR>", 'ntx') + call assert_equal(len(split(orig_buffer_menus, "\n")) + 2, len(split(cmdline_buffer_menus, "\n"))) + call assert_equal(orig_buffer_menus, execute("nmenu Buffers")) + + if has('terminal') + " Open a terminal window and test that it does not create a buffer menu + " item. + terminal + let term_buffer_menus = execute('nmenu Buffers') + call assert_equal(len(split(orig_buffer_menus, "\n")) + 2, len(split(term_buffer_menus, "\n"))) + bwipe! + call assert_equal(orig_buffer_menus, execute("nmenu Buffers")) + endif + + " Remove menus to clean up + source $VIMRUNTIME/delmenu.vim + call assert_equal('', v:errmsg) +endfunc + func Test_translate_menu() if !has('multi_lang') return @@ -89,9 +131,39 @@ func Test_menu_commands() unlet g:did_menu endfun +" Test various menu related errors +func Test_menu_errors() + menu Test.Foo :version<CR> + + " Error cases + call assert_fails('menu .Test.Foo :ls<CR>', 'E475:') + call assert_fails('menu Test. :ls<CR>', 'E330:') + call assert_fails('menu Foo. :ls<CR>', 'E331:') + call assert_fails('unmenu Test.Foo abc', 'E488:') + call assert_fails('menu <Tab>:ls :ls<CR>', 'E792:') + call assert_fails('menu Test.<Tab>:ls :ls<CR>', 'E792:') + call assert_fails('menu Test.Foo.Bar :ls<CR>', 'E327:') + call assert_fails('menu Test.-Sep-.Baz :ls<CR>', 'E332:') + call assert_fails('menu Foo.Bar.--.Baz :ls<CR>', 'E332:') + call assert_fails('menu disable Test.Foo.Bar', 'E327:') + call assert_fails('menu disable T.Foo', 'E329:') + call assert_fails('unmenu Test.Foo.Bar', 'E327:') + call assert_fails('cunmenu Test.Foo', 'E328:') + call assert_fails('unmenu Test.Bar', 'E329:') + call assert_fails('menu Test.Foo.Bar', 'E327:') + call assert_fails('cmenu Test.Foo', 'E328:') + call assert_fails('emenu x Test.Foo', 'E475:') + call assert_fails('emenu Test.Foo.Bar', 'E334:') + call assert_fails('menutranslate Test', 'E474:') + + silent! unmenu Foo + unmenu Test +endfunc + " Test for menu item completion in command line func Test_menu_expand() " Create the menu itmes for test + menu Dummy.Nothing lll for i in range(1, 4) let m = 'menu Xmenu.A' .. i .. '.A' .. i for j in range(1, 4) @@ -117,10 +189,356 @@ func Test_menu_expand() " Test for <Up> to go up a menu call feedkeys(":emenu Xmenu.A\<Tab>\<Down>\<Up>\<Up>\<Up>" .. \ "\<C-A>\<C-B>\"\<CR>", 'xt') - call assert_equal('"emenu Buffers. Xmenu.', @:) + call assert_equal('"emenu Dummy. Xmenu.', @:) + + " Test for expanding only submenus + call feedkeys(":popup Xmenu.\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"popup Xmenu.A1 A2 A3 A4', @:) + + " Test for expanding menus after enable/disable + call feedkeys(":menu enable Xmenu.\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"menu enable Xmenu.A1. A2. A3. A4.', @:) + call feedkeys(":menu disable Xmenu.\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"menu disable Xmenu.A1. A2. A3. A4.', @:) + + " Test for expanding non-existing menu path + call feedkeys(":menu xyz.\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"menu xyz.', @:) + call feedkeys(":menu Xmenu.A1.A1B1.xyz.\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"menu Xmenu.A1.A1B1.xyz.', @:) set wildmenu& unmenu Xmenu + unmenu Dummy + + " Test for expanding popup menus with some hidden items + menu Xmenu.foo.A1 a1 + menu Xmenu.]bar bar + menu Xmenu.]baz.B1 b1 + menu Xmenu.-sep- : + call feedkeys(":popup Xmenu.\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"popup Xmenu.foo', @:) + unmenu Xmenu +endfunc + +" Test for the menu_info() function +func Test_menu_info() + " Define menus with various attributes + 10nnoremenu 10.10 T&est.F&oo :echo 'foo'<CR> + 10nmenu <silent> 10.20 T&est.B&ar<Tab>:bar :echo 'bar'<CR> + 10nmenu <script> 10.30.5 T&est.Ba&z.Qu&x :echo 'qux'<CR> + + let d = #{name: "B&ar\t:bar", display: 'Bar', modes: 'n', shortcut: 'a', + \ accel: ':bar', priority: 20, enabled: v:true, silent: v:true, + \ noremenu: v:false, script: v:false, rhs: ":echo 'bar'<CR>"} + call assert_equal(d, menu_info('Test.Bar')) + + let d = #{name: 'Ba&z', display: 'Baz', modes: 'n', shortcut: 'z', + \ priority: 30, submenus: ['Qux']} + call assert_equal(d, menu_info('Test.Baz')) + + let d = #{name: 'T&est', display: 'Test', modes: 'n', shortcut: 'e', + \ priority: 10, submenus: ['Foo', 'Bar', 'Baz']} + call assert_equal(d, menu_info('Test')) + call assert_equal({}, menu_info('Test.Dummy')) + call assert_equal({}, menu_info('Dummy')) + + nmenu disable Test.Foo + call assert_equal(v:false, menu_info('Test.Foo').enabled) + nmenu enable Test.Foo + call assert_equal(v:true, menu_info('Test.Foo').enabled) + + call assert_equal(menu_info('Test.Foo'), menu_info('Test.Foo', '')) + nmenu Test.abc <Nop> + call assert_equal('<Nop>', menu_info('Test.abc').rhs) + call assert_fails('call menu_info([])', 'E730:') + nunmenu Test + + " Test for defining menus in different modes + menu Test.menu :menu<CR> + menu! Test.menu! :menu!<CR> + amenu Test.amenu :amenu<CR> + nmenu Test.nmenu :nmenu<CR> + omenu Test.omenu :omenu<CR> + vmenu Test.vmenu :vmenu<CR> + xmenu Test.xmenu :xmenu<CR> + smenu Test.smenu :smenu<CR> + imenu <silent> <script> Test.imenu :imenu<CR> + cmenu Test.cmenu :cmenu<CR> + tlmenu Test.tlmenu :tlmenu<CR> + tmenu Test.nmenu Normal mode menu + tmenu Test.omenu Op-pending mode menu + noremenu Test.noremenu :noremenu<CR> + noremenu! Test.noremenu! :noremenu!<CR> + anoremenu Test.anoremenu :anoremenu<CR> + nnoremenu Test.nnoremenu :nnoremenu<CR> + onoremenu Test.onoremenu :onoremenu<CR> + vnoremenu Test.vnoremenu :vnoremenu<CR> + xnoremenu Test.xnoremenu :xnoremenu<CR> + snoremenu Test.snoremenu :snoremenu<CR> + inoremenu <silent> Test.inoremenu :inoremenu<CR> + cnoremenu Test.cnoremenu :cnoremenu<CR> + tlnoremenu Test.tlnoremenu :tlnoremenu<CR> + call assert_equal(#{name: 'menu', priority: 500, shortcut: '', + \ display: 'menu', modes: ' ', enabled: v:true, silent: v:false, + \ rhs: ":menu<CR>", noremenu: v:false, script: v:false}, + \ menu_info('Test.menu')) + call assert_equal(#{name: 'menu!', priority: 500, shortcut: '', + \ display: 'menu!', modes: '!', enabled: v:true, silent: v:false, + \ rhs: ":menu!<CR>", noremenu: v:false, script: v:false}, + \ menu_info('Test.menu!', '!')) + call assert_equal(#{name: 'amenu', priority: 500, shortcut: '', + \ display: 'amenu', modes: 'a', enabled: v:true, silent: v:false, + \ rhs: ":amenu<CR>", noremenu: v:false, script: v:false}, + \ menu_info('Test.amenu', 'a')) + call assert_equal(#{name: 'nmenu', priority: 500, shortcut: '', + \ display: 'nmenu', modes: 'n', enabled: v:true, silent: v:false, + \ rhs: ':nmenu<CR>', noremenu: v:false, script: v:false}, + \ menu_info('Test.nmenu', 'n')) + call assert_equal(#{name: 'omenu', priority: 500, shortcut: '', + \ display: 'omenu', modes: 'o', enabled: v:true, silent: v:false, + \ rhs: ':omenu<CR>', noremenu: v:false, script: v:false}, + \ menu_info('Test.omenu', 'o')) + call assert_equal(#{name: 'vmenu', priority: 500, shortcut: '', + \ display: 'vmenu', modes: 'v', enabled: v:true, silent: v:false, + \ rhs: ':vmenu<CR>', noremenu: v:false, script: v:false}, + \ menu_info('Test.vmenu', 'v')) + call assert_equal(#{name: 'xmenu', priority: 500, shortcut: '', + \ display: 'xmenu', modes: 'x', enabled: v:true, silent: v:false, + \ rhs: ':xmenu<CR>', noremenu: v:false, script: v:false}, + \ menu_info('Test.xmenu', 'x')) + call assert_equal(#{name: 'smenu', priority: 500, shortcut: '', + \ display: 'smenu', modes: 's', enabled: v:true, silent: v:false, + \ rhs: ':smenu<CR>', noremenu: v:false, script: v:false}, + \ menu_info('Test.smenu', 's')) + call assert_equal(#{name: 'imenu', priority: 500, shortcut: '', + \ display: 'imenu', modes: 'i', enabled: v:true, silent: v:true, + \ rhs: ':imenu<CR>', noremenu: v:false, script: v:true}, + \ menu_info('Test.imenu', 'i')) + call assert_equal(#{ name: 'cmenu', priority: 500, shortcut: '', + \ display: 'cmenu', modes: 'c', enabled: v:true, silent: v:false, + \ rhs: ':cmenu<CR>', noremenu: v:false, script: v:false}, + \ menu_info('Test.cmenu', 'c')) + call assert_equal(#{name: 'tlmenu', priority: 500, shortcut: '', + \ display: 'tlmenu', modes: 'tl', enabled: v:true, silent: v:false, + \ rhs: ':tlmenu<CR>', noremenu: v:false, script: v:false}, + \ menu_info('Test.tlmenu', 'tl')) + call assert_equal(#{name: 'noremenu', priority: 500, shortcut: '', + \ display: 'noremenu', modes: ' ', enabled: v:true, silent: v:false, + \ rhs: ":noremenu<CR>", noremenu: v:true, script: v:false}, + \ menu_info('Test.noremenu')) + call assert_equal(#{name: 'noremenu!', priority: 500, shortcut: '', + \ display: 'noremenu!', modes: '!', enabled: v:true, silent: v:false, + \ rhs: ":noremenu!<CR>", noremenu: v:true, script: v:false}, + \ menu_info('Test.noremenu!', '!')) + call assert_equal(#{name: 'anoremenu', priority: 500, shortcut: '', + \ display: 'anoremenu', modes: 'a', enabled: v:true, silent: v:false, + \ rhs: ":anoremenu<CR>", noremenu: v:true, script: v:false}, + \ menu_info('Test.anoremenu', 'a')) + call assert_equal(#{name: 'nnoremenu', priority: 500, shortcut: '', + \ display: 'nnoremenu', modes: 'n', enabled: v:true, silent: v:false, + \ rhs: ':nnoremenu<CR>', noremenu: v:true, script: v:false}, + \ menu_info('Test.nnoremenu', 'n')) + call assert_equal(#{name: 'onoremenu', priority: 500, shortcut: '', + \ display: 'onoremenu', modes: 'o', enabled: v:true, silent: v:false, + \ rhs: ':onoremenu<CR>', noremenu: v:true, script: v:false}, + \ menu_info('Test.onoremenu', 'o')) + call assert_equal(#{name: 'vnoremenu', priority: 500, shortcut: '', + \ display: 'vnoremenu', modes: 'v', enabled: v:true, silent: v:false, + \ rhs: ':vnoremenu<CR>', noremenu: v:true, script: v:false}, + \ menu_info('Test.vnoremenu', 'v')) + call assert_equal(#{name: 'xnoremenu', priority: 500, shortcut: '', + \ display: 'xnoremenu', modes: 'x', enabled: v:true, silent: v:false, + \ rhs: ':xnoremenu<CR>', noremenu: v:true, script: v:false}, + \ menu_info('Test.xnoremenu', 'x')) + call assert_equal(#{name: 'snoremenu', priority: 500, shortcut: '', + \ display: 'snoremenu', modes: 's', enabled: v:true, silent: v:false, + \ rhs: ':snoremenu<CR>', noremenu: v:true, script: v:false}, + \ menu_info('Test.snoremenu', 's')) + call assert_equal(#{name: 'inoremenu', priority: 500, shortcut: '', + \ display: 'inoremenu', modes: 'i', enabled: v:true, silent: v:true, + \ rhs: ':inoremenu<CR>', noremenu: v:true, script: v:false}, + \ menu_info('Test.inoremenu', 'i')) + call assert_equal(#{ name: 'cnoremenu', priority: 500, shortcut: '', + \ display: 'cnoremenu', modes: 'c', enabled: v:true, silent: v:false, + \ rhs: ':cnoremenu<CR>', noremenu: v:true, script: v:false}, + \ menu_info('Test.cnoremenu', 'c')) + call assert_equal(#{name: 'tlnoremenu', priority: 500, shortcut: '', + \ display: 'tlnoremenu', modes: 'tl', enabled: v:true, silent: v:false, + \ rhs: ':tlnoremenu<CR>', noremenu: v:true, script: v:false}, + \ menu_info('Test.tlnoremenu', 'tl')) + + " Test for getting all the top-level menu names + call assert_notequal(menu_info('').submenus, []) + + aunmenu Test + tlunmenu Test + call assert_equal({}, menu_info('Test')) + call assert_equal({}, menu_info('Test', '!')) + call assert_equal({}, menu_info('Test', 'a')) + call assert_equal({}, menu_info('Test', 'n')) + call assert_equal({}, menu_info('Test', 'o')) + call assert_equal({}, menu_info('Test', 'v')) + call assert_equal({}, menu_info('Test', 'x')) + call assert_equal({}, menu_info('Test', 's')) + call assert_equal({}, menu_info('Test', 'i')) + call assert_equal({}, menu_info('Test', 'c')) + call assert_equal({}, menu_info('Test', 't')) + call assert_equal({}, menu_info('Test', 'tl')) + + amenu Test.amenu :amenu<CR> + call assert_equal(':amenu<CR>', menu_info('Test.amenu', '').rhs) + call assert_equal('<C-\><C-O>:amenu<CR>', menu_info('Test.amenu', '!').rhs) + call assert_equal(':amenu<CR>', menu_info('Test.amenu', 'n').rhs) + call assert_equal('<C-C>:amenu<CR><C-\><C-G>', + \ menu_info('Test.amenu', 'o').rhs) + call assert_equal('<C-C>:amenu<CR><C-\><C-G>', + \ menu_info('Test.amenu', 'v').rhs) + call assert_equal('<C-C>:amenu<CR><C-\><C-G>', + \ menu_info('Test.amenu', 'x').rhs) + call assert_equal('<C-C>:amenu<CR><C-\><C-G>', + \ menu_info('Test.amenu', 's').rhs) + call assert_equal('<C-\><C-O>:amenu<CR>', menu_info('Test.amenu', 'i').rhs) + call assert_equal('<C-C>:amenu<CR><C-\><C-G>', + \ menu_info('Test.amenu', 'c').rhs) + aunmenu Test.amenu + + " Test for hidden menus + menu ]Test.menu :menu<CR> + call assert_equal(#{name: ']Test', display: ']Test', priority: 500, + \ shortcut: '', modes: ' ', submenus: ['menu']}, + \ menu_info(']Test')) + unmenu ]Test +endfunc + +" Test for <special> keyword in a menu with 'cpo' containing '<' +func Test_menu_special() + throw 'Skipped: Nvim does not support cpoptions flag "<"' + new + set cpo+=< + nmenu Test.Sign am<Tab>n<Esc> + call feedkeys(":emenu n Test.Sign\<CR>", 'x') + call assert_equal("m<Tab>n<Esc>", getline(1)) + nunmenu Test.Sign + nmenu <special> Test.Sign am<Tab>n<Esc> + call setline(1, '') + call feedkeys(":emenu n Test.Sign\<CR>", 'x') + call assert_equal("m\tn", getline(1)) + set cpo-=< + close! + nunmenu Test.Sign +endfunc + +" Test for "icon=filname" in a toolbar +func Test_menu_icon() + CheckFeature toolbar + nmenu icon=myicon.xpm Toolbar.Foo :echo "Foo"<CR> + call assert_equal('myicon.xpm', "Toolbar.Foo"->menu_info().icon) + nunmenu Toolbar.Foo + + " Test for using the builtin icon + amenu ToolBar.BuiltIn22 :echo "BuiltIn22"<CR> + call assert_equal(#{name: 'BuiltIn22', display: 'BuiltIn22', + \ enabled: v:true, shortcut: '', modes: 'a', script: v:false, + \ iconidx: 22, priority: 500, silent: v:false, + \ rhs: ':echo "BuiltIn22"<CR>', noremenu: v:false}, + \ menu_info("ToolBar.BuiltIn22")) + aunmenu ToolBar.BuiltIn22 +endfunc + +" Test for ":emenu" command in different modes +func Test_emenu_cmd() + new + xmenu Test.foo rx + call setline(1, ['aaaa', 'bbbb']) + normal ggVj + %emenu Test.foo + call assert_equal(['xxxx', 'xxxx'], getline(1, 2)) + call setline(1, ['aaaa', 'bbbb']) + exe "normal ggVj\<Esc>" + %emenu Test.foo + call assert_equal(['xxxx', 'xxxx'], getline(1, 2)) + call setline(1, ['aaaa', 'bbbb']) + exe "normal ggV\<Esc>" + 2emenu Test.foo + call assert_equal(['aaaa', 'xxxx'], getline(1, 2)) + xunmenu Test.foo + close! +endfunc + +" Test for PopUp menus +func Test_popup_menu() + 20menu PopUp.foo :echo 'foo'<CR> + 20menu PopUp.bar :echo 'bar'<CR> + call assert_equal(#{name: 'PopUp', display: 'PopUp', priority: 20, + \ shortcut: '', modes: ' ', submenus: ['foo', 'bar']}, + \ menu_info('PopUp')) + menu disable PopUp.bar + call assert_equal(v:true, "PopUp.foo"->menu_info().enabled) + call assert_equal(v:false, "PopUp.bar"->menu_info().enabled) + menu enable PopUp.bar + call assert_equal(v:true, "PopUp.bar"->menu_info().enabled) + unmenu PopUp +endfunc + +" Test for listing the menus using the :menu command +func Test_show_menus() + " In the GUI, tear-off menu items are present in the output below + " So skip this test + CheckNotGui + aunmenu * + call assert_equal(['--- Menus ---'], split(execute('menu'), "\n")) + nmenu <script> 200.10 Test.nmenu1 :nmenu1<CR> + nmenu 200.20 Test.nmenu2 :nmenu2<CR> + nnoremenu 200.30 Test.nmenu3 :nmenu3<CR> + nmenu 200.40 Test.nmenu4 :nmenu4<CR> + nmenu 200.50 disable Test.nmenu4 + let exp =<< trim [TEXT] + --- Menus --- + 200 Test + 10 nmenu1 + n& :nmenu1<CR> + 20 nmenu2 + n :nmenu2<CR> + 30 nmenu3 + n* :nmenu3<CR> + 40 nmenu4 + n - :nmenu4<CR> + [TEXT] + call assert_equal(exp, split(execute('nmenu'), "\n")) + nunmenu Test +endfunc + +" Test for menu tips +func Test_tmenu() + tunmenu * + call assert_equal(['--- Menus ---'], split(execute('tmenu'), "\n")) + tmenu Test.nmenu1 nmenu1 + tmenu Test.nmenu2.sub1 nmenu2.sub1 + let exp =<< trim [TEXT] + --- Menus --- + 500 Test + 500 nmenu1 + t - nmenu1 + 500 nmenu2 + 500 sub1 + t - nmenu2.sub1 + [TEXT] + call assert_equal(exp, split(execute('tmenu'), "\n")) + tunmenu Test +endfunc + +func Test_only_modifier() + exe "tmenu a.b \x80\xfc0" + let exp =<< trim [TEXT] + --- Menus --- + 500 a + 500 b + t - <T-2-^@> + [TEXT] + call assert_equal(exp, split(execute('tmenu'), "\n")) + + tunmenu a.b endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim index f18ddb274c..7cb70aa2af 100644 --- a/src/nvim/testdir/test_normal.vim +++ b/src/nvim/testdir/test_normal.vim @@ -1975,7 +1975,8 @@ func Test_normal31_r_cmd() " using CTRL-Y and CTRL-E. " Different code paths are used for utf-8 and latin1 encodings set showmatch - for enc in ['latin1', 'utf-8'] + " for enc in ['latin1', 'utf-8'] + for enc in ['utf-8'] enew! let &encoding = enc call setline(1, [' {a}', 'xxxxxxxxxx', ' [b]']) diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index c5b1266689..b10f0f5030 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -310,6 +310,7 @@ func Test_set_errors() call assert_fails('set sidescroll=-1', 'E487:') call assert_fails('set tabstop=-1', 'E487:') call assert_fails('set tabstop=10000', 'E474:') + call assert_fails('let &tabstop = 10000', 'E474:') call assert_fails('set tabstop=5500000000', 'E474:') call assert_fails('set textwidth=-1', 'E487:') call assert_fails('set timeoutlen=-1', 'E487:') @@ -325,6 +326,7 @@ func Test_set_errors() call assert_fails('set comments=a', 'E525:') call assert_fails('set foldmarker=x', 'E536:') call assert_fails('set commentstring=x', 'E537:') + call assert_fails('let &commentstring = "x"', 'E537:') call assert_fails('set complete=x', 'E539:') call assert_fails('set rulerformat=%-', 'E539:') call assert_fails('set rulerformat=%(', 'E542:') diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim index 0486ed83ad..3d1e3fa6db 100644 --- a/src/nvim/testdir/test_popup.vim +++ b/src/nvim/testdir/test_popup.vim @@ -864,15 +864,21 @@ func Test_popup_position() endfunc func Test_popup_command() - if !CanRunVimInTerminal() || !has('menu') - return - endif + CheckScreendump + CheckFeature menu - call writefile([ - \ 'one two three four five', - \ 'and one two Xthree four five', - \ 'one more two three four five', - \ ], 'Xtest') + menu Test.Foo Foo + call assert_fails('popup Test.Foo', 'E336:') + call assert_fails('popup Test.Foo.X', 'E327:') + call assert_fails('popup Foo', 'E337:') + unmenu Test.Foo + + let lines =<< trim END + one two three four five + and one two Xthree four five + one more two three four five + END + call writefile(lines, 'Xtest') let buf = RunVimInTerminal('Xtest', {}) call term_sendkeys(buf, ":source $VIMRUNTIME/menu.vim\<CR>") call term_sendkeys(buf, "/X\<CR>:popup PopUp\<CR>") diff --git a/src/nvim/testdir/test_tabpage.vim b/src/nvim/testdir/test_tabpage.vim index d891684ecb..07f35ddb4f 100644 --- a/src/nvim/testdir/test_tabpage.vim +++ b/src/nvim/testdir/test_tabpage.vim @@ -133,18 +133,18 @@ function Test_tabpage() call assert_fails("99tabmove", 'E16:') call assert_fails("+99tabmove", 'E16:') call assert_fails("-99tabmove", 'E16:') - call assert_fails("tabmove foo", 'E474:') - call assert_fails("tabmove 99", 'E474:') - call assert_fails("tabmove +99", 'E474:') - call assert_fails("tabmove -99", 'E474:') - call assert_fails("tabmove -3+", 'E474:') - call assert_fails("tabmove $3", 'E474:') + call assert_fails("tabmove foo", 'E475:') + call assert_fails("tabmove 99", 'E475:') + call assert_fails("tabmove +99", 'E475:') + call assert_fails("tabmove -99", 'E475:') + call assert_fails("tabmove -3+", 'E475:') + call assert_fails("tabmove $3", 'E475:') call assert_fails("%tabonly", 'E16:') 1tabonly! tabmove 1 call assert_equal(1, tabpagenr()) tabnew - call assert_fails("-2tabmove", 'E474:') + call assert_fails("-2tabmove", 'E16:') tabonly! endfunc @@ -320,14 +320,14 @@ function Test_tabpage_with_tabnext() call Check_tab_count(4, 'tabnext -', 3) call Check_tab_count(4, 'tabnext -2', 2) call Check_tab_count(3, 'tabnext $', 5) - call assert_fails('tabnext 0', 'E474:') - call assert_fails('tabnext .', 'E474:') - call assert_fails('tabnext -+', 'E474:') - call assert_fails('tabnext +2-', 'E474:') - call assert_fails('tabnext $3', 'E474:') - call assert_fails('tabnext 99', 'E474:') - call assert_fails('tabnext +99', 'E474:') - call assert_fails('tabnext -99', 'E474:') + call assert_fails('tabnext 0', 'E475:') + call assert_fails('tabnext .', 'E475:') + call assert_fails('tabnext -+', 'E475:') + call assert_fails('tabnext +2-', 'E475:') + call assert_fails('tabnext $3', 'E475:') + call assert_fails('tabnext 99', 'E475:') + call assert_fails('tabnext +99', 'E475:') + call assert_fails('tabnext -99', 'E475:') 1tabonly! endfunction @@ -352,7 +352,7 @@ function Test_tabpage_with_tabprevious() let err_code = 'E16:' else let entire_cmd = cmd . ' ' . c - let err_code = 'E474:' + let err_code = 'E475:' endif call assert_fails(entire_cmd, err_code) endfor @@ -451,7 +451,7 @@ function Test_tabpage_with_tabclose() let err_code = 'E16:' else let entire_cmd = 'tabclose ' . c - let err_code = 'E474:' + let err_code = 'E475:' endif call assert_fails(entire_cmd, err_code) call assert_equal(6, tabpagenr('$')) @@ -460,8 +460,8 @@ function Test_tabpage_with_tabclose() call assert_fails('3tabclose', 'E37:') call assert_fails('tabclose 3', 'E37:') - call assert_fails('tabclose -+', 'E474:') - call assert_fails('tabclose +2-', 'E474:') + call assert_fails('tabclose -+', 'E475:') + call assert_fails('tabclose +2-', 'E475:') call assert_equal(6, tabpagenr('$')) 1tabonly! @@ -506,7 +506,7 @@ function Test_tabpage_with_tabonly() let err_code = 'E16:' else let entire_cmd = 'tabonly ' . c - let err_code = 'E474:' + let err_code = 'E475:' endif call assert_fails(entire_cmd, err_code) call assert_equal(6, tabpagenr('$')) @@ -517,13 +517,13 @@ function Test_tabpage_with_tabonly() for c in tc call s:reconstruct_tabpage_for_test(6) let entire_cmd = 'tabonly' . c[2] . ' ' . c[1] - let err_code = 'E474:' + let err_code = 'E475:' call assert_fails(entire_cmd, err_code) call assert_equal(6, tabpagenr('$')) endfor - call assert_fails('tabonly -+', 'E474:') - call assert_fails('tabonly +2-', 'E474:') + call assert_fails('tabonly -+', 'E475:') + call assert_fails('tabonly +2-', 'E475:') call assert_equal(6, tabpagenr('$')) 1tabonly! diff --git a/src/nvim/usercmd.c b/src/nvim/usercmd.c new file mode 100644 index 0000000000..15197dc504 --- /dev/null +++ b/src/nvim/usercmd.c @@ -0,0 +1,1742 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +// usercmd.c: User defined command support + +#include <assert.h> +#include <inttypes.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> + +#include "nvim/api/private/helpers.h" +#include "nvim/ascii.h" +#include "nvim/charset.h" +#include "nvim/ex_docmd.h" +#include "nvim/garray.h" +#include "nvim/lua/executor.h" +#include "nvim/os/input.h" +#include "nvim/usercmd.h" +#include "nvim/window.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "usercmd.c.generated.h" +#endif + +garray_T ucmds = { 0, 0, sizeof(ucmd_T), 4, NULL }; + +static char e_complete_used_without_allowing_arguments[] + = N_("E1208: -complete used without allowing arguments"); +static char e_no_such_user_defined_command_str[] + = N_("E184: No such user-defined command: %s"); +static char e_no_such_user_defined_command_in_current_buffer_str[] + = N_("E1237: No such user-defined command in current buffer: %s"); + +/// List of names for completion for ":command" with the EXPAND_ flag. +/// Must be alphabetical for completion. +static const char *command_complete[] = +{ + [EXPAND_ARGLIST] = "arglist", + [EXPAND_AUGROUP] = "augroup", + [EXPAND_BEHAVE] = "behave", + [EXPAND_BUFFERS] = "buffer", + [EXPAND_CHECKHEALTH] = "checkhealth", + [EXPAND_COLORS] = "color", + [EXPAND_COMMANDS] = "command", + [EXPAND_COMPILER] = "compiler", + [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", + [EXPAND_EVENTS] = "event", + [EXPAND_EXPRESSION] = "expression", + [EXPAND_FILES] = "file", + [EXPAND_FILES_IN_PATH] = "file_in_path", + [EXPAND_FILETYPE] = "filetype", + [EXPAND_FUNCTIONS] = "function", + [EXPAND_HELP] = "help", + [EXPAND_HIGHLIGHT] = "highlight", + [EXPAND_HISTORY] = "history", +#ifdef HAVE_WORKING_LIBINTL + [EXPAND_LOCALES] = "locale", +#endif + [EXPAND_LUA] = "lua", + [EXPAND_MAPCLEAR] = "mapclear", + [EXPAND_MAPPINGS] = "mapping", + [EXPAND_MENUS] = "menu", + [EXPAND_MESSAGES] = "messages", + [EXPAND_OWNSYNTAX] = "syntax", + [EXPAND_SYNTIME] = "syntime", + [EXPAND_SETTINGS] = "option", + [EXPAND_PACKADD] = "packadd", + [EXPAND_SHELLCMD] = "shellcmd", + [EXPAND_SIGN] = "sign", + [EXPAND_TAGS] = "tag", + [EXPAND_TAGS_LISTFILES] = "tag_listfiles", + [EXPAND_USER] = "user", + [EXPAND_USER_VARS] = "var", +}; + +/// List of names of address types. Must be alphabetical for completion. +static struct { + cmd_addr_T expand; + char *name; + char *shortname; +} addr_type_complete[] = +{ + { ADDR_ARGUMENTS, "arguments", "arg" }, + { ADDR_LINES, "lines", "line" }, + { ADDR_LOADED_BUFFERS, "loaded_buffers", "load" }, + { ADDR_TABS, "tabs", "tab" }, + { ADDR_BUFFERS, "buffers", "buf" }, + { ADDR_WINDOWS, "windows", "win" }, + { ADDR_QUICKFIX, "quickfix", "qf" }, + { ADDR_OTHER, "other", "?" }, + { ADDR_NONE, NULL, NULL } +}; + +/// Search for a user command that matches "eap->cmd". +/// Return cmdidx in "eap->cmdidx", flags in "eap->argt", idx in "eap->useridx". +/// Return a pointer to just after the command. +/// Return NULL if there is no matching command. +/// +/// @param *p end of the command (possibly including count) +/// @param full set to TRUE for a full match +/// @param xp used for completion, NULL otherwise +/// @param complp completion flags or NULL +char *find_ucmd(exarg_T *eap, char *p, int *full, expand_T *xp, int *complp) +{ + int len = (int)(p - eap->cmd); + int j, k, matchlen = 0; + ucmd_T *uc; + bool found = false; + bool possible = false; + char *cp, *np; // Point into typed cmd and test name + garray_T *gap; + bool amb_local = false; // Found ambiguous buffer-local command, + // only full match global is accepted. + + // Look for buffer-local user commands first, then global ones. + gap = &prevwin_curwin()->w_buffer->b_ucmds; + for (;;) { + for (j = 0; j < gap->ga_len; j++) { + uc = USER_CMD_GA(gap, j); + cp = eap->cmd; + np = (char *)uc->uc_name; + k = 0; + while (k < len && *np != NUL && *cp++ == *np++) { + k++; + } + if (k == len || (*np == NUL && ascii_isdigit(eap->cmd[k]))) { + // If finding a second match, the command is ambiguous. But + // not if a buffer-local command wasn't a full match and a + // global command is a full match. + if (k == len && found && *np != NUL) { + if (gap == &ucmds) { + return NULL; + } + amb_local = true; + } + + if (!found || (k == len && *np == NUL)) { + // If we matched up to a digit, then there could + // be another command including the digit that we + // should use instead. + if (k == len) { + found = true; + } else { + possible = true; + } + + if (gap == &ucmds) { + eap->cmdidx = CMD_USER; + } else { + eap->cmdidx = CMD_USER_BUF; + } + eap->argt = uc->uc_argt; + eap->useridx = j; + eap->addr_type = uc->uc_addr_type; + + if (complp != NULL) { + *complp = uc->uc_compl; + } + if (xp != NULL) { + xp->xp_luaref = uc->uc_compl_luaref; + xp->xp_arg = (char *)uc->uc_compl_arg; + xp->xp_script_ctx = uc->uc_script_ctx; + xp->xp_script_ctx.sc_lnum += sourcing_lnum; + } + // Do not search for further abbreviations + // if this is an exact match. + matchlen = k; + if (k == len && *np == NUL) { + if (full != NULL) { + *full = true; + } + amb_local = false; + break; + } + } + } + } + + // Stop if we found a full match or searched all. + if (j < gap->ga_len || gap == &ucmds) { + break; + } + gap = &ucmds; + } + + // Only found ambiguous matches. + if (amb_local) { + if (xp != NULL) { + xp->xp_context = EXPAND_UNSUCCESSFUL; + } + return NULL; + } + + // The match we found may be followed immediately by a number. Move "p" + // back to point to it. + if (found || possible) { + return p + (matchlen - len); + } + return p; +} + +const char *set_context_in_user_cmd(expand_T *xp, const char *arg_in) +{ + const char *arg = arg_in; + const char *p; + + // Check for attributes + while (*arg == '-') { + arg++; // Skip "-". + p = (const char *)skiptowhite((const char_u *)arg); + if (*p == NUL) { + // Cursor is still in the attribute. + p = strchr(arg, '='); + if (p == NULL) { + // No "=", so complete attribute names. + xp->xp_context = EXPAND_USER_CMD_FLAGS; + xp->xp_pattern = (char *)arg; + return NULL; + } + + // For the -complete, -nargs and -addr attributes, we complete + // their arguments as well. + if (STRNICMP(arg, "complete", p - arg) == 0) { + xp->xp_context = EXPAND_USER_COMPLETE; + xp->xp_pattern = (char *)p + 1; + return NULL; + } else if (STRNICMP(arg, "nargs", p - arg) == 0) { + xp->xp_context = EXPAND_USER_NARGS; + xp->xp_pattern = (char *)p + 1; + return NULL; + } else if (STRNICMP(arg, "addr", p - arg) == 0) { + xp->xp_context = EXPAND_USER_ADDR_TYPE; + xp->xp_pattern = (char *)p + 1; + return NULL; + } + return NULL; + } + arg = (const char *)skipwhite(p); + } + + // After the attributes comes the new command name. + p = (const char *)skiptowhite((const char_u *)arg); + if (*p == NUL) { + xp->xp_context = EXPAND_USER_COMMANDS; + xp->xp_pattern = (char *)arg; + return NULL; + } + + // And finally comes a normal command. + return (const char *)skipwhite(p); +} + +char *expand_user_command_name(int idx) +{ + return get_user_commands(NULL, idx - CMD_SIZE); +} + +/// Function given to ExpandGeneric() to obtain the list of user command names. +char *get_user_commands(expand_T *xp FUNC_ATTR_UNUSED, int idx) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + // In cmdwin, the alternative buffer should be used. + const buf_T *const buf = prevwin_curwin()->w_buffer; + + if (idx < buf->b_ucmds.ga_len) { + return (char *)USER_CMD_GA(&buf->b_ucmds, idx)->uc_name; + } + + idx -= buf->b_ucmds.ga_len; + if (idx < ucmds.ga_len) { + char *name = (char *)USER_CMD(idx)->uc_name; + + for (int i = 0; i < buf->b_ucmds.ga_len; i++) { + if (STRCMP(name, USER_CMD_GA(&buf->b_ucmds, i)->uc_name) == 0) { + // global command is overruled by buffer-local one + return ""; + } + } + return name; + } + return NULL; +} + +/// Get the name of user command "idx". "cmdidx" can be CMD_USER or +/// CMD_USER_BUF. +/// +/// @return NULL if the command is not found. +char *get_user_command_name(int idx, int cmdidx) +{ + if (cmdidx == CMD_USER && idx < ucmds.ga_len) { + return (char *)USER_CMD(idx)->uc_name; + } + if (cmdidx == CMD_USER_BUF) { + // In cmdwin, the alternative buffer should be used. + const buf_T *const buf = prevwin_curwin()->w_buffer; + + if (idx < buf->b_ucmds.ga_len) { + return (char *)USER_CMD_GA(&buf->b_ucmds, idx)->uc_name; + } + } + return NULL; +} + +/// Function given to ExpandGeneric() to obtain the list of user address type names. +char *get_user_cmd_addr_type(expand_T *xp, int idx) +{ + return addr_type_complete[idx].name; +} + +/// Function given to ExpandGeneric() to obtain the list of user command +/// attributes. +char *get_user_cmd_flags(expand_T *xp, int idx) +{ + static char *user_cmd_flags[] = { "addr", "bang", "bar", + "buffer", "complete", "count", + "nargs", "range", "register", + "keepscript" }; + + if (idx >= (int)ARRAY_SIZE(user_cmd_flags)) { + return NULL; + } + return user_cmd_flags[idx]; +} + +/// Function given to ExpandGeneric() to obtain the list of values for -nargs. +char *get_user_cmd_nargs(expand_T *xp, int idx) +{ + static char *user_cmd_nargs[] = { "0", "1", "*", "?", "+" }; + + if (idx >= (int)ARRAY_SIZE(user_cmd_nargs)) { + return NULL; + } + return user_cmd_nargs[idx]; +} + +static char *get_command_complete(int arg) +{ + if (arg >= (int)(ARRAY_SIZE(command_complete))) { + return NULL; + } else { + return (char *)command_complete[arg]; + } +} + +/// Function given to ExpandGeneric() to obtain the list of values for -complete. +char *get_user_cmd_complete(expand_T *xp, int idx) +{ + if (idx >= (int)ARRAY_SIZE(command_complete)) { + return NULL; + } + char *cmd_compl = get_command_complete(idx); + if (cmd_compl == NULL) { + return ""; + } else { + return cmd_compl; + } +} + +int cmdcomplete_str_to_type(const char *complete_str) +{ + for (int i = 0; i < (int)(ARRAY_SIZE(command_complete)); i++) { + char *cmd_compl = get_command_complete(i); + if (cmd_compl == NULL) { + continue; + } + if (strcmp(complete_str, command_complete[i]) == 0) { + return i; + } + } + + return EXPAND_NOTHING; +} + +static void uc_list(char *name, size_t name_len) +{ + int i, j; + bool found = false; + ucmd_T *cmd; + uint32_t a; + + // In cmdwin, the alternative buffer should be used. + const garray_T *gap = &prevwin_curwin()->w_buffer->b_ucmds; + for (;;) { + for (i = 0; i < gap->ga_len; i++) { + cmd = USER_CMD_GA(gap, i); + a = cmd->uc_argt; + + // Skip commands which don't match the requested prefix and + // commands filtered out. + if (STRNCMP(name, cmd->uc_name, name_len) != 0 + || message_filtered(cmd->uc_name)) { + continue; + } + + // Put out the title first time + if (!found) { + msg_puts_title(_("\n Name Args Address " + "Complete Definition")); + } + found = true; + msg_putchar('\n'); + if (got_int) { + break; + } + + // Special cases + int len = 4; + if (a & EX_BANG) { + msg_putchar('!'); + len--; + } + if (a & EX_REGSTR) { + msg_putchar('"'); + len--; + } + if (gap != &ucmds) { + msg_putchar('b'); + len--; + } + if (a & EX_TRLBAR) { + msg_putchar('|'); + len--; + } + while (len-- > 0) { + msg_putchar(' '); + } + + msg_outtrans_attr(cmd->uc_name, HL_ATTR(HLF_D)); + len = (int)STRLEN(cmd->uc_name) + 4; + + do { + msg_putchar(' '); + len++; + } while (len < 22); + + // "over" is how much longer the name is than the column width for + // the name, we'll try to align what comes after. + const int over = len - 22; + len = 0; + + // Arguments + switch (a & (EX_EXTRA | EX_NOSPC | EX_NEEDARG)) { + case 0: + IObuff[len++] = '0'; + break; + case (EX_EXTRA): + IObuff[len++] = '*'; + break; + case (EX_EXTRA | EX_NOSPC): + IObuff[len++] = '?'; + break; + case (EX_EXTRA | EX_NEEDARG): + IObuff[len++] = '+'; + break; + case (EX_EXTRA | EX_NOSPC | EX_NEEDARG): + IObuff[len++] = '1'; + break; + } + + do { + IObuff[len++] = ' '; + } while (len < 5 - over); + + // Address / Range + if (a & (EX_RANGE | EX_COUNT)) { + if (a & EX_COUNT) { + // -count=N + snprintf((char *)IObuff + len, IOSIZE, "%" PRId64 "c", + (int64_t)cmd->uc_def); + len += (int)STRLEN(IObuff + len); + } else if (a & EX_DFLALL) { + IObuff[len++] = '%'; + } else if (cmd->uc_def >= 0) { + // -range=N + snprintf((char *)IObuff + len, IOSIZE, "%" PRId64 "", + (int64_t)cmd->uc_def); + len += (int)STRLEN(IObuff + len); + } else { + IObuff[len++] = '.'; + } + } + + do { + IObuff[len++] = ' '; + } while (len < 8 - over); + + // Address Type + for (j = 0; addr_type_complete[j].expand != ADDR_NONE; j++) { + if (addr_type_complete[j].expand != ADDR_LINES + && addr_type_complete[j].expand == cmd->uc_addr_type) { + STRCPY(IObuff + len, addr_type_complete[j].shortname); + len += (int)STRLEN(IObuff + len); + break; + } + } + + do { + IObuff[len++] = ' '; + } while (len < 13 - over); + + // Completion + char *cmd_compl = get_command_complete(cmd->uc_compl); + if (cmd_compl != NULL) { + STRCPY(IObuff + len, get_command_complete(cmd->uc_compl)); + len += (int)STRLEN(IObuff + len); + } + + do { + IObuff[len++] = ' '; + } while (len < 25 - over); + + IObuff[len] = '\0'; + msg_outtrans((char *)IObuff); + + if (cmd->uc_luaref != LUA_NOREF) { + char *fn = nlua_funcref_str(cmd->uc_luaref); + msg_puts_attr(fn, HL_ATTR(HLF_8)); + xfree(fn); + // put the description on a new line + if (*cmd->uc_rep != NUL) { + msg_puts("\n "); + } + } + + msg_outtrans_special(cmd->uc_rep, false, + name_len == 0 ? Columns - 47 : 0); + if (p_verbose > 0) { + last_set_msg(cmd->uc_script_ctx); + } + line_breakcheck(); + if (got_int) { + break; + } + } + if (gap == &ucmds || i < gap->ga_len) { + break; + } + gap = &ucmds; + } + + if (!found) { + msg(_("No user-defined commands found")); + } +} + +/// Parse address type argument +int parse_addr_type_arg(char *value, int vallen, cmd_addr_T *addr_type_arg) + FUNC_ATTR_NONNULL_ALL +{ + int i, a, b; + + for (i = 0; addr_type_complete[i].expand != ADDR_NONE; i++) { + a = (int)STRLEN(addr_type_complete[i].name) == vallen; + b = STRNCMP(value, addr_type_complete[i].name, vallen) == 0; + if (a && b) { + *addr_type_arg = addr_type_complete[i].expand; + break; + } + } + + if (addr_type_complete[i].expand == ADDR_NONE) { + char *err = value; + + for (i = 0; err[i] != NUL && !ascii_iswhite(err[i]); i++) {} + err[i] = NUL; + semsg(_("E180: Invalid address type value: %s"), err); + return FAIL; + } + + return OK; +} + +/// Parse a completion argument "value[vallen]". +/// The detected completion goes in "*complp", argument type in "*argt". +/// When there is an argument, for function and user defined completion, it's +/// copied to allocated memory and stored in "*compl_arg". +/// +/// @return FAIL if something is wrong. +int parse_compl_arg(const char *value, int vallen, int *complp, uint32_t *argt, char **compl_arg) + FUNC_ATTR_NONNULL_ALL +{ + const char *arg = NULL; + size_t arglen = 0; + int i; + int valend = vallen; + + // Look for any argument part - which is the part after any ',' + for (i = 0; i < vallen; i++) { + if (value[i] == ',') { + arg = (char *)&value[i + 1]; + arglen = (size_t)(vallen - i - 1); + valend = i; + break; + } + } + + for (i = 0; i < (int)ARRAY_SIZE(command_complete); i++) { + if (get_command_complete(i) == NULL) { + continue; + } + if ((int)STRLEN(command_complete[i]) == valend + && STRNCMP(value, command_complete[i], valend) == 0) { + *complp = i; + if (i == EXPAND_BUFFERS) { + *argt |= EX_BUFNAME; + } else if (i == EXPAND_DIRECTORIES || i == EXPAND_FILES) { + *argt |= EX_XFILE; + } + break; + } + } + + if (i == (int)ARRAY_SIZE(command_complete)) { + semsg(_("E180: Invalid complete value: %s"), value); + return FAIL; + } + + if (*complp != EXPAND_USER_DEFINED && *complp != EXPAND_USER_LIST + && arg != NULL) { + emsg(_("E468: Completion argument only allowed for custom completion")); + return FAIL; + } + + if ((*complp == EXPAND_USER_DEFINED || *complp == EXPAND_USER_LIST) + && arg == NULL) { + emsg(_("E467: Custom completion requires a function argument")); + return FAIL; + } + + if (arg != NULL) { + *compl_arg = xstrnsave(arg, arglen); + } + return OK; +} + +static int uc_scan_attr(char *attr, size_t len, uint32_t *argt, long *def, int *flags, int *complp, + char_u **compl_arg, cmd_addr_T *addr_type_arg) + FUNC_ATTR_NONNULL_ALL +{ + char *p; + + if (len == 0) { + emsg(_("E175: No attribute specified")); + return FAIL; + } + + // First, try the simple attributes (no arguments) + if (STRNICMP(attr, "bang", len) == 0) { + *argt |= EX_BANG; + } else if (STRNICMP(attr, "buffer", len) == 0) { + *flags |= UC_BUFFER; + } else if (STRNICMP(attr, "register", len) == 0) { + *argt |= EX_REGSTR; + } else if (STRNICMP(attr, "keepscript", len) == 0) { + *argt |= EX_KEEPSCRIPT; + } else if (STRNICMP(attr, "bar", len) == 0) { + *argt |= EX_TRLBAR; + } else { + int i; + char *val = NULL; + size_t vallen = 0; + size_t attrlen = len; + + // Look for the attribute name - which is the part before any '=' + for (i = 0; i < (int)len; i++) { + if (attr[i] == '=') { + val = &attr[i + 1]; + vallen = len - (size_t)i - 1; + attrlen = (size_t)i; + break; + } + } + + if (STRNICMP(attr, "nargs", attrlen) == 0) { + if (vallen == 1) { + if (*val == '0') { + // Do nothing - this is the default; + } else if (*val == '1') { + *argt |= (EX_EXTRA | EX_NOSPC | EX_NEEDARG); + } else if (*val == '*') { + *argt |= EX_EXTRA; + } else if (*val == '?') { + *argt |= (EX_EXTRA | EX_NOSPC); + } else if (*val == '+') { + *argt |= (EX_EXTRA | EX_NEEDARG); + } else { + goto wrong_nargs; + } + } else { +wrong_nargs: + emsg(_("E176: Invalid number of arguments")); + return FAIL; + } + } else if (STRNICMP(attr, "range", attrlen) == 0) { + *argt |= EX_RANGE; + if (vallen == 1 && *val == '%') { + *argt |= EX_DFLALL; + } else if (val != NULL) { + p = val; + if (*def >= 0) { +two_count: + emsg(_("E177: Count cannot be specified twice")); + return FAIL; + } + + *def = getdigits_long(&p, true, 0); + *argt |= EX_ZEROR; + + if (p != val + vallen || vallen == 0) { +invalid_count: + emsg(_("E178: Invalid default value for count")); + return FAIL; + } + } + // default for -range is using buffer lines + if (*addr_type_arg == ADDR_NONE) { + *addr_type_arg = ADDR_LINES; + } + } else if (STRNICMP(attr, "count", attrlen) == 0) { + *argt |= (EX_COUNT | EX_ZEROR | EX_RANGE); + // default for -count is using any number + if (*addr_type_arg == ADDR_NONE) { + *addr_type_arg = ADDR_OTHER; + } + + if (val != NULL) { + p = val; + if (*def >= 0) { + goto two_count; + } + + *def = getdigits_long(&p, true, 0); + + if (p != val + vallen) { + goto invalid_count; + } + } + + if (*def < 0) { + *def = 0; + } + } else if (STRNICMP(attr, "complete", attrlen) == 0) { + if (val == NULL) { + emsg(_("E179: argument required for -complete")); + return FAIL; + } + + if (parse_compl_arg(val, (int)vallen, complp, argt, (char **)compl_arg) + == FAIL) { + return FAIL; + } + } else if (STRNICMP(attr, "addr", attrlen) == 0) { + *argt |= EX_RANGE; + if (val == NULL) { + emsg(_("E179: argument required for -addr")); + return FAIL; + } + if (parse_addr_type_arg(val, (int)vallen, addr_type_arg) == FAIL) { + return FAIL; + } + if (*addr_type_arg != ADDR_LINES) { + *argt |= EX_ZEROR; + } + } else { + char ch = attr[len]; + attr[len] = '\0'; + semsg(_("E181: Invalid attribute: %s"), attr); + attr[len] = ch; + return FAIL; + } + } + + return OK; +} + +/// Check for a valid user command name +/// +/// If the given {name} is valid, then a pointer to the end of the valid name is returned. +/// Otherwise, returns NULL. +char *uc_validate_name(char *name) +{ + if (ASCII_ISALPHA(*name)) { + while (ASCII_ISALNUM(*name)) { + name++; + } + } + if (!ends_excmd(*name) && !ascii_iswhite(*name)) { + return NULL; + } + + return name; +} + +/// Create a new user command {name}, if one doesn't already exist. +/// +/// This function takes ownership of compl_arg, compl_luaref, and luaref. +/// +/// @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, long def, int flags, + int compl, char *compl_arg, LuaRef compl_luaref, LuaRef preview_luaref, + cmd_addr_T addr_type, LuaRef luaref, bool force) + FUNC_ATTR_NONNULL_ARG(1, 3) +{ + ucmd_T *cmd = NULL; + int i; + int cmp = 1; + char *rep_buf = NULL; + garray_T *gap; + + replace_termcodes(rep, STRLEN(rep), &rep_buf, 0, NULL, CPO_TO_CPO_FLAGS); + if (rep_buf == NULL) { + // Can't replace termcodes - try using the string as is + rep_buf = xstrdup(rep); + } + + // get address of growarray: global or in curbuf + if (flags & UC_BUFFER) { + gap = &curbuf->b_ucmds; + if (gap->ga_itemsize == 0) { + ga_init(gap, (int)sizeof(ucmd_T), 4); + } + } else { + gap = &ucmds; + } + + // Search for the command in the already defined commands. + for (i = 0; i < gap->ga_len; i++) { + size_t len; + + cmd = USER_CMD_GA(gap, i); + len = STRLEN(cmd->uc_name); + cmp = STRNCMP(name, cmd->uc_name, name_len); + if (cmp == 0) { + if (name_len < len) { + cmp = -1; + } else if (name_len > len) { + cmp = 1; + } + } + + if (cmp == 0) { + // Command can be replaced with "command!" and when sourcing the + // same script again, but only once. + if (!force + && (cmd->uc_script_ctx.sc_sid != current_sctx.sc_sid + || cmd->uc_script_ctx.sc_seq == current_sctx.sc_seq)) { + semsg(_("E174: Command already exists: add ! to replace it: %s"), + name); + goto fail; + } + + XFREE_CLEAR(cmd->uc_rep); + XFREE_CLEAR(cmd->uc_compl_arg); + NLUA_CLEAR_REF(cmd->uc_luaref); + NLUA_CLEAR_REF(cmd->uc_compl_luaref); + NLUA_CLEAR_REF(cmd->uc_preview_luaref); + break; + } + + // Stop as soon as we pass the name to add + if (cmp < 0) { + break; + } + } + + // Extend the array unless we're replacing an existing command + if (cmp != 0) { + ga_grow(gap, 1); + + char *const p = xstrnsave(name, name_len); + + cmd = USER_CMD_GA(gap, i); + memmove(cmd + 1, cmd, (size_t)(gap->ga_len - i) * sizeof(ucmd_T)); + + gap->ga_len++; + + cmd->uc_name = (char_u *)p; + } + + cmd->uc_rep = (char_u *)rep_buf; + cmd->uc_argt = argt; + cmd->uc_def = def; + cmd->uc_compl = compl; + cmd->uc_script_ctx = current_sctx; + cmd->uc_script_ctx.sc_lnum += sourcing_lnum; + nlua_set_sctx(&cmd->uc_script_ctx); + cmd->uc_compl_arg = (char_u *)compl_arg; + cmd->uc_compl_luaref = compl_luaref; + cmd->uc_preview_luaref = preview_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); + NLUA_CLEAR_REF(preview_luaref); + return FAIL; +} + +/// ":command ..." +void ex_command(exarg_T *eap) +{ + char *name; + char *end; + char *p; + uint32_t argt = 0; + long def = -1; + int flags = 0; + int compl = EXPAND_NOTHING; + char *compl_arg = NULL; + cmd_addr_T addr_type_arg = ADDR_NONE; + int has_attr = (eap->arg[0] == '-'); + size_t name_len; + + p = eap->arg; + + // Check for attributes + while (*p == '-') { + p++; + end = (char *)skiptowhite((char_u *)p); + if (uc_scan_attr(p, (size_t)(end - p), &argt, &def, &flags, &compl, (char_u **)&compl_arg, + &addr_type_arg) == FAIL) { + return; + } + p = skipwhite(end); + } + + // Get the name (if any) and skip to the following argument. + name = p; + end = uc_validate_name(name); + if (!end) { + emsg(_("E182: Invalid command name")); + return; + } + name_len = (size_t)(end - name); + + // If there is nothing after the name, and no attributes were specified, + // we are listing commands + p = skipwhite(end); + if (!has_attr && ends_excmd(*p)) { + uc_list(name, name_len); + } else if (!ASCII_ISUPPER(*name)) { + emsg(_("E183: User defined commands must start with an uppercase letter")); + } else if (name_len <= 4 && STRNCMP(name, "Next", name_len) == 0) { + emsg(_("E841: Reserved name, cannot be used for user defined command")); + } else if (compl > 0 && (argt & EX_EXTRA) == 0) { + emsg(_(e_complete_used_without_allowing_arguments)); + } else { + uc_add_command(name, name_len, p, argt, def, flags, compl, compl_arg, LUA_NOREF, LUA_NOREF, + addr_type_arg, LUA_NOREF, eap->forceit); + } +} + +/// ":comclear" +/// Clear all user commands, global and for current buffer. +void ex_comclear(exarg_T *eap) +{ + uc_clear(&ucmds); + uc_clear(&curbuf->b_ucmds); +} + +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); + NLUA_CLEAR_REF(cmd->uc_preview_luaref); +} + +/// Clear all user commands for "gap". +void uc_clear(garray_T *gap) +{ + GA_DEEP_CLEAR(gap, ucmd_T, free_ucmd); +} + +void ex_delcommand(exarg_T *eap) +{ + int i = 0; + ucmd_T *cmd = NULL; + int res = -1; + garray_T *gap; + const char *arg = eap->arg; + bool buffer_only = false; + + if (STRNCMP(arg, "-buffer", 7) == 0 && ascii_iswhite(arg[7])) { + buffer_only = true; + arg = skipwhite(arg + 7); + } + + gap = &curbuf->b_ucmds; + for (;;) { + for (i = 0; i < gap->ga_len; i++) { + cmd = USER_CMD_GA(gap, i); + res = STRCMP(arg, cmd->uc_name); + if (res <= 0) { + break; + } + } + if (gap == &ucmds || res == 0 || buffer_only) { + break; + } + gap = &ucmds; + } + + if (res != 0) { + semsg(_(buffer_only + ? e_no_such_user_defined_command_in_current_buffer_str + : e_no_such_user_defined_command_str), + arg); + return; + } + + free_ucmd(cmd); + + gap->ga_len--; + + if (i < gap->ga_len) { + memmove(cmd, cmd + 1, (size_t)(gap->ga_len - i) * sizeof(ucmd_T)); + } +} + +/// Split a string by unescaped whitespace (space & tab), used for f-args on Lua commands callback. +/// Similar to uc_split_args(), but does not allocate, add quotes, add commas and is an iterator. +/// +/// @param[in] arg String to split +/// @param[in] arglen Length of {arg} +/// @param[inout] end Index of last character of previous iteration +/// @param[out] buf Buffer to copy string into +/// @param[out] len Length of string in {buf} +/// +/// @return true if iteration is complete, else false +bool uc_split_args_iter(const char *arg, size_t arglen, size_t *end, char *buf, size_t *len) +{ + if (!arglen) { + return true; + } + + size_t pos = *end; + while (pos < arglen && ascii_iswhite(arg[pos])) { + pos++; + } + + size_t l = 0; + for (; pos < arglen - 1; pos++) { + if (arg[pos] == '\\' && (arg[pos + 1] == '\\' || ascii_iswhite(arg[pos + 1]))) { + buf[l++] = arg[++pos]; + } else { + buf[l++] = arg[pos]; + if (ascii_iswhite(arg[pos + 1])) { + *end = pos + 1; + *len = l; + return false; + } + } + } + + if (pos < arglen && !ascii_iswhite(arg[pos])) { + buf[l++] = arg[pos]; + } + + *len = l; + return true; +} + +/// split and quote args for <f-args> +static char *uc_split_args(char *arg, char **args, size_t *arglens, size_t argc, size_t *lenp) +{ + char *buf; + char *p; + char *q; + int len; + + // Precalculate length + len = 2; // Initial and final quotes + if (args == NULL) { + p = arg; + + while (*p) { + if (p[0] == '\\' && p[1] == '\\') { + len += 2; + p += 2; + } else if (p[0] == '\\' && ascii_iswhite(p[1])) { + len += 1; + p += 2; + } else if (*p == '\\' || *p == '"') { + len += 2; + p += 1; + } else if (ascii_iswhite(*p)) { + p = skipwhite(p); + if (*p == NUL) { + break; + } + len += 4; // ", " + } else { + const int charlen = utfc_ptr2len(p); + + len += charlen; + p += charlen; + } + } + } else { + for (size_t i = 0; i < argc; i++) { + p = args[i]; + const char *arg_end = args[i] + arglens[i]; + + while (p < arg_end) { + if (*p == '\\' || *p == '"') { + len += 2; + p += 1; + } else { + const int charlen = utfc_ptr2len(p); + + len += charlen; + p += charlen; + } + } + + if (i != argc - 1) { + len += 4; // ", " + } + } + } + + buf = xmalloc((size_t)len + 1); + + q = buf; + *q++ = '"'; + + if (args == NULL) { + p = arg; + while (*p) { + if (p[0] == '\\' && p[1] == '\\') { + *q++ = '\\'; + *q++ = '\\'; + p += 2; + } else if (p[0] == '\\' && ascii_iswhite(p[1])) { + *q++ = p[1]; + p += 2; + } else if (*p == '\\' || *p == '"') { + *q++ = '\\'; + *q++ = *p++; + } else if (ascii_iswhite(*p)) { + p = skipwhite(p); + if (*p == NUL) { + break; + } + *q++ = '"'; + *q++ = ','; + *q++ = ' '; + *q++ = '"'; + } else { + mb_copy_char((const char_u **)&p, (char_u **)&q); + } + } + } else { + for (size_t i = 0; i < argc; i++) { + p = args[i]; + const char *arg_end = args[i] + arglens[i]; + + while (p < arg_end) { + if (*p == '\\' || *p == '"') { + *q++ = '\\'; + *q++ = *p++; + } else { + mb_copy_char((const char_u **)&p, (char_u **)&q); + } + } + if (i != argc - 1) { + *q++ = '"'; + *q++ = ','; + *q++ = ' '; + *q++ = '"'; + } + } + } + + *q++ = '"'; + *q = 0; + + *lenp = (size_t)len; + return buf; +} + +static size_t add_cmd_modifier(char *buf, char *mod_str, bool *multi_mods) +{ + size_t result = STRLEN(mod_str); + if (*multi_mods) { + result++; + } + + if (buf != NULL) { + if (*multi_mods) { + STRCAT(buf, " "); + } + STRCAT(buf, mod_str); + } + + *multi_mods = true; + return result; +} + +/// Add modifiers from "cmod->cmod_split" to "buf". Set "multi_mods" when one +/// was added. +/// +/// @return the number of bytes added +size_t add_win_cmd_modifers(char *buf, const cmdmod_T *cmod, bool *multi_mods) +{ + size_t result = 0; + + // :aboveleft and :leftabove + if (cmod->cmod_split & WSP_ABOVE) { + result += add_cmd_modifier(buf, "aboveleft", multi_mods); + } + // :belowright and :rightbelow + if (cmod->cmod_split & WSP_BELOW) { + result += add_cmd_modifier(buf, "belowright", multi_mods); + } + // :botright + if (cmod->cmod_split & WSP_BOT) { + result += add_cmd_modifier(buf, "botright", multi_mods); + } + + // :tab + if (cmod->cmod_tab > 0) { + result += add_cmd_modifier(buf, "tab", multi_mods); + } + // :topleft + if (cmod->cmod_split & WSP_TOP) { + result += add_cmd_modifier(buf, "topleft", multi_mods); + } + // :vertical + if (cmod->cmod_split & WSP_VERT) { + result += add_cmd_modifier(buf, "vertical", multi_mods); + } + return result; +} + +/// Generate text for the "cmod" command modifiers. +/// If "buf" is NULL just return the length. +size_t uc_mods(char *buf, const cmdmod_T *cmod, bool quote) +{ + size_t result = 0; + bool multi_mods = false; + + typedef struct { + int flag; + char *name; + } mod_entry_T; + static mod_entry_T mod_entries[] = { + { CMOD_BROWSE, "browse" }, + { CMOD_CONFIRM, "confirm" }, + { CMOD_HIDE, "hide" }, + { CMOD_KEEPALT, "keepalt" }, + { CMOD_KEEPJUMPS, "keepjumps" }, + { CMOD_KEEPMARKS, "keepmarks" }, + { CMOD_KEEPPATTERNS, "keeppatterns" }, + { CMOD_LOCKMARKS, "lockmarks" }, + { CMOD_NOSWAPFILE, "noswapfile" }, + { CMOD_UNSILENT, "unsilent" }, + { CMOD_NOAUTOCMD, "noautocmd" }, + { CMOD_SANDBOX, "sandbox" }, + }; + + result = quote ? 2 : 0; + if (buf != NULL) { + if (quote) { + *buf++ = '"'; + } + *buf = '\0'; + } + + // the modifiers that are simple flags + for (size_t i = 0; i < ARRAY_SIZE(mod_entries); i++) { + if (cmod->cmod_flags & mod_entries[i].flag) { + result += add_cmd_modifier(buf, mod_entries[i].name, &multi_mods); + } + } + + // :silent + if (cmod->cmod_flags & CMOD_SILENT) { + result += add_cmd_modifier(buf, + (cmod->cmod_flags & CMOD_ERRSILENT) ? "silent!" : "silent", + &multi_mods); + } + // :verbose + if (cmod->cmod_verbose > 0) { + int verbose_value = cmod->cmod_verbose - 1; + if (verbose_value == 1) { + result += add_cmd_modifier(buf, "verbose", &multi_mods); + } else { + char verbose_buf[NUMBUFLEN]; + snprintf(verbose_buf, NUMBUFLEN, "%dverbose", verbose_value); + result += add_cmd_modifier(buf, verbose_buf, &multi_mods); + } + } + // flags from cmod->cmod_split + result += add_win_cmd_modifers(buf, cmod, &multi_mods); + + if (quote && buf != NULL) { + buf += result - 2; + *buf = '"'; + } + return result; +} + +/// Check for a <> code in a user command. +/// +/// @param code points to the '<'. "len" the length of the <> (inclusive). +/// @param buf is where the result is to be added. +/// @param cmd the user command we're expanding +/// @param eap ex arguments +/// @param split_buf points to a buffer used for splitting, caller should free it. +/// @param split_len is the length of what "split_buf" contains. +/// +/// @return the length of the replacement, which has been added to "buf". +/// Return -1 if there was no match, and only the "<" has been copied. +static size_t uc_check_code(char *code, size_t len, char *buf, ucmd_T *cmd, exarg_T *eap, + char **split_buf, size_t *split_len) +{ + size_t result = 0; + char *p = code + 1; + size_t l = len - 2; + int quote = 0; + enum { + ct_ARGS, + ct_BANG, + ct_COUNT, + ct_LINE1, + ct_LINE2, + ct_RANGE, + ct_MODS, + ct_REGISTER, + ct_LT, + ct_NONE, + } type = ct_NONE; + + if ((vim_strchr("qQfF", *p) != NULL) && p[1] == '-') { + quote = (*p == 'q' || *p == 'Q') ? 1 : 2; + p += 2; + l -= 2; + } + + l++; + if (l <= 1) { + type = ct_NONE; + } else if (STRNICMP(p, "args>", l) == 0) { + type = ct_ARGS; + } else if (STRNICMP(p, "bang>", l) == 0) { + type = ct_BANG; + } else if (STRNICMP(p, "count>", l) == 0) { + type = ct_COUNT; + } else if (STRNICMP(p, "line1>", l) == 0) { + type = ct_LINE1; + } else if (STRNICMP(p, "line2>", l) == 0) { + type = ct_LINE2; + } else if (STRNICMP(p, "range>", l) == 0) { + type = ct_RANGE; + } else if (STRNICMP(p, "lt>", l) == 0) { + type = ct_LT; + } else if (STRNICMP(p, "reg>", l) == 0 || STRNICMP(p, "register>", l) == 0) { + type = ct_REGISTER; + } else if (STRNICMP(p, "mods>", l) == 0) { + type = ct_MODS; + } + + switch (type) { + case ct_ARGS: + // Simple case first + if (*eap->arg == NUL) { + if (quote == 1) { + result = 2; + if (buf != NULL) { + STRCPY(buf, "''"); + } + } else { + result = 0; + } + break; + } + + // When specified there is a single argument don't split it. + // Works for ":Cmd %" when % is "a b c". + if ((eap->argt & EX_NOSPC) && quote == 2) { + quote = 1; + } + + switch (quote) { + case 0: // No quoting, no splitting + result = STRLEN(eap->arg); + if (buf != NULL) { + STRCPY(buf, eap->arg); + } + break; + case 1: // Quote, but don't split + result = STRLEN(eap->arg) + 2; + for (p = eap->arg; *p; p++) { + if (*p == '\\' || *p == '"') { + result++; + } + } + + if (buf != NULL) { + *buf++ = '"'; + for (p = eap->arg; *p; p++) { + if (*p == '\\' || *p == '"') { + *buf++ = '\\'; + } + *buf++ = *p; + } + *buf = '"'; + } + + break; + case 2: // Quote and split (<f-args>) + // This is hard, so only do it once, and cache the result + if (*split_buf == NULL) { + *split_buf = uc_split_args(eap->arg, eap->args, eap->arglens, eap->argc, split_len); + } + + result = *split_len; + if (buf != NULL && result != 0) { + STRCPY(buf, *split_buf); + } + + break; + } + break; + + case ct_BANG: + result = eap->forceit ? 1 : 0; + if (quote) { + result += 2; + } + if (buf != NULL) { + if (quote) { + *buf++ = '"'; + } + if (eap->forceit) { + *buf++ = '!'; + } + if (quote) { + *buf = '"'; + } + } + break; + + case ct_LINE1: + case ct_LINE2: + case ct_RANGE: + case ct_COUNT: { + char num_buf[20]; + long num = (type == ct_LINE1) ? eap->line1 : + (type == ct_LINE2) ? eap->line2 : + (type == ct_RANGE) ? eap->addr_count : + (eap->addr_count > 0) ? eap->line2 : cmd->uc_def; + size_t num_len; + + snprintf(num_buf, sizeof(num_buf), "%" PRId64, (int64_t)num); + num_len = STRLEN(num_buf); + result = num_len; + + if (quote) { + result += 2; + } + + if (buf != NULL) { + if (quote) { + *buf++ = '"'; + } + STRCPY(buf, num_buf); + buf += num_len; + if (quote) { + *buf = '"'; + } + } + + break; + } + + case ct_MODS: + result = uc_mods(buf, &cmdmod, quote); + break; + + case ct_REGISTER: + result = eap->regname ? 1 : 0; + if (quote) { + result += 2; + } + if (buf != NULL) { + if (quote) { + *buf++ = '\''; + } + if (eap->regname) { + *buf++ = (char)eap->regname; + } + if (quote) { + *buf = '\''; + } + } + break; + + case ct_LT: + result = 1; + if (buf != NULL) { + *buf = '<'; + } + break; + + default: + // Not recognized: just copy the '<' and return -1. + result = (size_t)-1; + if (buf != NULL) { + *buf = '<'; + } + break; + } + + return result; +} + +int do_ucmd(exarg_T *eap, bool preview) +{ + char *buf; + char *p; + char *q; + + char *start; + char *end = NULL; + char *ksp; + size_t len, totlen; + + size_t split_len = 0; + char *split_buf = NULL; + ucmd_T *cmd; + + if (eap->cmdidx == CMD_USER) { + cmd = USER_CMD(eap->useridx); + } else { + cmd = USER_CMD_GA(&curbuf->b_ucmds, eap->useridx); + } + + if (preview) { + assert(cmd->uc_preview_luaref > 0); + return nlua_do_ucmd(cmd, eap, true); + } + + if (cmd->uc_luaref > 0) { + nlua_do_ucmd(cmd, eap, false); + return 0; + } + + // Replace <> in the command by the arguments. + // First round: "buf" is NULL, compute length, allocate "buf". + // Second round: copy result into "buf". + buf = NULL; + for (;;) { + p = (char *)cmd->uc_rep; // source + q = buf; // destination + totlen = 0; + + for (;;) { + start = vim_strchr(p, '<'); + if (start != NULL) { + end = vim_strchr(start + 1, '>'); + } + if (buf != NULL) { + for (ksp = p; *ksp != NUL && (char_u)(*ksp) != K_SPECIAL; ksp++) {} + if ((char_u)(*ksp) == K_SPECIAL + && (start == NULL || ksp < start || end == NULL) + && ((char_u)ksp[1] == KS_SPECIAL && ksp[2] == KE_FILLER)) { + // K_SPECIAL has been put in the buffer as K_SPECIAL + // KS_SPECIAL KE_FILLER, like for mappings, but + // do_cmdline() doesn't handle that, so convert it back. + len = (size_t)(ksp - p); + if (len > 0) { + memmove(q, p, len); + q += len; + } + *q++ = (char)K_SPECIAL; + p = ksp + 3; + continue; + } + } + + // break if no <item> is found + if (start == NULL || end == NULL) { + break; + } + + // Include the '>' + end++; + + // Take everything up to the '<' + len = (size_t)(start - p); + if (buf == NULL) { + totlen += len; + } else { + memmove(q, p, len); + q += len; + } + + len = uc_check_code(start, (size_t)(end - start), q, cmd, eap, &split_buf, &split_len); + if (len == (size_t)-1) { + // no match, continue after '<' + p = start + 1; + len = 1; + } else { + p = end; + } + if (buf == NULL) { + totlen += len; + } else { + q += len; + } + } + if (buf != NULL) { // second time here, finished + STRCPY(q, p); + break; + } + + totlen += STRLEN(p); // Add on the trailing characters + buf = xmalloc(totlen + 1); + } + + sctx_T save_current_sctx; + bool restore_current_sctx = false; + if ((cmd->uc_argt & EX_KEEPSCRIPT) == 0) { + restore_current_sctx = true; + save_current_sctx = current_sctx; + current_sctx.sc_sid = cmd->uc_script_ctx.sc_sid; + } + (void)do_cmdline(buf, eap->getline, eap->cookie, + DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED); + + // Careful: Do not use "cmd" here, it may have become invalid if a user + // command was added. + if (restore_current_sctx) { + current_sctx = save_current_sctx; + } + xfree(buf); + xfree(split_buf); + + return 0; +} + +/// Gets a map of maps describing user-commands defined for buffer `buf` or +/// defined globally if `buf` is NULL. +/// +/// @param buf Buffer to inspect, or NULL to get global commands. +/// +/// @return Map of maps describing commands +Dictionary commands_array(buf_T *buf) +{ + Dictionary rv = ARRAY_DICT_INIT; + char str[20]; + garray_T *gap = (buf == NULL) ? &ucmds : &buf->b_ucmds; + + for (int i = 0; i < gap->ga_len; i++) { + char arg[2] = { 0, 0 }; + Dictionary d = ARRAY_DICT_INIT; + ucmd_T *cmd = USER_CMD_GA(gap, i); + + PUT(d, "name", STRING_OBJ(cstr_to_string((char *)cmd->uc_name))); + PUT(d, "definition", STRING_OBJ(cstr_to_string((char *)cmd->uc_rep))); + PUT(d, "script_id", INTEGER_OBJ(cmd->uc_script_ctx.sc_sid)); + PUT(d, "bang", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_BANG))); + PUT(d, "bar", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_TRLBAR))); + PUT(d, "register", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_REGSTR))); + PUT(d, "keepscript", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_KEEPSCRIPT))); + PUT(d, "preview", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_PREVIEW))); + + switch (cmd->uc_argt & (EX_EXTRA | EX_NOSPC | EX_NEEDARG)) { + case 0: + arg[0] = '0'; break; + case (EX_EXTRA): + arg[0] = '*'; break; + case (EX_EXTRA | EX_NOSPC): + arg[0] = '?'; break; + case (EX_EXTRA | EX_NEEDARG): + arg[0] = '+'; break; + case (EX_EXTRA | EX_NOSPC | EX_NEEDARG): + arg[0] = '1'; break; + } + PUT(d, "nargs", STRING_OBJ(cstr_to_string(arg))); + + char *cmd_compl = get_command_complete(cmd->uc_compl); + PUT(d, "complete", (cmd_compl == NULL + ? NIL : STRING_OBJ(cstr_to_string(cmd_compl)))); + PUT(d, "complete_arg", cmd->uc_compl_arg == NULL + ? NIL : STRING_OBJ(cstr_to_string((char *)cmd->uc_compl_arg))); + + Object obj = NIL; + if (cmd->uc_argt & EX_COUNT) { + if (cmd->uc_def >= 0) { + snprintf(str, sizeof(str), "%" PRId64, (int64_t)cmd->uc_def); + obj = STRING_OBJ(cstr_to_string(str)); // -count=N + } else { + obj = STRING_OBJ(cstr_to_string("0")); // -count + } + } + PUT(d, "count", obj); + + obj = NIL; + if (cmd->uc_argt & EX_RANGE) { + if (cmd->uc_argt & EX_DFLALL) { + obj = STRING_OBJ(cstr_to_string("%")); // -range=% + } else if (cmd->uc_def >= 0) { + snprintf(str, sizeof(str), "%" PRId64, (int64_t)cmd->uc_def); + obj = STRING_OBJ(cstr_to_string(str)); // -range=N + } else { + obj = STRING_OBJ(cstr_to_string(".")); // -range + } + } + PUT(d, "range", obj); + + obj = NIL; + for (int j = 0; addr_type_complete[j].expand != ADDR_NONE; j++) { + if (addr_type_complete[j].expand != ADDR_LINES + && addr_type_complete[j].expand == cmd->uc_addr_type) { + obj = STRING_OBJ(cstr_to_string(addr_type_complete[j].name)); + break; + } + } + PUT(d, "addr", obj); + + PUT(rv, (char *)cmd->uc_name, DICTIONARY_OBJ(d)); + } + return rv; +} diff --git a/src/nvim/usercmd.h b/src/nvim/usercmd.h new file mode 100644 index 0000000000..637862b216 --- /dev/null +++ b/src/nvim/usercmd.h @@ -0,0 +1,30 @@ +#ifndef NVIM_USERCMD_H +#define NVIM_USERCMD_H + +#include "nvim/ex_cmds_defs.h" + +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_preview_luaref; // Reference to Lua preview 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 "usercmd.h.generated.h" +#endif +#endif // NVIM_USERCMD_H diff --git a/src/nvim/window.c b/src/nvim/window.c index abb277bd23..39346faa14 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -5523,6 +5523,7 @@ void win_setheight_win(int height, win_T *win) msg_row = row; msg_col = 0; redraw_all_later(NOT_VALID); + redraw_cmdline = true; } } |