aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJosh Rahm <joshuarahm@gmail.com>2022-08-07 16:01:34 -0600
committerJosh Rahm <joshuarahm@gmail.com>2022-08-07 16:01:34 -0600
commita5f27a311fb28797a72b8aa16ec7122c5a1b15e4 (patch)
tree3732f7339e29431f31310aef6ffc802cf4f6255d /src
parent6c909fedc924d9f4257aa204b0168c6177cc5d28 (diff)
parent629169462a82f0fbb7a8911a4554894537d6776c (diff)
downloadrneovim-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')
-rwxr-xr-xsrc/nvim/CMakeLists.txt24
-rw-r--r--src/nvim/api/buffer.c28
-rw-r--r--src/nvim/api/command.c11
-rw-r--r--src/nvim/autocmd.c9
-rw-r--r--src/nvim/buffer.c5
-rw-r--r--src/nvim/eval.c6
-rw-r--r--src/nvim/eval.lua1
-rw-r--r--src/nvim/eval/funcs.c8
-rw-r--r--src/nvim/eval/typval.c11
-rw-r--r--src/nvim/eval/userfunc.c8
-rw-r--r--src/nvim/eval/vars.c11
-rw-r--r--src/nvim/ex_cmds.c2
-rw-r--r--src/nvim/ex_cmds_defs.h56
-rw-r--r--src/nvim/ex_docmd.c2352
-rw-r--r--src/nvim/ex_docmd.h21
-rw-r--r--src/nvim/ex_eval.c46
-rw-r--r--src/nvim/ex_getln.c4
-rw-r--r--src/nvim/getchar.c1
-rw-r--r--src/nvim/globals.h2
-rw-r--r--src/nvim/insexpand.c1
-rw-r--r--src/nvim/lua/executor.c33
-rw-r--r--src/nvim/lua/executor.h2
-rw-r--r--src/nvim/mapping.c11
-rw-r--r--src/nvim/match.c2
-rw-r--r--src/nvim/menu.c269
-rw-r--r--src/nvim/message.c4
-rw-r--r--src/nvim/os/env.c2
-rw-r--r--src/nvim/quickfix.c2
-rw-r--r--src/nvim/sign.c2
-rw-r--r--src/nvim/terminal.c5
-rw-r--r--src/nvim/testdir/setup.vim4
-rw-r--r--src/nvim/testdir/test_alot.vim1
-rw-r--r--src/nvim/testdir/test_menu.vim420
-rw-r--r--src/nvim/testdir/test_normal.vim3
-rw-r--r--src/nvim/testdir/test_options.vim2
-rw-r--r--src/nvim/testdir/test_popup.vim22
-rw-r--r--src/nvim/testdir/test_tabpage.vim46
-rw-r--r--src/nvim/usercmd.c1742
-rw-r--r--src/nvim/usercmd.h30
-rw-r--r--src/nvim/window.c1
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;
}
}