aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rwxr-xr-xsrc/nvim/CMakeLists.txt18
-rw-r--r--src/nvim/README.md2
-rw-r--r--src/nvim/api/buffer.c34
-rw-r--r--src/nvim/api/keysets.lua39
-rw-r--r--src/nvim/api/private/helpers.c173
-rw-r--r--src/nvim/api/vimscript.c385
-rw-r--r--src/nvim/buffer.c4
-rw-r--r--src/nvim/edit.c2
-rw-r--r--src/nvim/eval/funcs.c23
-rw-r--r--src/nvim/eval/funcs.h6
-rw-r--r--src/nvim/ex_cmds_defs.h3
-rw-r--r--src/nvim/ex_docmd.c467
-rw-r--r--src/nvim/ex_getln.c5
-rw-r--r--src/nvim/ex_session.c2
-rw-r--r--src/nvim/generators/gen_eval.lua42
-rw-r--r--src/nvim/generators/hashy.lua24
-rw-r--r--src/nvim/getchar.c2
-rw-r--r--src/nvim/globals.h1
-rw-r--r--src/nvim/keycodes.c (renamed from src/nvim/keymap.c)46
-rw-r--r--src/nvim/keycodes.h (renamed from src/nvim/keymap.h)118
-rw-r--r--src/nvim/log.c14
-rw-r--r--src/nvim/lua/executor.c11
-rw-r--r--src/nvim/mbyte.c25
-rw-r--r--src/nvim/menu.c2
-rw-r--r--src/nvim/message.c2
-rw-r--r--src/nvim/normal.c2
-rw-r--r--src/nvim/option.c10
-rw-r--r--src/nvim/os/input.c2
-rw-r--r--src/nvim/os/stdpaths.c23
-rw-r--r--src/nvim/os/stdpaths_defs.h1
-rw-r--r--src/nvim/search.c2
-rw-r--r--src/nvim/shada.c2
-rw-r--r--src/nvim/spellfile.c11
-rw-r--r--src/nvim/syntax.c2
-rw-r--r--src/nvim/terminal.c6
-rw-r--r--src/nvim/testdir/test_buffer.vim11
-rw-r--r--src/nvim/testdir/test_matchfuzzy.vim3
-rw-r--r--src/nvim/testdir/test_spell.vim8
-rw-r--r--src/nvim/testdir/test_spell_utf8.vim6
-rw-r--r--src/nvim/testdir/test_undo.vim17
-rw-r--r--src/nvim/undo.c11
-rw-r--r--src/nvim/vim.h2
42 files changed, 1219 insertions, 350 deletions
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt
index e6f70033d0..2a323bdea0 100755
--- a/src/nvim/CMakeLists.txt
+++ b/src/nvim/CMakeLists.txt
@@ -185,15 +185,8 @@ if(NOT MSVC)
set_source_files_properties(
${EXTERNAL_SOURCES} PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion -Wno-missing-noreturn -Wno-missing-format-attribute -Wno-double-promotion")
- # gperf generates ANSI-C with incorrect linkage, ignore it.
- check_c_compiler_flag(-Wstatic-in-inline HAS_WSTATIC_IN_INLINE)
- if(HAS_WSTATIC_IN_INLINE)
- set_source_files_properties(
- eval/funcs.c PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-static-in-inline -Wno-conversion")
- else()
- set_source_files_properties(
- eval/funcs.c PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion")
- endif()
+ set_source_files_properties(
+ eval/funcs.c PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion")
endif()
if(NOT "${MIN_LOG_LEVEL}" MATCHES "^$")
@@ -408,14 +401,9 @@ add_custom_command(OUTPUT ${GENERATED_EX_CMDS_ENUM} ${GENERATED_EX_CMDS_DEFS}
DEPENDS ${EX_CMDS_GENERATOR} ${CMAKE_CURRENT_LIST_DIR}/ex_cmds.lua
)
-if(NOT GPERF_PRG)
- message(FATAL_ERROR "gperf was not found.")
-endif()
add_custom_command(OUTPUT ${GENERATED_FUNCS} ${FUNCS_DATA}
COMMAND ${LUA_PRG} ${FUNCS_GENERATOR}
- ${CMAKE_CURRENT_LIST_DIR} ${GENERATED_DIR} ${API_METADATA} ${FUNCS_DATA}
- COMMAND ${GPERF_PRG}
- ${GENERATED_DIR}/funcs.generated.h.gperf --output-file=${GENERATED_FUNCS}
+ ${CMAKE_CURRENT_LIST_DIR} ${LUA_SHARED_MODULE_SOURCE} ${GENERATED_DIR} ${API_METADATA} ${FUNCS_DATA}
DEPENDS ${FUNCS_GENERATOR} ${CMAKE_CURRENT_LIST_DIR}/eval.lua ${API_METADATA}
)
list(APPEND NVIM_GENERATED_FOR_SOURCES
diff --git a/src/nvim/README.md b/src/nvim/README.md
index c7cb233c73..9417629691 100644
--- a/src/nvim/README.md
+++ b/src/nvim/README.md
@@ -38,7 +38,7 @@ alternate file (e.g. stderr) use `LOG_CALLSTACK_TO_FILE(FILE*)`. Requires
Many log messages have a shared prefix, such as "UI" or "RPC". Use the shell to
filter the log, e.g. at DEBUG level you might want to exclude UI messages:
- tail -F ~/.cache/nvim/log | cat -v | stdbuf -o0 grep -v UI | stdbuf -o0 tee -a log
+ tail -F ~/.local/state/nvim/log | cat -v | stdbuf -o0 grep -v UI | stdbuf -o0 tee -a log
Build with ASAN
---------------
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 45dadae1dd..9842975d62 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -262,7 +262,7 @@ void nvim__buf_redraw_range(Buffer buffer, Integer first, Integer last, Error *e
/// @param channel_id
/// @param buffer Buffer handle, or 0 for current buffer
/// @param start First line index
-/// @param end Last line index (exclusive)
+/// @param end Last line index, exclusive
/// @param strict_indexing Whether out-of-bounds should be an error.
/// @param[out] err Error details, if any
/// @return Array of lines, or empty array for unloaded buffer.
@@ -358,7 +358,7 @@ static bool check_string_array(Array arr, bool disallow_nl, Error *err)
/// @param channel_id
/// @param buffer Buffer handle, or 0 for current buffer
/// @param start First line index
-/// @param end Last line index (exclusive)
+/// @param end Last line index, exclusive
/// @param strict_indexing Whether out-of-bounds should be an error.
/// @param replacement Array of lines to use as replacement
/// @param[out] err Error details, if any
@@ -514,24 +514,25 @@ end:
/// Sets (replaces) a range in the buffer
///
-/// This is recommended over nvim_buf_set_lines when only modifying parts of a
-/// line, as extmarks will be preserved on non-modified parts of the touched
+/// This is recommended over |nvim_buf_set_lines()| when only modifying parts of
+/// a line, as extmarks will be preserved on non-modified parts of the touched
/// lines.
///
-/// Indexing is zero-based and end-exclusive.
+/// Indexing is zero-based. Row indices are end-inclusive, and column indices
+/// are end-exclusive.
///
-/// To insert text at a given index, set `start` and `end` ranges to the same
-/// index. To delete a range, set `replacement` to an array containing
-/// an empty string, or simply an empty array.
+/// To insert text at a given `(row, column)` location, use `start_row = end_row
+/// = row` and `start_col = end_col = col`. To delete the text in a range, use
+/// `replacement = {}`.
///
-/// Prefer nvim_buf_set_lines when adding or deleting entire lines only.
+/// Prefer |nvim_buf_set_lines()| if you are only adding or deleting entire lines.
///
/// @param channel_id
/// @param buffer Buffer handle, or 0 for current buffer
/// @param start_row First line index
-/// @param start_col First column
-/// @param end_row Last line index
-/// @param end_col Last column
+/// @param start_col Starting column (byte offset) on first line
+/// @param end_row Last line index, inclusive
+/// @param end_col Ending column (byte offset) on last line, exclusive
/// @param replacement Array of lines to use as replacement
/// @param[out] err Error details, if any
void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, Integer start_col,
@@ -760,16 +761,17 @@ end:
/// This differs from |nvim_buf_get_lines()| in that it allows retrieving only
/// portions of a line.
///
-/// Indexing is zero-based. Column indices are end-exclusive.
+/// Indexing is zero-based. Row indices are end-inclusive, and column indices
+/// are end-exclusive.
///
/// Prefer |nvim_buf_get_lines()| when retrieving entire lines.
///
/// @param channel_id
/// @param buffer Buffer handle, or 0 for current buffer
/// @param start_row First line index
-/// @param start_col Starting byte offset of first line
-/// @param end_row Last line index
-/// @param end_col Ending byte offset of last line (exclusive)
+/// @param start_col Starting column (byte offset) on first line
+/// @param end_row Last line index, inclusive
+/// @param end_col Ending column (byte offset) on last line, exclusive
/// @param opts Optional parameters. Currently unused.
/// @param[out] err Error details, if any
/// @return Array of lines, or empty array for unloaded buffer.
diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua
index 5baffaf505..78f8ed3cca 100644
--- a/src/nvim/api/keysets.lua
+++ b/src/nvim/api/keysets.lua
@@ -155,5 +155,44 @@ return {
create_augroup = {
"clear";
};
+ cmd = {
+ "cmd";
+ "range";
+ "count";
+ "reg";
+ "bang";
+ "args";
+ "magic";
+ "mods";
+ "nargs";
+ "addr";
+ "nextcmd";
+ };
+ cmd_magic = {
+ "file";
+ "bar";
+ };
+ cmd_mods = {
+ "silent";
+ "emsg_silent";
+ "sandbox";
+ "noautocmd";
+ "browse";
+ "confirm";
+ "hide";
+ "keepalt";
+ "keepjumps";
+ "keepmarks";
+ "keeppatterns";
+ "lockmarks";
+ "noswapfile";
+ "tab";
+ "verbose";
+ "vertical";
+ "split";
+ };
+ cmd_opts = {
+ "output";
+ };
}
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index eb86964a72..542e5c4953 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -19,6 +19,7 @@
#include "nvim/decoration.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
+#include "nvim/ex_cmds_defs.h"
#include "nvim/extmark.h"
#include "nvim/fileio.h"
#include "nvim/getchar.h"
@@ -1690,3 +1691,175 @@ int init_sign_text(char **sign_text, char *text)
return OK;
}
+
+/// Check if a string contains only whitespace characters.
+bool string_iswhite(String str)
+{
+ for (size_t i = 0; i < str.size; i++) {
+ if (!ascii_iswhite(str.data[i])) {
+ // Found a non-whitespace character
+ return false;
+ } else if (str.data[i] == NUL) {
+ // Terminate at first occurence of a NUL character
+ break;
+ }
+ }
+ return true;
+}
+
+// Add modifier string for command into the command line. Includes trailing whitespace if non-empty.
+// @return OK or FAIL.
+static int add_cmd_modifier_str(char *cmdline, size_t *pos, const size_t bufsize,
+ CmdParseInfo *cmdinfo)
+{
+#define APPEND_MODIFIER(...) \
+ do { \
+ if (*pos < bufsize) { \
+ *pos += (size_t)snprintf(cmdline + *pos, bufsize - *pos, __VA_ARGS__); \
+ } \
+ if (*pos < bufsize) { \
+ cmdline[*pos] = ' '; \
+ *pos += 1; \
+ } else { \
+ goto err; \
+ } \
+ } while (0)
+
+#define APPEND_MODIFIER_IF(cond, mod) \
+ do { \
+ if (cond) { \
+ APPEND_MODIFIER(mod); \
+ } \
+ } while (0)
+
+ if (cmdinfo->cmdmod.tab != 0) {
+ APPEND_MODIFIER("%dtab", cmdinfo->cmdmod.tab - 1);
+ }
+ if (cmdinfo->verbose != -1) {
+ APPEND_MODIFIER("%ldverbose", cmdinfo->verbose);
+ }
+
+ switch (cmdinfo->cmdmod.split & (WSP_ABOVE | WSP_BELOW | WSP_TOP | WSP_BOT)) {
+ case WSP_ABOVE:
+ APPEND_MODIFIER("aboveleft");
+ break;
+ case WSP_BELOW:
+ APPEND_MODIFIER("belowright");
+ break;
+ case WSP_TOP:
+ APPEND_MODIFIER("topleft");
+ break;
+ case WSP_BOT:
+ APPEND_MODIFIER("botright");
+ break;
+ default:
+ break;
+ }
+
+ APPEND_MODIFIER_IF(cmdinfo->cmdmod.split & WSP_VERT, "vertical");
+
+ if (cmdinfo->emsg_silent) {
+ APPEND_MODIFIER("silent!");
+ } else if (cmdinfo->silent) {
+ APPEND_MODIFIER("silent");
+ }
+
+ APPEND_MODIFIER_IF(cmdinfo->sandbox, "sandbox");
+ APPEND_MODIFIER_IF(cmdinfo->noautocmd, "noautocmd");
+ APPEND_MODIFIER_IF(cmdinfo->cmdmod.browse, "browse");
+ APPEND_MODIFIER_IF(cmdinfo->cmdmod.confirm, "confirm");
+ APPEND_MODIFIER_IF(cmdinfo->cmdmod.hide, "hide");
+ APPEND_MODIFIER_IF(cmdinfo->cmdmod.keepalt, "keepalt");
+ APPEND_MODIFIER_IF(cmdinfo->cmdmod.keepjumps, "keepjumps");
+ APPEND_MODIFIER_IF(cmdinfo->cmdmod.keepmarks, "keepmarks");
+ APPEND_MODIFIER_IF(cmdinfo->cmdmod.keeppatterns, "keeppatterns");
+ APPEND_MODIFIER_IF(cmdinfo->cmdmod.lockmarks, "lockmarks");
+ APPEND_MODIFIER_IF(cmdinfo->cmdmod.noswapfile, "noswapfile");
+
+ return OK;
+err:
+ return FAIL;
+
+#undef APPEND_MODIFIER
+#undef APPEND_MODIFIER_IF
+}
+
+/// Build cmdline string for command, used by `nvim_cmd()`.
+///
+/// @return OK or FAIL.
+int build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdinfo, char **args,
+ size_t argc)
+{
+ const size_t bufsize = IOSIZE;
+ size_t pos = 0;
+ char *cmdline = xcalloc(bufsize, sizeof(char));
+
+#define CMDLINE_APPEND(...) \
+ do { \
+ if (pos < bufsize) { \
+ pos += (size_t)snprintf(cmdline + pos, bufsize - pos, __VA_ARGS__); \
+ } else { \
+ goto err; \
+ } \
+ } while (0)
+
+ // Command modifiers.
+ if (add_cmd_modifier_str(cmdline, &pos, bufsize, cmdinfo) == FAIL) {
+ goto err;
+ }
+
+ // Command range / count.
+ if (eap->argt & EX_RANGE) {
+ if (eap->addr_count == 1) {
+ CMDLINE_APPEND("%ld", eap->line2);
+ } else if (eap->addr_count > 1) {
+ CMDLINE_APPEND("%ld,%ld", eap->line1, eap->line2);
+ eap->addr_count = 2; // Make sure address count is not greater than 2
+ }
+ }
+
+ // Keep the index of the position where command name starts, so eap->cmd can point to it.
+ size_t cmdname_idx = pos;
+ CMDLINE_APPEND("%s", eap->cmd);
+ eap->cmd = cmdline + cmdname_idx;
+
+ // Command bang.
+ if (eap->argt & EX_BANG && eap->forceit) {
+ CMDLINE_APPEND("!");
+ }
+
+ // Command register.
+ if (eap->argt & EX_REGSTR && eap->regname) {
+ CMDLINE_APPEND(" %c", eap->regname);
+ }
+
+ // Iterate through each argument and store the starting position and length of each argument in
+ // the cmdline string in `eap->args` and `eap->arglens`, respectively.
+ eap->args = xcalloc(argc, sizeof(char *));
+ eap->arglens = xcalloc(argc, sizeof(size_t));
+ for (size_t i = 0; i < argc; i++) {
+ eap->args[i] = cmdline + pos + 1; // add 1 to skip the leading space.
+ eap->arglens[i] = STRLEN(args[i]);
+ CMDLINE_APPEND(" %s", args[i]);
+ }
+ eap->argc = argc;
+ // If there isn't an argument, make eap->arg point to end of cmd
+ eap->arg = argc > 0 ? eap->args[0] : cmdline + pos;
+
+ // Replace, :make and :grep with 'makeprg' and 'grepprg'.
+ char *p = replace_makeprg(eap, eap->arg, cmdlinep);
+ if (p != eap->arg) {
+ // If replace_makeprg modified the cmdline string, correct the argument pointers.
+ assert(argc == 1);
+ eap->arg = p;
+ eap->args[0] = p;
+ }
+
+ *cmdlinep = cmdline;
+ return OK;
+err:
+ xfree(cmdline);
+ return FAIL;
+
+#undef CMDLINE_APPEND
+}
diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c
index c4a301839f..02f406d686 100644
--- a/src/nvim/api/vimscript.c
+++ b/src/nvim/api/vimscript.c
@@ -10,10 +10,14 @@
#include "nvim/api/private/helpers.h"
#include "nvim/api/vimscript.h"
#include "nvim/ascii.h"
+#include "nvim/autocmd.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/userfunc.h"
#include "nvim/ex_cmds2.h"
+#include "nvim/ops.h"
+#include "nvim/strings.h"
+#include "nvim/vim.h"
#include "nvim/viml/parser/expressions.h"
#include "nvim/viml/parser/parser.h"
#include "nvim/window.h"
@@ -22,7 +26,9 @@
# include "api/vimscript.c.generated.h"
#endif
-/// Executes Vimscript (multiline block of Ex-commands), like anonymous
+#define IS_USER_CMDIDX(idx) ((int)(idx) < 0)
+
+/// Executes Vimscript (multiline block of Ex commands), like anonymous
/// |:source|.
///
/// Unlike |nvim_command()| this function supports heredocs, script-scope (s:),
@@ -32,6 +38,7 @@
///
/// @see |execute()|
/// @see |nvim_command()|
+/// @see |nvim_cmd()|
///
/// @param src Vimscript code
/// @param output Capture and return all (non-error, non-shell |:!|) output
@@ -89,13 +96,16 @@ theend:
return (String)STRING_INIT;
}
-/// Executes an ex-command.
+/// Executes an Ex command.
///
/// On execution error: fails with VimL error, does not update v:errmsg.
///
-/// @see |nvim_exec()|
+/// Prefer using |nvim_cmd()| or |nvim_exec()| over this. To evaluate multiple lines of Vim script
+/// or an Ex command directly, use |nvim_exec()|. To construct an Ex command using a structured
+/// format and then execute it, use |nvim_cmd()|. To modify an Ex command before evaluating it, use
+/// |nvim_parse_cmd()| in conjunction with |nvim_cmd()|.
///
-/// @param command Ex-command string
+/// @param command Ex command string
/// @param[out] err Error details (Vim error), if any
void nvim_command(String command, Error *err)
FUNC_API_SINCE(1)
@@ -978,3 +988,370 @@ end:
xfree(cmdline);
return result;
}
+
+/// Executes an Ex command.
+///
+/// Unlike |nvim_command()| this command takes a structured Dictionary instead of a String. This
+/// allows for easier construction and manipulation of an Ex command. This also allows for things
+/// such as having spaces inside a command argument, expanding filenames in a command that otherwise
+/// doesn't expand filenames, etc.
+///
+/// @see |nvim_exec()|
+/// @see |nvim_command()|
+///
+/// @param cmd Command to execute. Must be a Dictionary that can contain the same values as
+/// the return value of |nvim_parse_cmd()| except "addr", "nargs" and "nextcmd"
+/// which are ignored if provided. All values except for "cmd" are optional.
+/// @param opts Optional parameters.
+/// - output: (boolean, default false) Whether to return command output.
+/// @param[out] err Error details, if any.
+/// @return Command output (non-error, non-shell |:!|) if `output` is true, else empty string.
+String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error *err)
+ FUNC_API_SINCE(10)
+{
+ exarg_T ea;
+ memset(&ea, 0, sizeof(ea));
+ ea.verbose_save = -1;
+ ea.save_msg_silent = -1;
+
+ CmdParseInfo cmdinfo;
+ memset(&cmdinfo, 0, sizeof(cmdinfo));
+ cmdinfo.verbose = -1;
+
+ char *cmdline = NULL;
+ char *cmdname = NULL;
+ char **args = NULL;
+ size_t argc = 0;
+
+ String retv = (String)STRING_INIT;
+
+#define OBJ_TO_BOOL(var, value, default, varname) \
+ do { \
+ var = api_object_to_bool(value, varname, default, err); \
+ if (ERROR_SET(err)) { \
+ goto end; \
+ } \
+ } while (0)
+
+#define VALIDATION_ERROR(...) \
+ do { \
+ api_set_error(err, kErrorTypeValidation, __VA_ARGS__); \
+ goto end; \
+ } while (0)
+
+ bool output;
+ OBJ_TO_BOOL(output, opts->output, false, "'output'");
+
+ // First, parse the command name and check if it exists and is valid.
+ if (!HAS_KEY(cmd->cmd) || cmd->cmd.type != kObjectTypeString
+ || cmd->cmd.data.string.data[0] == NUL) {
+ VALIDATION_ERROR("'cmd' must be a non-empty String");
+ }
+
+ cmdname = string_to_cstr(cmd->cmd.data.string);
+ ea.cmd = cmdname;
+
+ char *p = find_ex_command(&ea, NULL);
+
+ // If this looks like an undefined user command and there are CmdUndefined
+ // autocommands defined, trigger the matching autocommands.
+ if (p != NULL && ea.cmdidx == CMD_SIZE && ASCII_ISUPPER(*ea.cmd)
+ && has_event(EVENT_CMDUNDEFINED)) {
+ p = xstrdup(cmdname);
+ int ret = apply_autocmds(EVENT_CMDUNDEFINED, (char_u *)p, (char_u *)p, true, NULL);
+ xfree(p);
+ // If the autocommands did something and didn't cause an error, try
+ // finding the command again.
+ p = (ret && !aborting()) ? find_ex_command(&ea, NULL) : ea.cmd;
+ }
+
+ if (p == NULL || ea.cmdidx == CMD_SIZE) {
+ VALIDATION_ERROR("Command not found: %s", cmdname);
+ }
+ if (is_cmd_ni(ea.cmdidx)) {
+ VALIDATION_ERROR("Command not implemented: %s", cmdname);
+ }
+
+ // Get the command flags so that we can know what type of arguments the command uses.
+ // Not required for a user command since `find_ex_command` already deals with it in that case.
+ if (!IS_USER_CMDIDX(ea.cmdidx)) {
+ ea.argt = get_cmd_argt(ea.cmdidx);
+ }
+
+ // Parse command arguments since it's needed to get the command address type.
+ if (HAS_KEY(cmd->args)) {
+ if (cmd->args.type != kObjectTypeArray) {
+ VALIDATION_ERROR("'args' must be an Array");
+ }
+ // Check if every argument is valid
+ for (size_t i = 0; i < cmd->args.data.array.size; i++) {
+ Object elem = cmd->args.data.array.items[i];
+ if (elem.type != kObjectTypeString) {
+ VALIDATION_ERROR("Command argument must be a String");
+ } else if (string_iswhite(elem.data.string)) {
+ VALIDATION_ERROR("Command argument must have non-whitespace characters");
+ }
+ }
+
+ argc = cmd->args.data.array.size;
+ bool argc_valid;
+
+ // Check if correct number of arguments is used.
+ switch (ea.argt & (EX_EXTRA | EX_NOSPC | EX_NEEDARG)) {
+ case EX_EXTRA | EX_NOSPC | EX_NEEDARG:
+ argc_valid = argc == 1;
+ break;
+ case EX_EXTRA | EX_NOSPC:
+ argc_valid = argc <= 1;
+ break;
+ case EX_EXTRA | EX_NEEDARG:
+ argc_valid = argc >= 1;
+ break;
+ case EX_EXTRA:
+ argc_valid = true;
+ break;
+ default:
+ argc_valid = argc == 0;
+ break;
+ }
+
+ if (!argc_valid) {
+ argc = 0; // Ensure that args array isn't erroneously freed at the end.
+ VALIDATION_ERROR("Incorrect number of arguments supplied");
+ }
+
+ if (argc != 0) {
+ args = xcalloc(argc, sizeof(char *));
+
+ for (size_t i = 0; i < argc; i++) {
+ args[i] = string_to_cstr(cmd->args.data.array.items[i].data.string);
+ }
+ }
+ }
+
+ // Simply pass the first argument (if it exists) as the arg pointer to `set_cmd_addr_type()`
+ // since it only ever checks the first argument.
+ set_cmd_addr_type(&ea, argc > 0 ? (char_u *)args[0] : NULL);
+
+ if (HAS_KEY(cmd->range)) {
+ if (!(ea.argt & EX_RANGE)) {
+ VALIDATION_ERROR("Command cannot accept a range");
+ } else if (cmd->range.type != kObjectTypeArray) {
+ VALIDATION_ERROR("'range' must be an Array");
+ } else if (cmd->range.data.array.size > 2) {
+ VALIDATION_ERROR("'range' cannot contain more than two elements");
+ }
+
+ Array range = cmd->range.data.array;
+ ea.addr_count = (int)range.size;
+
+ for (size_t i = 0; i < range.size; i++) {
+ Object elem = range.items[i];
+ if (elem.type != kObjectTypeInteger || elem.data.integer < 0) {
+ VALIDATION_ERROR("'range' element must be a non-negative Integer");
+ }
+ }
+
+ if (range.size > 0) {
+ ea.line1 = range.items[0].data.integer;
+ ea.line2 = range.items[range.size - 1].data.integer;
+ }
+
+ if (invalid_range(&ea) != NULL) {
+ VALIDATION_ERROR("Invalid range provided");
+ }
+ }
+ if (ea.addr_count == 0) {
+ if (ea.argt & EX_DFLALL) {
+ set_cmd_dflall_range(&ea); // Default range for range=%
+ } else {
+ ea.line1 = ea.line2 = get_cmd_default_range(&ea); // Default range.
+
+ if (ea.addr_type == ADDR_OTHER) {
+ // Default is 1, not cursor.
+ ea.line2 = 1;
+ }
+ }
+ }
+
+ if (HAS_KEY(cmd->count)) {
+ if (!(ea.argt & EX_COUNT)) {
+ VALIDATION_ERROR("Command cannot accept a count");
+ } else if (cmd->count.type != kObjectTypeInteger || cmd->count.data.integer < 0) {
+ VALIDATION_ERROR("'count' must be a non-negative Integer");
+ }
+ set_cmd_count(&ea, cmd->count.data.integer, true);
+ }
+
+ if (HAS_KEY(cmd->reg)) {
+ if (!(ea.argt & EX_REGSTR)) {
+ VALIDATION_ERROR("Command cannot accept a register");
+ } else if (cmd->reg.type != kObjectTypeString || cmd->reg.data.string.size != 1) {
+ VALIDATION_ERROR("'reg' must be a single character");
+ }
+ char regname = cmd->reg.data.string.data[0];
+ if (regname == '=') {
+ VALIDATION_ERROR("Cannot use register \"=");
+ } else if (!valid_yank_reg(regname, ea.cmdidx != CMD_put && !IS_USER_CMDIDX(ea.cmdidx))) {
+ VALIDATION_ERROR("Invalid register: \"%c", regname);
+ }
+ ea.regname = (uint8_t)regname;
+ }
+
+ OBJ_TO_BOOL(ea.forceit, cmd->bang, false, "'bang'");
+ if (ea.forceit && !(ea.argt & EX_BANG)) {
+ VALIDATION_ERROR("Command cannot accept a bang");
+ }
+
+ if (HAS_KEY(cmd->magic)) {
+ if (cmd->magic.type != kObjectTypeDictionary) {
+ VALIDATION_ERROR("'magic' must be a Dictionary");
+ }
+
+ Dict(cmd_magic) magic = { 0 };
+ if (!api_dict_to_keydict(&magic, KeyDict_cmd_magic_get_field,
+ cmd->magic.data.dictionary, err)) {
+ goto end;
+ }
+
+ 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'");
+ } else {
+ cmdinfo.magic.file = ea.argt & EX_XFILE;
+ cmdinfo.magic.bar = ea.argt & EX_TRLBAR;
+ }
+
+ if (HAS_KEY(cmd->mods)) {
+ if (cmd->mods.type != kObjectTypeDictionary) {
+ VALIDATION_ERROR("'mods' must be a Dictionary");
+ }
+
+ Dict(cmd_mods) mods = { 0 };
+ if (!api_dict_to_keydict(&mods, KeyDict_cmd_mods_get_field, cmd->mods.data.dictionary, err)) {
+ goto end;
+ }
+
+ if (HAS_KEY(mods.tab)) {
+ if (mods.tab.type != kObjectTypeInteger || mods.tab.data.integer < 0) {
+ VALIDATION_ERROR("'mods.tab' must be a non-negative Integer");
+ }
+ cmdinfo.cmdmod.tab = (int)mods.tab.data.integer + 1;
+ }
+
+ if (HAS_KEY(mods.verbose)) {
+ if (mods.verbose.type != kObjectTypeInteger || mods.verbose.data.integer <= 0) {
+ VALIDATION_ERROR("'mods.verbose' must be a non-negative Integer");
+ }
+ cmdinfo.verbose = mods.verbose.data.integer;
+ }
+
+ bool vertical;
+ OBJ_TO_BOOL(vertical, mods.vertical, false, "'mods.vertical'");
+ cmdinfo.cmdmod.split |= (vertical ? WSP_VERT : 0);
+
+ if (HAS_KEY(mods.split)) {
+ if (mods.split.type != kObjectTypeString) {
+ VALIDATION_ERROR("'mods.split' must be a String");
+ }
+
+ if (STRCMP(mods.split.data.string.data, "aboveleft") == 0
+ || STRCMP(mods.split.data.string.data, "leftabove") == 0) {
+ cmdinfo.cmdmod.split |= WSP_ABOVE;
+ } else if (STRCMP(mods.split.data.string.data, "belowright") == 0
+ || STRCMP(mods.split.data.string.data, "rightbelow") == 0) {
+ cmdinfo.cmdmod.split |= WSP_BELOW;
+ } else if (STRCMP(mods.split.data.string.data, "topleft") == 0) {
+ cmdinfo.cmdmod.split |= WSP_TOP;
+ } else if (STRCMP(mods.split.data.string.data, "botright") == 0) {
+ cmdinfo.cmdmod.split |= WSP_BOT;
+ } else {
+ VALIDATION_ERROR("Invalid value for 'mods.split'");
+ }
+ }
+
+ OBJ_TO_BOOL(cmdinfo.silent, mods.silent, false, "'mods.silent'");
+ OBJ_TO_BOOL(cmdinfo.emsg_silent, mods.emsg_silent, false, "'mods.emsg_silent'");
+ OBJ_TO_BOOL(cmdinfo.sandbox, mods.sandbox, false, "'mods.sandbox'");
+ OBJ_TO_BOOL(cmdinfo.noautocmd, mods.noautocmd, false, "'mods.noautocmd'");
+ OBJ_TO_BOOL(cmdinfo.cmdmod.browse, mods.browse, false, "'mods.browse'");
+ OBJ_TO_BOOL(cmdinfo.cmdmod.confirm, mods.confirm, false, "'mods.confirm'");
+ OBJ_TO_BOOL(cmdinfo.cmdmod.hide, mods.hide, false, "'mods.hide'");
+ OBJ_TO_BOOL(cmdinfo.cmdmod.keepalt, mods.keepalt, false, "'mods.keepalt'");
+ OBJ_TO_BOOL(cmdinfo.cmdmod.keepjumps, mods.keepjumps, false, "'mods.keepjumps'");
+ OBJ_TO_BOOL(cmdinfo.cmdmod.keepmarks, mods.keepmarks, false, "'mods.keepmarks'");
+ OBJ_TO_BOOL(cmdinfo.cmdmod.keeppatterns, mods.keeppatterns, false, "'mods.keeppatterns'");
+ OBJ_TO_BOOL(cmdinfo.cmdmod.lockmarks, mods.lockmarks, false, "'mods.lockmarks'");
+ OBJ_TO_BOOL(cmdinfo.cmdmod.noswapfile, mods.noswapfile, false, "'mods.noswapfile'");
+
+ if (cmdinfo.sandbox && !(ea.argt & EX_SBOXOK)) {
+ VALIDATION_ERROR("Command cannot be run in sandbox");
+ }
+ }
+
+ // Finally, build the command line string that will be stored inside ea.cmdlinep.
+ // This also sets the values of ea.cmd, ea.arg, ea.args and ea.arglens.
+ if (build_cmdline_str(&cmdline, &ea, &cmdinfo, args, argc) == FAIL) {
+ goto end;
+ }
+ ea.cmdlinep = &cmdline;
+
+ garray_T capture_local;
+ const int save_msg_silent = msg_silent;
+ garray_T * const save_capture_ga = capture_ga;
+
+ if (output) {
+ ga_init(&capture_local, 1, 80);
+ capture_ga = &capture_local;
+ }
+
+ try_start();
+ if (output) {
+ msg_silent++;
+ }
+
+ WITH_SCRIPT_CONTEXT(channel_id, {
+ execute_cmd(&ea, &cmdinfo);
+ });
+
+ if (output) {
+ capture_ga = save_capture_ga;
+ msg_silent = save_msg_silent;
+ }
+ try_end(err);
+
+ if (ERROR_SET(err)) {
+ goto clear_ga;
+ }
+
+ if (output && capture_local.ga_len > 1) {
+ retv = (String){
+ .data = capture_local.ga_data,
+ .size = (size_t)capture_local.ga_len,
+ };
+ // redir usually (except :echon) prepends a newline.
+ if (retv.data[0] == '\n') {
+ memmove(retv.data, retv.data + 1, retv.size - 1);
+ retv.data[retv.size - 1] = '\0';
+ retv.size = retv.size - 1;
+ }
+ goto end;
+ }
+clear_ga:
+ if (output) {
+ ga_clear(&capture_local);
+ }
+end:
+ xfree(cmdline);
+ xfree(cmdname);
+ xfree(ea.args);
+ xfree(ea.arglens);
+ for (size_t i = 0; i < argc; i++) {
+ xfree(args[i]);
+ }
+ xfree(args);
+
+ return retv;
+
+#undef OBJ_TO_BOOL
+#undef VALIDATION_ERROR
+}
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index 30e5edb4d0..18c8a2c250 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -2366,7 +2366,7 @@ static char_u *buflist_match(regmatch_T *rmp, buf_T *buf, bool ignore_case)
{
// First try the short file name, then the long file name.
char_u *match = fname_match(rmp, buf->b_sfname, ignore_case);
- if (match == NULL) {
+ if (match == NULL && rmp->regprog != NULL) {
match = fname_match(rmp, buf->b_ffname, ignore_case);
}
return match;
@@ -2387,7 +2387,7 @@ static char_u *fname_match(regmatch_T *rmp, char_u *name, bool ignore_case)
rmp->rm_ic = p_fic || ignore_case;
if (vim_regexec(rmp, name, (colnr_T)0)) {
match = name;
- } else {
+ } else if (rmp->regprog != NULL) {
// Replace $(HOME) with '~' and try matching again.
p = home_replace_save(NULL, name);
if (vim_regexec(rmp, p, (colnr_T)0)) {
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index 90a6f81cc0..c2e61271c7 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -29,7 +29,7 @@
#include "nvim/highlight_group.h"
#include "nvim/indent.h"
#include "nvim/indent_c.h"
-#include "nvim/keymap.h"
+#include "nvim/keycodes.h"
#include "nvim/main.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index cf5f86b3f7..ee00736b63 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -80,9 +80,6 @@ typedef enum {
kSomeMatchStrPos, ///< Data for matchstrpos().
} SomeMatchType;
-KHASH_MAP_INIT_STR(functions, VimLFuncDef)
-
-
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval/funcs.c.generated.h"
@@ -134,14 +131,11 @@ char *get_function_name(expand_T *xp, int idx)
return (char *)name;
}
}
- while ((size_t)++intidx < ARRAY_SIZE(functions)
- && functions[intidx].name[0] == '\0') {}
- if ((size_t)intidx >= ARRAY_SIZE(functions)) {
+ const char *const key = functions[++intidx].name;
+ if (!key) {
return NULL;
}
-
- const char *const key = functions[intidx].name;
const size_t key_len = strlen(key);
memcpy(IObuff, key, key_len);
IObuff[key_len] = '(';
@@ -178,18 +172,19 @@ char *get_expr_name(expand_T *xp, int idx)
/// @param[in] name Name of the function.
///
/// @return pointer to the function definition or NULL if not found.
-const VimLFuncDef *find_internal_func(const char *const name)
+const EvalFuncDef *find_internal_func(const char *const name)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE FUNC_ATTR_NONNULL_ALL
{
size_t len = strlen(name);
- return find_internal_func_gperf(name, len);
+ int index = find_internal_func_hash(name, len);
+ return index >= 0 ? &functions[index] : NULL;
}
int call_internal_func(const char_u *const fname, const int argcount, typval_T *const argvars,
typval_T *const rettv)
FUNC_ATTR_NONNULL_ALL
{
- const VimLFuncDef *const fdef = find_internal_func((const char *)fname);
+ const EvalFuncDef *const fdef = find_internal_func((const char *)fname);
if (fdef == NULL) {
return ERROR_UNKNOWN;
} else if (argcount < fdef->min_argc) {
@@ -207,7 +202,7 @@ int call_internal_method(const char_u *const fname, const int argcount, typval_T
typval_T *const rettv, typval_T *const basetv)
FUNC_ATTR_NONNULL_ALL
{
- const VimLFuncDef *const fdef = find_internal_func((const char *)fname);
+ const EvalFuncDef *const fdef = find_internal_func((const char *)fname);
if (fdef == NULL) {
return ERROR_UNKNOWN;
} else if (fdef->base_arg == BASE_NONE) {
@@ -9842,6 +9837,10 @@ static void f_stdpath(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->vval.v_string = get_xdg_home(kXDGDataHome);
} else if (strequal(p, "cache")) {
rettv->vval.v_string = get_xdg_home(kXDGCacheHome);
+ } else if (strequal(p, "state")) {
+ rettv->vval.v_string = get_xdg_home(kXDGStateHome);
+ } else if (strequal(p, "log")) {
+ rettv->vval.v_string = get_xdg_home(kXDGStateHome);
} else if (strequal(p, "config_dirs")) {
get_xdg_var_list(kXDGConfigDirs, rettv);
} else if (strequal(p, "data_dirs")) {
diff --git a/src/nvim/eval/funcs.h b/src/nvim/eval/funcs.h
index c6a0cb959e..4ab4c8f800 100644
--- a/src/nvim/eval/funcs.h
+++ b/src/nvim/eval/funcs.h
@@ -9,19 +9,19 @@ typedef void (*FunPtr)(void);
/// Prototype of C function that implements VimL function
typedef void (*VimLFunc)(typval_T *args, typval_T *rvar, FunPtr data);
-/// Special flags for base_arg @see VimLFuncDef
+/// Special flags for base_arg @see EvalFuncDef
#define BASE_NONE 0 ///< Not a method (no base argument).
#define BASE_LAST UINT8_MAX ///< Use the last argument as the method base.
/// Structure holding VimL function definition
-typedef struct fst {
+typedef struct {
char *name; ///< Name of the function.
uint8_t min_argc; ///< Minimal number of arguments.
uint8_t max_argc; ///< Maximal number of arguments.
uint8_t base_arg; ///< Method base arg # (1-indexed), BASE_NONE or BASE_LAST.
VimLFunc func; ///< Function implementation.
FunPtr data; ///< Userdata for function implementation.
-} VimLFuncDef;
+} EvalFuncDef;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval/funcs.h.generated.h"
diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h
index a51f805984..d8dd3da9e6 100644
--- a/src/nvim/ex_cmds_defs.h
+++ b/src/nvim/ex_cmds_defs.h
@@ -175,6 +175,9 @@ enum {
/// Arguments used for Ex commands.
struct exarg {
char *arg; ///< argument of the command
+ char **args; ///< starting position of command arguments
+ size_t *arglens; ///< length of command arguments
+ size_t argc; ///< number of command arguments
char *nextcmd; ///< next command (NULL if none)
char *cmd; ///< the name of the command (except for :make)
char **cmdlinep; ///< pointer to pointer of allocated cmdline
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index fc66677537..2173494be9 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -41,7 +41,7 @@
#include "nvim/highlight_group.h"
#include "nvim/if_cscope.h"
#include "nvim/input.h"
-#include "nvim/keymap.h"
+#include "nvim/keycodes.h"
#include "nvim/lua/executor.h"
#include "nvim/main.h"
#include "nvim/mark.h"
@@ -1218,7 +1218,7 @@ static char *skip_colon_white(const char *p, bool skipleadingwhite)
/// Set the addr type for command
///
/// @param p pointer to character after command name in cmdline
-static void set_cmd_addr_type(exarg_T *eap, char_u *p)
+void set_cmd_addr_type(exarg_T *eap, char_u *p)
{
// ea.addr_type for user commands is set by find_ucmd
if (IS_USER_CMDIDX(eap->cmdidx)) {
@@ -1239,8 +1239,48 @@ static void set_cmd_addr_type(exarg_T *eap, char_u *p)
}
}
+/// Get default range number for command based on its address type
+linenr_T get_cmd_default_range(exarg_T *eap)
+{
+ switch (eap->addr_type) {
+ case ADDR_LINES:
+ case ADDR_OTHER:
+ // Default is the cursor line number. Avoid using an invalid
+ // line number though.
+ return MIN(curwin->w_cursor.lnum, curbuf->b_ml.ml_line_count);
+ break;
+ case ADDR_WINDOWS:
+ return CURRENT_WIN_NR;
+ break;
+ case ADDR_ARGUMENTS:
+ return MIN(curwin->w_arg_idx + 1, ARGCOUNT);
+ break;
+ case ADDR_LOADED_BUFFERS:
+ case ADDR_BUFFERS:
+ return curbuf->b_fnum;
+ break;
+ case ADDR_TABS:
+ return CURRENT_TAB_NR;
+ break;
+ case ADDR_TABS_RELATIVE:
+ case ADDR_UNSIGNED:
+ return 1;
+ break;
+ case ADDR_QUICKFIX:
+ return (linenr_T)qf_get_cur_idx(eap);
+ break;
+ case ADDR_QUICKFIX_VALID:
+ return qf_get_cur_valid_idx(eap);
+ break;
+ default:
+ return 0;
+ // Will give an error later if a range is found.
+ break;
+ }
+}
+
/// Set default command range for -range=% based on the addr type of the command
-static void set_cmd_default_range(exarg_T *eap)
+void set_cmd_dflall_range(exarg_T *eap)
{
buf_T *buf;
@@ -1320,6 +1360,25 @@ static void parse_register(exarg_T *eap)
}
}
+// Change line1 and line2 of Ex command to use count
+void set_cmd_count(exarg_T *eap, long count, bool validate)
+{
+ if (eap->addr_type != ADDR_LINES) { // e.g. :buffer 2, :sleep 3
+ eap->line2 = count;
+ if (eap->addr_count == 0) {
+ eap->addr_count = 1;
+ }
+ } else {
+ eap->line1 = eap->line2;
+ eap->line2 += count - 1;
+ eap->addr_count++;
+ // Be vi compatible: no error message for out of range.
+ if (validate && eap->line2 > curbuf->b_ml.ml_line_count) {
+ eap->line2 = curbuf->b_ml.ml_line_count;
+ }
+ }
+}
+
static int parse_count(exarg_T *eap, char **errormsg, bool validate)
{
// Check for a count. When accepting a EX_BUFNAME, don't use "123foo" as a
@@ -1338,25 +1397,19 @@ static int parse_count(exarg_T *eap, char **errormsg, bool validate)
}
return FAIL;
}
- if (eap->addr_type != ADDR_LINES) { // e.g. :buffer 2, :sleep 3
- eap->line2 = n;
- if (eap->addr_count == 0) {
- eap->addr_count = 1;
- }
- } else {
- eap->line1 = eap->line2;
- eap->line2 += n - 1;
- eap->addr_count++;
- // Be vi compatible: no error message for out of range.
- if (validate && eap->line2 > curbuf->b_ml.ml_line_count) {
- eap->line2 = curbuf->b_ml.ml_line_count;
- }
- }
+ set_cmd_count(eap, n, validate);
}
return OK;
}
+/// Check if command is not implemented
+bool is_cmd_ni(cmdidx_T cmdidx)
+{
+ return !IS_USER_CMDIDX(cmdidx) && (cmdnames[cmdidx].cmd_func == ex_ni
+ || cmdnames[cmdidx].cmd_func == ex_script_ni);
+}
+
/// Parse command line and return information about the first command.
///
/// @param cmdline Command line string
@@ -1394,14 +1447,17 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er
if (eap->save_msg_silent != -1) {
cmdinfo->silent = !!msg_silent;
msg_silent = eap->save_msg_silent;
+ eap->save_msg_silent = -1;
}
if (eap->did_esilent) {
cmdinfo->emsg_silent = true;
emsg_silent--;
+ eap->did_esilent = false;
}
if (eap->did_sandbox) {
cmdinfo->sandbox = true;
sandbox--;
+ eap->did_sandbox = false;
}
if (cmdmod.save_ei != NULL) {
cmdinfo->noautocmd = true;
@@ -1411,6 +1467,7 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er
if (eap->verbose_save != -1) {
cmdinfo->verbose = p_verbose;
p_verbose = eap->verbose_save;
+ eap->verbose_save = -1;
} else {
cmdinfo->verbose = -1;
}
@@ -1424,7 +1481,7 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er
if (*eap->cmd == '*') {
eap->cmd = skipwhite(eap->cmd + 1);
}
- p = find_command(eap, NULL);
+ p = find_ex_command(eap, NULL);
// Set command address type and parse command range
set_cmd_addr_type(eap, (char_u *)p);
@@ -1494,7 +1551,7 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er
}
// Set default range for command if required
if ((eap->argt & EX_DFLALL) && eap->addr_count == 0) {
- set_cmd_default_range(eap);
+ set_cmd_dflall_range(eap);
}
// Parse register and count
@@ -1519,6 +1576,148 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er
return true;
}
+/// 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
+void execute_cmd(exarg_T *eap, CmdParseInfo *cmdinfo)
+{
+#define ERROR(msg) \
+ do { \
+ emsg(msg); \
+ goto end; \
+ } while (0)
+
+ char *errormsg = NULL;
+ cmdmod_T save_cmdmod = cmdmod;
+ cmdmod = cmdinfo->cmdmod;
+
+ // Apply command modifiers
+ if (cmdinfo->silent) {
+ eap->save_msg_silent = msg_silent;
+ msg_silent++;
+ }
+ if (cmdinfo->emsg_silent) {
+ eap->did_esilent = true;
+ emsg_silent++;
+ }
+ if (cmdinfo->sandbox) {
+ eap->did_sandbox = true;
+ sandbox++;
+ }
+ if (cmdinfo->noautocmd) {
+ cmdmod.save_ei = (char *)vim_strsave(p_ei);
+ set_string_option_direct("ei", -1, (char_u *)"all", OPT_FREE, SID_NONE);
+ }
+ if (cmdinfo->verbose != -1) {
+ eap->verbose_save = p_verbose;
+ p_verbose = cmdinfo->verbose;
+ }
+
+ if (!MODIFIABLE(curbuf) && (eap->argt & EX_MODIFY)
+ // allow :put in terminals
+ && !(curbuf->terminal && eap->cmdidx == CMD_put)) {
+ ERROR(_(e_modifiable));
+ }
+ if (text_locked() && !(eap->argt & EX_CMDWIN)
+ && !IS_USER_CMDIDX(eap->cmdidx)) {
+ 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, (char_u **)eap->cmdlinep, &errormsg) == FAIL) {
+ ERROR(errormsg);
+ }
+ }
+
+ // Accept buffer name. Cannot be used at the same time with a buffer
+ // number. Don't do this for a user command.
+ if ((eap->argt & EX_BUFNAME) && *eap->arg != NUL && eap->addr_count == 0
+ && !IS_USER_CMDIDX(eap->cmdidx)) {
+ if (eap->args == NULL) {
+ // If argument positions are not specified, search the argument for the buffer name.
+ // :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.
+ char *p;
+
+ if (eap->cmdidx == CMD_bdelete || eap->cmdidx == CMD_bwipeout
+ || eap->cmdidx == CMD_bunload) {
+ p = (char *)skiptowhite_esc((char_u *)eap->arg);
+ } else {
+ p = eap->arg + STRLEN(eap->arg);
+ while (p > eap->arg && ascii_iswhite(p[-1])) {
+ p--;
+ }
+ }
+ eap->line2 = buflist_findpat((char_u *)eap->arg, (char_u *)p, (eap->argt & EX_BUFUNL) != 0,
+ false, false);
+ eap->addr_count = 1;
+ eap->arg = skipwhite(p);
+ } else {
+ // If argument positions are specified, just use the first argument
+ eap->line2 = buflist_findpat((char_u *)eap->args[0],
+ (char_u *)(eap->args[0] + eap->arglens[0]),
+ (eap->argt & EX_BUFUNL) != 0, false, false);
+ eap->addr_count = 1;
+ // Shift each argument by 1
+ if (eap->args != NULL) {
+ for (size_t i = 0; i < eap->argc - 1; i++) {
+ eap->args[i] = eap->args[i + 1];
+ }
+ // Make the last argument point to the NUL terminator at the end of string
+ eap->args[eap->argc - 1] = eap->args[eap->argc - 1] + eap->arglens[eap->argc - 1];
+ eap->argc -= 1;
+ }
+ eap->arg = eap->args[0];
+ }
+ if (eap->line2 < 0) { // failed
+ goto end;
+ }
+ }
+
+ // Execute the command
+ if (IS_USER_CMDIDX(eap->cmdidx)) {
+ // Execute a user-defined command.
+ do_ucmd(eap);
+ } else {
+ // Call the function to execute the command.
+ eap->errmsg = NULL;
+ (cmdnames[eap->cmdidx].cmd_func)(eap);
+ if (eap->errmsg != NULL) {
+ ERROR(_(eap->errmsg));
+ }
+ }
+end:
+ // Undo command modifiers
+ undo_cmdmod(eap, msg_scroll);
+ cmdmod = save_cmdmod;
+
+#undef ERROR
+}
+
/// Execute one Ex command.
///
/// If 'sourcing' is TRUE, the command will be included in the error message.
@@ -1606,7 +1805,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter
if (*ea.cmd == '*') {
ea.cmd = skipwhite(ea.cmd + 1);
}
- p = find_command(&ea, NULL);
+ p = find_ex_command(&ea, NULL);
// Count this line for profiling if skip is TRUE.
if (do_profiling == PROF_YES
@@ -1732,7 +1931,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter
xfree(p);
// If the autocommands did something and didn't cause an error, try
// finding the command again.
- p = (ret && !aborting()) ? find_command(&ea, NULL) : ea.cmd;
+ p = (ret && !aborting()) ? find_ex_command(&ea, NULL) : ea.cmd;
}
if (p == NULL) {
@@ -1759,10 +1958,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter
}
// set when Not Implemented
- const int ni = !IS_USER_CMDIDX(ea.cmdidx)
- && (cmdnames[ea.cmdidx].cmd_func == ex_ni
- || cmdnames[ea.cmdidx].cmd_func == ex_script_ni);
-
+ const int ni = is_cmd_ni(ea.cmdidx);
// Forced commands.
if (*p == '!' && ea.cmdidx != CMD_substitute
@@ -1976,7 +2172,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter
}
if ((ea.argt & EX_DFLALL) && ea.addr_count == 0) {
- set_cmd_default_range(&ea);
+ set_cmd_dflall_range(&ea);
}
// Parse register and count
@@ -2544,6 +2740,7 @@ static void undo_cmdmod(const exarg_T *eap, int save_msg_scroll)
}
+
/// Parse the address range, if any, in "eap".
/// May set the last search pattern, unless "silent" is true.
///
@@ -2557,47 +2754,7 @@ int parse_cmd_address(exarg_T *eap, char **errormsg, bool silent)
// Repeat for all ',' or ';' separated addresses.
for (;;) {
eap->line1 = eap->line2;
- switch (eap->addr_type) {
- case ADDR_LINES:
- case ADDR_OTHER:
- // Default is the cursor line number. Avoid using an invalid
- // line number though.
- if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) {
- eap->line2 = curbuf->b_ml.ml_line_count;
- } else {
- eap->line2 = curwin->w_cursor.lnum;
- }
- break;
- case ADDR_WINDOWS:
- eap->line2 = CURRENT_WIN_NR;
- break;
- case ADDR_ARGUMENTS:
- eap->line2 = curwin->w_arg_idx + 1;
- if (eap->line2 > ARGCOUNT) {
- eap->line2 = ARGCOUNT;
- }
- break;
- case ADDR_LOADED_BUFFERS:
- case ADDR_BUFFERS:
- eap->line2 = curbuf->b_fnum;
- break;
- case ADDR_TABS:
- eap->line2 = CURRENT_TAB_NR;
- break;
- case ADDR_TABS_RELATIVE:
- case ADDR_UNSIGNED:
- eap->line2 = 1;
- break;
- case ADDR_QUICKFIX:
- eap->line2 = (linenr_T)qf_get_cur_idx(eap);
- break;
- case ADDR_QUICKFIX_VALID:
- eap->line2 = qf_get_cur_valid_idx(eap);
- break;
- case ADDR_NONE:
- // Will give an error later if a range is found.
- break;
- }
+ eap->line2 = get_cmd_default_range(eap);
eap->cmd = skipwhite(eap->cmd);
lnum = get_address(eap, &eap->cmd, eap->addr_type, eap->skip, silent,
eap->addr_count == 0, address_count++);
@@ -2772,7 +2929,7 @@ static void append_command(char *cmd)
/// "full" is set to TRUE if the whole command name matched.
///
/// @return NULL for an ambiguous user command.
-static char *find_command(exarg_T *eap, int *full)
+char *find_ex_command(exarg_T *eap, int *full)
FUNC_ATTR_NONNULL_ARG(1)
{
int len;
@@ -2859,7 +3016,7 @@ static char *find_command(exarg_T *eap, int *full)
for (; (int)eap->cmdidx < CMD_SIZE;
eap->cmdidx = (cmdidx_T)((int)eap->cmdidx + 1)) {
- if (STRNCMP(cmdnames[(int)eap->cmdidx].cmd_name, (char *)eap->cmd,
+ if (STRNCMP(cmdnames[(int)eap->cmdidx].cmd_name, eap->cmd,
(size_t)len) == 0) {
if (full != NULL
&& cmdnames[(int)eap->cmdidx].cmd_name[len] == NUL) {
@@ -3076,7 +3233,7 @@ int cmd_exists(const char *const name)
ea.cmd = (char *)((*name == '2' || *name == '3') ? name + 1 : name);
ea.cmdidx = (cmdidx_T)0;
int full = false;
- p = find_command(&ea, &full);
+ p = find_ex_command(&ea, &full);
if (p == NULL) {
return 3;
}
@@ -3108,7 +3265,7 @@ void f_fullcommand(typval_T *argvars, typval_T *rettv, FunPtr fptr)
ea.cmd = (*name == '2' || *name == '3') ? name + 1 : name;
ea.cmdidx = (cmdidx_T)0;
- char *p = find_command(&ea, NULL);
+ char *p = find_ex_command(&ea, NULL);
if (p == NULL || ea.cmdidx == CMD_SIZE) {
return;
}
@@ -3431,7 +3588,7 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff)
xp->xp_shell = TRUE;
#endif
// When still after the command name expand executables.
- if ((char_u *)xp->xp_pattern == (char_u *)skipwhite(arg)) {
+ if (xp->xp_pattern == skipwhite(arg)) {
xp->xp_context = EXPAND_SHELLCMD;
}
}
@@ -4416,7 +4573,7 @@ static void ex_script_ni(exarg_T *eap)
/// Check range in Ex command for validity.
///
/// @return NULL when valid, error message when invalid.
-static char *invalid_range(exarg_T *eap)
+char *invalid_range(exarg_T *eap)
{
buf_T *buf;
if (eap->line1 < 0 || eap->line2 < 0 || eap->line1 > eap->line2) {
@@ -4535,7 +4692,7 @@ static char *skip_grep_pat(exarg_T *eap)
/// For the ":make" and ":grep" commands insert the 'makeprg'/'grepprg' option
/// in the command line, so that things like % get expanded.
-static char *replace_makeprg(exarg_T *eap, char *p, char **cmdlinep)
+char *replace_makeprg(exarg_T *eap, char *p, char **cmdlinep)
{
char *new_cmdline;
char *program;
@@ -4773,7 +4930,7 @@ int expand_filename(exarg_T *eap, char_u **cmdlinep, char **errormsgp)
return OK;
}
-/// Replace part of the command line, keeping eap->cmd, eap->arg and
+/// Replace part of the command line, keeping eap->cmd, eap->arg, eap->args and
/// eap->nextcmd correct.
/// "src" points to the part that is to be replaced, of length "srclen".
/// "repl" is the replacement string.
@@ -4792,6 +4949,7 @@ static char *repl_cmdline(exarg_T *eap, char *src, size_t srclen, char *repl, ch
i += STRLEN(eap->nextcmd); // add space for next command
}
char *new_cmdline = xmalloc(i);
+ size_t offset = (size_t)(src - *cmdlinep);
/*
* Copy the stuff before the expanded part.
@@ -4799,7 +4957,7 @@ static char *repl_cmdline(exarg_T *eap, char *src, size_t srclen, char *repl, ch
* Copy what came after the expanded part.
* Copy the next commands, if there are any.
*/
- i = (size_t)(src - *cmdlinep); // length of part before match
+ i = offset; // length of part before match
memmove(new_cmdline, *cmdlinep, i);
memmove(new_cmdline + i, repl, len);
@@ -4814,6 +4972,19 @@ static char *repl_cmdline(exarg_T *eap, char *src, size_t srclen, char *repl, ch
}
eap->cmd = new_cmdline + (eap->cmd - *cmdlinep);
eap->arg = new_cmdline + (eap->arg - *cmdlinep);
+
+ for (size_t j = 0; j < eap->argc; j++) {
+ if (offset >= (size_t)(eap->args[j] - *cmdlinep)) {
+ // If replaced text is after or in the same position as the argument,
+ // the argument's position relative to the beginning of the cmdline stays the same.
+ eap->args[j] = new_cmdline + (eap->args[j] - *cmdlinep);
+ } else {
+ // Otherwise, argument gets shifted alongside the replaced text.
+ // The amount of the shift is equal to the difference of the old and new string length.
+ eap->args[j] = new_cmdline + (eap->args[j] - *cmdlinep) + (len - srclen);
+ }
+ }
+
if (eap->do_ecmd_cmd != NULL && eap->do_ecmd_cmd != dollar_command) {
eap->do_ecmd_cmd = new_cmdline + (eap->do_ecmd_cmd - *cmdlinep);
}
@@ -5077,7 +5248,9 @@ static int get_tabpage_arg(exarg_T *eap)
tab_number = 0;
} else {
tab_number = (int)eap->line2;
- if (!unaccept_arg0 && *skipwhite(*eap->cmdlinep) == '-') {
+ char *cmdp = eap->cmd;
+ while (--cmdp > *eap->cmdlinep && (*cmdp == ' ' || ascii_isdigit(*cmdp))) {}
+ if (!unaccept_arg0 && *cmdp == '-') {
tab_number--;
if (tab_number < unaccept_arg0) {
eap->errmsg = e_invarg;
@@ -6002,7 +6175,7 @@ bool uc_split_args_iter(const char *arg, size_t arglen, size_t *end, char *buf,
}
/// split and quote args for <f-args>
-static char *uc_split_args(char *arg, size_t *lenp)
+static char *uc_split_args(char *arg, char **args, size_t *arglens, size_t argc, size_t *lenp)
{
char *buf;
char *p;
@@ -6010,61 +6183,107 @@ static char *uc_split_args(char *arg, size_t *lenp)
int len;
// Precalculate length
- p = arg;
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;
+ 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;
}
- len += 3; // ","
- } else {
- const int charlen = utfc_ptr2len(p);
+ }
+ } 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;
+ len += charlen;
+ p += charlen;
+ }
+ }
+
+ if (i != argc - 1) {
+ len += 3; // ","
+ }
}
}
buf = xmalloc((size_t)len + 1);
- p = arg;
q = buf;
*q++ = '"';
- 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;
+
+ 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++ = ',';
- *q++ = '"';
- } else {
- mb_copy_char((const char_u **)&p, (char_u **)&q);
}
}
+
*q++ = '"';
*q = 0;
@@ -6201,7 +6420,7 @@ static size_t uc_check_code(char *code, size_t len, char *buf, ucmd_T *cmd, exar
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, split_len);
+ *split_buf = uc_split_args(eap->arg, eap->args, eap->arglens, eap->argc, split_len);
}
result = *split_len;
@@ -9887,7 +10106,7 @@ bool cmd_can_preview(char *cmd)
if (*ea.cmd == '*') {
ea.cmd = skipwhite(ea.cmd + 1);
}
- char *end = find_command(&ea, NULL);
+ char *end = find_ex_command(&ea, NULL);
switch (ea.cmdidx) {
case CMD_substitute:
@@ -10278,3 +10497,9 @@ void verify_command(char *cmd)
msg("` `.:.`.,:iii;;;;;;;;iii;;;:` `.`` "
" `nW");
}
+
+/// Get argt of command with id
+uint32_t get_cmd_argt(cmdidx_T cmdidx)
+{
+ return cmdnames[(int)cmdidx].cmd_argt;
+}
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index d4665ac031..a7e843704f 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -33,12 +33,13 @@
#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
+#include "nvim/globals.h"
#include "nvim/highlight.h"
#include "nvim/highlight_defs.h"
#include "nvim/highlight_group.h"
#include "nvim/if_cscope.h"
#include "nvim/indent.h"
-#include "nvim/keymap.h"
+#include "nvim/keycodes.h"
#include "nvim/lib/kvec.h"
#include "nvim/log.h"
#include "nvim/lua/executor.h"
@@ -2522,7 +2523,7 @@ char *get_text_locked_msg(void)
bool curbuf_locked(void)
{
if (curbuf->b_ro_locked > 0) {
- emsg(_("E788: Not allowed to edit another buffer now"));
+ emsg(_(e_cannot_edit_other_buf));
return true;
}
return allbuf_locked();
diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c
index 045f778e4c..4554af5356 100644
--- a/src/nvim/ex_session.c
+++ b/src/nvim/ex_session.c
@@ -27,7 +27,7 @@
#include "nvim/fold.h"
#include "nvim/getchar.h"
#include "nvim/globals.h"
-#include "nvim/keymap.h"
+#include "nvim/keycodes.h"
#include "nvim/move.h"
#include "nvim/option.h"
#include "nvim/os/input.h"
diff --git a/src/nvim/generators/gen_eval.lua b/src/nvim/generators/gen_eval.lua
index 945fa5099f..f094a04c07 100644
--- a/src/nvim/generators/gen_eval.lua
+++ b/src/nvim/generators/gen_eval.lua
@@ -1,9 +1,12 @@
local mpack = require('mpack')
local nvimsrcdir = arg[1]
-local autodir = arg[2]
-local metadata_file = arg[3]
-local funcs_file = arg[4]
+local shared_file = arg[2]
+local autodir = arg[3]
+local metadata_file = arg[4]
+local funcs_file = arg[5]
+
+_G.vim = loadfile(shared_file)()
if nvimsrcdir == '--help' then
print([[
@@ -20,7 +23,9 @@ package.path = nvimsrcdir .. '/?.lua;' .. package.path
local funcsfname = autodir .. '/funcs.generated.h'
-local gperfpipe = io.open(funcsfname .. '.gperf', 'wb')
+local hashy = require'generators.hashy'
+
+local hashpipe = io.open(funcsfname, 'wb')
local funcs = require('eval').funcs
local metadata = mpack.unpack(io.open(metadata_file, 'rb'):read("*all"))
@@ -38,21 +43,15 @@ local funcsdata = io.open(funcs_file, 'w')
funcsdata:write(mpack.pack(funcs))
funcsdata:close()
-gperfpipe:write([[
-%language=ANSI-C
-%global-table
-%readonly-tables
-%define initializer-suffix ,0,0,BASE_NONE,NULL,NULL
-%define word-array-name functions
-%define hash-function-name hash_internal_func_gperf
-%define lookup-function-name find_internal_func_gperf
-%omit-struct-type
-%struct-type
-VimLFuncDef;
-%%
-]])
-for name, def in pairs(funcs) do
+local names = vim.tbl_keys(funcs)
+
+local neworder, hashfun = hashy.hashy_hash("find_internal_func", names, function (idx)
+ return "functions["..idx.."].name"
+end)
+hashpipe:write("static const EvalFuncDef functions[] = {\n")
+for _, name in ipairs(neworder) do
+ local def = funcs[name]
local args = def.args or 0
if type(args) == 'number' then
args = {args, args}
@@ -62,7 +61,10 @@ for name, def in pairs(funcs) do
local base = def.base or "BASE_NONE"
local func = def.func or ('f_' .. name)
local data = def.data or "NULL"
- gperfpipe:write(('%s, %s, %s, %s, &%s, (FunPtr)%s\n')
+ hashpipe:write((' { "%s", %s, %s, %s, &%s, (FunPtr)%s },\n')
:format(name, args[1], args[2], base, func, data))
end
-gperfpipe:close()
+hashpipe:write(' { NULL, 0, 0, BASE_NONE, NULL, NULL },\n')
+hashpipe:write("};\n\n")
+hashpipe:write(hashfun)
+hashpipe:close()
diff --git a/src/nvim/generators/hashy.lua b/src/nvim/generators/hashy.lua
index fac24c810a..b10bafb9f9 100644
--- a/src/nvim/generators/hashy.lua
+++ b/src/nvim/generators/hashy.lua
@@ -77,7 +77,7 @@ function M.switcher(put, tab, maxlen, worst_buck_size)
put "break;\n"
end
put " default: break;\n"
- put " }\n "
+ put " }\n "
else
local startidx = #neworder
table.insert(neworder, posbuck[keys[1]][1])
@@ -85,7 +85,7 @@ function M.switcher(put, tab, maxlen, worst_buck_size)
put("low = "..startidx.."; ")
if bucky then put("high = "..endidx.."; ") end
end
- put " break;\n"
+ put "break;\n"
end
end
put " default: break;\n"
@@ -105,17 +105,23 @@ function M.hashy_hash(name, strings, access)
end
local neworder = M.switcher(put, len_pos_buckets, maxlen, worst_buck_size)
if worst_buck_size > 1 then
- error [[ not implemented yet ]] -- TODO(bfredl)
+ put ([[
+ for (int i = low; i < high; i++) {
+ if (!memcmp(str, ]]..access("i")..[[, len)) {
+ return i;
+ }
+ }
+ return -1;
+]])
else
- put [[
- if (low < 0) {
+ put ([[
+ if (low < 0 || memcmp(str, ]]..access("low")..[[, len)) {
return -1;
}
- ]]
- put("if(memcmp(str, "..access("low")..", len)) {\n return -1;\n }\n")
- put " return low;\n"
- put "}\n\n"
+ return low;
+]])
end
+ put "}\n\n"
return neworder, table.concat(stats)
end
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index c0a5a14f7b..df45de2abf 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -31,7 +31,7 @@
#include "nvim/garray.h"
#include "nvim/getchar.h"
#include "nvim/input.h"
-#include "nvim/keymap.h"
+#include "nvim/keycodes.h"
#include "nvim/lua/executor.h"
#include "nvim/main.h"
#include "nvim/mbyte.h"
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index ef8ff11bf8..1a1f9bd866 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -997,6 +997,7 @@ EXTERN char e_listarg[] INIT(= N_("E686: Argument of %s must be a List"));
EXTERN char e_unsupportedoption[] INIT(= N_("E519: Option not supported"));
EXTERN char e_fnametoolong[] INIT(= N_("E856: Filename too long"));
EXTERN char e_float_as_string[] INIT(= N_("E806: using Float as a String"));
+EXTERN char e_cannot_edit_other_buf[] INIT(= N_("E788: Not allowed to edit another buffer now"));
EXTERN char e_autocmd_err[] INIT(= N_("E5500: autocmd has thrown an exception: %s"));
EXTERN char e_cmdmap_err[] INIT(= N_("E5520: <Cmd> mapping must end with <CR>"));
diff --git a/src/nvim/keymap.c b/src/nvim/keycodes.c
index b184b42354..676ddcf8d4 100644
--- a/src/nvim/keymap.c
+++ b/src/nvim/keycodes.c
@@ -9,7 +9,7 @@
#include "nvim/charset.h"
#include "nvim/edit.h"
#include "nvim/eval.h"
-#include "nvim/keymap.h"
+#include "nvim/keycodes.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/mouse.h"
@@ -17,12 +17,10 @@
#include "nvim/vim.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "keymap.c.generated.h"
+# include "keycodes.c.generated.h"
#endif
-/*
- * Some useful tables.
- */
+// Some useful tables.
static const struct modmasktable {
uint16_t mod_mask; ///< Bit-mask for particular key modifier.
@@ -43,10 +41,9 @@ static const struct modmasktable {
// NOTE: when adding an entry, update MAX_KEY_NAME_LEN!
};
-/*
- * Shifted key terminal codes and their unshifted equivalent.
- * Don't add mouse codes here, they are handled separately!
- */
+// Shifted key terminal codes and their unshifted equivalent.
+// Don't add mouse codes here, they are handled separately!
+
#define MOD_KEYS_ENTRY_SIZE 5
static char_u modifier_keys_table[] =
@@ -461,10 +458,7 @@ int handle_x_keys(const int key)
return key;
}
-/*
- * Return a string which contains the name of the given key when the given
- * modifiers are down.
- */
+/// @return a string which contains the name of the given key when the given modifiers are down.
char_u *get_special_key_name(int c, int modifiers)
{
static char_u string[MAX_KEY_NAME_LEN + 1];
@@ -481,10 +475,8 @@ char_u *get_special_key_name(int c, int modifiers)
c = KEY2TERMCAP1(c);
}
- /*
- * Translate shifted special keys into unshifted keys and set modifier.
- * Same for CTRL and ALT modifiers.
- */
+ // Translate shifted special keys into unshifted keys and set modifier.
+ // Same for CTRL and ALT modifiers.
if (IS_SPECIAL(c)) {
for (i = 0; modifier_keys_table[i] != 0; i += MOD_KEYS_ENTRY_SIZE) {
if (KEY2TERMCAP0(c) == (int)modifier_keys_table[i + 1]
@@ -500,10 +492,8 @@ char_u *get_special_key_name(int c, int modifiers)
// try to find the key in the special key table
table_idx = find_special_key_in_table(c);
- /*
- * When not a known special key, and not a printable character, try to
- * extract modifiers.
- */
+ // When not a known special key, and not a printable character, try to
+ // extract modifiers.
if (c > 0
&& utf_char2len(c) == 1) {
if (table_idx < 0
@@ -798,10 +788,8 @@ static int extract_modifiers(int key, int *modp, const bool simplify, bool *cons
return key;
}
-/*
- * Try to find key "c" in the special key table.
- * Return the index when found, -1 when not found.
- */
+/// Try to find key "c" in the special key table.
+/// @return the index when found, -1 when not found.
int find_special_key_in_table(int c)
{
int i;
@@ -844,10 +832,8 @@ int get_special_key_code(const char_u *name)
return 0;
}
-/*
- * Look up the given mouse code to return the relevant information in the other
- * arguments. Return which button is down or was released.
- */
+/// Look up the given mouse code to return the relevant information in the other arguments.
+/// @return which button is down or was released.
int get_mouse_button(int code, bool *is_click, bool *is_drag)
{
int i;
@@ -1009,7 +995,7 @@ char *replace_termcodes(const char *const from, const size_t from_len, char **co
} else {
result[dlen++] = *src;
}
- ++src;
+ src++;
}
}
result[dlen] = NUL;
diff --git a/src/nvim/keymap.h b/src/nvim/keycodes.h
index 9febd472f9..631a9f68d3 100644
--- a/src/nvim/keymap.h
+++ b/src/nvim/keycodes.h
@@ -1,13 +1,11 @@
-#ifndef NVIM_KEYMAP_H
-#define NVIM_KEYMAP_H
+#ifndef NVIM_KEYCODES_H
+#define NVIM_KEYCODES_H
#include "nvim/strings.h"
-/*
- * Keycode definitions for special keys.
- *
- * Any special key code sequences are replaced by these codes.
- */
+// Keycode definitions for special keys.
+//
+// Any special key code sequences are replaced by these codes.
//
// For MS-DOS some keys produce codes larger than 0xff. They are split into two
@@ -15,66 +13,49 @@
//
#define K_NUL (0xce) // for MS-DOS: special key follows
-/*
- * K_SPECIAL is the first byte of a special key code and is always followed by
- * two bytes.
- * The second byte can have any value. ASCII is used for normal termcap
- * entries, 0x80 and higher for special keys, see below.
- * The third byte is guaranteed to be between 0x02 and 0x7f.
- */
-
+/// K_SPECIAL is the first byte of a special key code and is always followed by
+/// two bytes.
+/// The second byte can have any value. ASCII is used for normal termcap
+/// entries, 0x80 and higher for special keys, see below.
+/// The third byte is guaranteed to be between 0x02 and 0x7f.
#define K_SPECIAL (0x80)
-/*
- * Positive characters are "normal" characters.
- * Negative characters are special key codes. Only characters below -0x200
- * are used to so that the absolute value can't be mistaken for a single-byte
- * character.
- */
+/// Positive characters are "normal" characters.
+/// Negative characters are special key codes. Only characters below -0x200
+/// are used to so that the absolute value can't be mistaken for a single-byte
+/// character.
#define IS_SPECIAL(c) ((c) < 0)
-/*
- * Characters 0x0100 - 0x01ff have a special meaning for abbreviations.
- * Multi-byte characters also have ABBR_OFF added, thus are above 0x0200.
- */
+/// Characters 0x0100 - 0x01ff have a special meaning for abbreviations.
+/// Multi-byte characters also have ABBR_OFF added, thus are above 0x0200.
#define ABBR_OFF 0x100
-/*
- * NUL cannot be in the input string, therefore it is replaced by
- * K_SPECIAL KS_ZERO KE_FILLER
- */
+/// NUL cannot be in the input string, therefore it is replaced by
+/// K_SPECIAL KS_ZERO KE_FILLER
#define KS_ZERO 255
-/*
- * K_SPECIAL cannot be in the input string, therefore it is replaced by
- * K_SPECIAL KS_SPECIAL KE_FILLER
- */
+/// K_SPECIAL cannot be in the input string, therefore it is replaced by
+/// K_SPECIAL KS_SPECIAL KE_FILLER
#define KS_SPECIAL 254
-/*
- * KS_EXTRA is used for keys that have no termcap name
- * K_SPECIAL KS_EXTRA KE_xxx
- */
+/// KS_EXTRA is used for keys that have no termcap name
+/// K_SPECIAL KS_EXTRA KE_xxx
#define KS_EXTRA 253
-/*
- * KS_MODIFIER is used when a modifier is given for a (special) key
- * K_SPECIAL KS_MODIFIER bitmask
- */
+/// KS_MODIFIER is used when a modifier is given for a (special) key
+/// K_SPECIAL KS_MODIFIER bitmask
#define KS_MODIFIER 252
-/*
- * These are used for the GUI
- * K_SPECIAL KS_xxx KE_FILLER
- */
+// These are used for the GUI
+// K_SPECIAL KS_xxx KE_FILLER
+
#define KS_MOUSE 251
#define KS_MENU 250
#define KS_VER_SCROLLBAR 249
#define KS_HOR_SCROLLBAR 248
-/*
- * Used for switching Select mode back on after a mapping or menu.
- */
+// Used for switching Select mode back on after a mapping or menu.
+
#define KS_SELECT 245
#define K_SELECT_STRING (char_u *)"\200\365X"
@@ -87,30 +68,24 @@
// Used for menu in a tab pages line.
#define KS_TABMENU 239
-/*
- * Filler used after KS_SPECIAL and others
- */
+/// Filler used after KS_SPECIAL and others
#define KE_FILLER ('X')
-/*
- * translation of three byte code "K_SPECIAL a b" into int "K_xxx" and back
- */
+// translation of three byte code "K_SPECIAL a b" into int "K_xxx" and back
+
#define TERMCAP2KEY(a, b) (-((a) + ((int)(b) << 8)))
#define KEY2TERMCAP0(x) ((-(x)) & 0xff)
#define KEY2TERMCAP1(x) (((unsigned)(-(x)) >> 8) & 0xff)
-/*
- * get second or third byte when translating special key code into three bytes
- */
+// get second or third byte when translating special key code into three bytes
+
#define K_SECOND(c) ((c) == K_SPECIAL ? KS_SPECIAL : (c) == \
NUL ? KS_ZERO : KEY2TERMCAP0(c))
#define K_THIRD(c) (((c) == K_SPECIAL || (c) == \
NUL) ? KE_FILLER : KEY2TERMCAP1(c))
-/*
- * get single int code from second byte after K_SPECIAL
- */
+/// get single int code from second byte after K_SPECIAL
#define TO_SPECIAL(a, b) ((a) == KS_SPECIAL ? K_SPECIAL : (a) == \
KS_ZERO ? K_ZERO : TERMCAP2KEY(a, b))
@@ -247,9 +222,8 @@ enum key_extra {
KE_COMMAND = 104, // <Cmd> special key
};
-/*
- * the three byte codes are replaced with the following int when using vgetc()
- */
+// the three byte codes are replaced with the following int when using vgetc()
+
#define K_ZERO TERMCAP2KEY(KS_ZERO, KE_FILLER)
#define K_UP TERMCAP2KEY('k', 'u')
@@ -430,10 +404,9 @@ enum key_extra {
#define K_TABLINE TERMCAP2KEY(KS_TABLINE, KE_FILLER)
#define K_TABMENU TERMCAP2KEY(KS_TABMENU, KE_FILLER)
-/*
- * Symbols for pseudo keys which are translated from the real key symbols
- * above.
- */
+// Symbols for pseudo keys which are translated from the real key symbols
+// above.
+
#define K_LEFTMOUSE TERMCAP2KEY(KS_EXTRA, KE_LEFTMOUSE)
#define K_LEFTMOUSE_NM TERMCAP2KEY(KS_EXTRA, KE_LEFTMOUSE_NM)
#define K_LEFTDRAG TERMCAP2KEY(KS_EXTRA, KE_LEFTDRAG)
@@ -486,11 +459,8 @@ enum key_extra {
#define MOD_MASK_MULTI_CLICK (MOD_MASK_2CLICK|MOD_MASK_3CLICK| \
MOD_MASK_4CLICK)
-/*
- * The length of the longest special key name, including modifiers.
- * Current longest is <M-C-S-T-D-A-4-ScrollWheelRight> (length includes '<' and
- * '>').
- */
+/// The length of the longest special key name, including modifiers.
+/// Current longest is <M-C-S-T-D-A-4-ScrollWheelRight> (length includes '<' and '>').
#define MAX_KEY_NAME_LEN 32
// Maximum length of a special key event as tokens. This includes modifiers.
@@ -524,6 +494,6 @@ enum {
};
#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "keymap.h.generated.h"
+# include "keycodes.h.generated.h"
#endif
-#endif // NVIM_KEYMAP_H
+#endif // NVIM_KEYCODES_H
diff --git a/src/nvim/log.c b/src/nvim/log.c
index 7d50ecf69e..815d53b570 100644
--- a/src/nvim/log.c
+++ b/src/nvim/log.c
@@ -51,7 +51,7 @@ static bool log_try_create(char *fname)
/// Initializes path to log file. Sets $NVIM_LOG_FILE if empty.
///
-/// Tries $NVIM_LOG_FILE, or falls back to $XDG_CACHE_HOME/nvim/log. Path to log
+/// Tries $NVIM_LOG_FILE, or falls back to $XDG_STATE_HOME/nvim/log. Path to log
/// file is cached, so only the first call has effect, unless first call was not
/// successful. Failed initialization indicates either a bug in expand_env()
/// or both $NVIM_LOG_FILE and $HOME environment variables are undefined.
@@ -69,16 +69,16 @@ static bool log_path_init(void)
|| log_file_path[0] == '\0'
|| os_isdir((char_u *)log_file_path)
|| !log_try_create(log_file_path)) {
- // Make kXDGCacheHome if it does not exist.
- char *cachehome = get_xdg_home(kXDGCacheHome);
+ // Make kXDGStateHome if it does not exist.
+ char *loghome = get_xdg_home(kXDGStateHome);
char *failed_dir = NULL;
bool log_dir_failure = false;
- if (!os_isdir((char_u *)cachehome)) {
- log_dir_failure = (os_mkdir_recurse(cachehome, 0700, &failed_dir) != 0);
+ if (!os_isdir((char_u *)loghome)) {
+ log_dir_failure = (os_mkdir_recurse(loghome, 0700, &failed_dir) != 0);
}
- XFREE_CLEAR(cachehome);
+ XFREE_CLEAR(loghome);
// Invalid $NVIM_LOG_FILE or failed to expand; fall back to default.
- char *defaultpath = stdpaths_user_cache_subpath("log");
+ char *defaultpath = stdpaths_user_state_subpath("log", 0, true);
size_t len = xstrlcpy(log_file_path, defaultpath, size);
xfree(defaultpath);
// Fall back to .nvimlog
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index 29ed40af03..9758cee0a5 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -1863,8 +1863,8 @@ void nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap)
if (cmd->uc_argt & EX_NOSPC) {
// Commands where nargs = 1 or "?" fargs is the same as args
lua_rawseti(lstate, -2, 1);
- } else {
- // Commands with more than one possible argument we split
+ } else if (eap->args == NULL) {
+ // For commands with more than one possible argument, split if argument list isn't available.
lua_pop(lstate, 1); // Pop the reference of opts.args
size_t length = STRLEN(eap->arg);
size_t end = 0;
@@ -1881,6 +1881,13 @@ void nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap)
}
}
xfree(buf);
+ } else {
+ // If argument list is available, just use it.
+ lua_pop(lstate, 1);
+ for (size_t i = 0; i < eap->argc; i++) {
+ lua_pushlstring(lstate, eap->args[i], eap->arglens[i]);
+ lua_rawseti(lstate, -2, (int)i + 1);
+ }
}
lua_setfield(lstate, -2, "fargs");
diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c
index 4ea9329a71..c7408c6260 100644
--- a/src/nvim/mbyte.c
+++ b/src/nvim/mbyte.c
@@ -1992,6 +1992,31 @@ theend:
convert_setup(&vimconv, NULL, NULL);
}
+/// @return true if string "s" is a valid utf-8 string.
+/// When "end" is NULL stop at the first NUL.
+/// When "end" is positive stop there.
+bool utf_valid_string(const char_u *s, const char_u *end)
+{
+ const char_u *p = s;
+
+ while (end == NULL ? *p != NUL : p < end) {
+ int l = utf8len_tab_zero[*p];
+ if (l == 0) {
+ return false; // invalid lead byte
+ }
+ if (end != NULL && p + l > end) {
+ return false; // incomplete byte sequence
+ }
+ p++;
+ while (--l > 0) {
+ if ((*p++ & 0xc0) != 0x80) {
+ return false; // invalid trail byte
+ }
+ }
+ }
+ return true;
+}
+
/*
* If the cursor moves on an trail byte, set the cursor on the lead byte.
* Thus it moves left if necessary.
diff --git a/src/nvim/menu.c b/src/nvim/menu.c
index 14084df4d4..7b254f1b62 100644
--- a/src/nvim/menu.c
+++ b/src/nvim/menu.c
@@ -18,7 +18,7 @@
#include "nvim/ex_docmd.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
-#include "nvim/keymap.h"
+#include "nvim/keycodes.h"
#include "nvim/memory.h"
#include "nvim/menu.h"
#include "nvim/message.h"
diff --git a/src/nvim/message.c b/src/nvim/message.c
index 29473ac2b6..52eafe6e26 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -24,7 +24,7 @@
#include "nvim/getchar.h"
#include "nvim/highlight.h"
#include "nvim/input.h"
-#include "nvim/keymap.h"
+#include "nvim/keycodes.h"
#include "nvim/main.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index c54cc3ed09..a983ff4436 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -33,7 +33,7 @@
#include "nvim/getchar.h"
#include "nvim/globals.h"
#include "nvim/indent.h"
-#include "nvim/keymap.h"
+#include "nvim/keycodes.h"
#include "nvim/log.h"
#include "nvim/main.h"
#include "nvim/mark.h"
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 66d9d2843c..b1e9226937 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -49,7 +49,7 @@
#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
#include "nvim/indent_c.h"
-#include "nvim/keymap.h"
+#include "nvim/keycodes.h"
#include "nvim/macros.h"
#include "nvim/mbyte.h"
#include "nvim/memfile.h"
@@ -491,17 +491,17 @@ void set_init_1(bool clean_arg)
#endif
false);
- char *backupdir = stdpaths_user_data_subpath("backup", 2, true);
+ char *backupdir = stdpaths_user_state_subpath("backup", 2, true);
const size_t backupdir_len = strlen(backupdir);
backupdir = xrealloc(backupdir, backupdir_len + 3);
memmove(backupdir + 2, backupdir, backupdir_len + 1);
memmove(backupdir, ".,", 2);
set_string_default("backupdir", backupdir, true);
- set_string_default("viewdir", stdpaths_user_data_subpath("view", 2, true),
+ set_string_default("viewdir", stdpaths_user_state_subpath("view", 2, true),
true);
- set_string_default("directory", stdpaths_user_data_subpath("swap", 2, true),
+ set_string_default("directory", stdpaths_user_state_subpath("swap", 2, true),
true);
- set_string_default("undodir", stdpaths_user_data_subpath("undo", 2, true),
+ set_string_default("undodir", stdpaths_user_state_subpath("undo", 2, true),
true);
// Set default for &runtimepath. All necessary expansions are performed in
// this function.
diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c
index 64e5a7f229..c99d2869da 100644
--- a/src/nvim/os/input.c
+++ b/src/nvim/os/input.c
@@ -13,7 +13,7 @@
#include "nvim/ex_cmds2.h"
#include "nvim/fileio.h"
#include "nvim/getchar.h"
-#include "nvim/keymap.h"
+#include "nvim/keycodes.h"
#include "nvim/main.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
diff --git a/src/nvim/os/stdpaths.c b/src/nvim/os/stdpaths.c
index 5b824d23f4..5d6ffc1db1 100644
--- a/src/nvim/os/stdpaths.c
+++ b/src/nvim/os/stdpaths.c
@@ -14,6 +14,7 @@ static const char *xdg_env_vars[] = {
[kXDGConfigHome] = "XDG_CONFIG_HOME",
[kXDGDataHome] = "XDG_DATA_HOME",
[kXDGCacheHome] = "XDG_CACHE_HOME",
+ [kXDGStateHome] = "XDG_STATE_HOME",
[kXDGRuntimeDir] = "XDG_RUNTIME_DIR",
[kXDGConfigDirs] = "XDG_CONFIG_DIRS",
[kXDGDataDirs] = "XDG_DATA_DIRS",
@@ -24,6 +25,7 @@ static const char *const xdg_defaults_env_vars[] = {
[kXDGConfigHome] = "LOCALAPPDATA",
[kXDGDataHome] = "LOCALAPPDATA",
[kXDGCacheHome] = "TEMP",
+ [kXDGStateHome] = "LOCALAPPDATA",
[kXDGRuntimeDir] = NULL,
[kXDGConfigDirs] = NULL,
[kXDGDataDirs] = NULL,
@@ -38,6 +40,7 @@ static const char *const xdg_defaults[] = {
[kXDGConfigHome] = "~\\AppData\\Local",
[kXDGDataHome] = "~\\AppData\\Local",
[kXDGCacheHome] = "~\\AppData\\Local\\Temp",
+ [kXDGStateHome] = "~\\AppData\\Local",
[kXDGRuntimeDir] = NULL,
[kXDGConfigDirs] = NULL,
[kXDGDataDirs] = NULL,
@@ -45,6 +48,7 @@ static const char *const xdg_defaults[] = {
[kXDGConfigHome] = "~/.config",
[kXDGDataHome] = "~/.local/share",
[kXDGCacheHome] = "~/.cache",
+ [kXDGStateHome] = "~/.local/state",
[kXDGRuntimeDir] = NULL,
[kXDGConfigDirs] = "/etc/xdg/",
[kXDGDataDirs] = "/usr/local/share/:/usr/share/",
@@ -133,15 +137,26 @@ char *stdpaths_user_conf_subpath(const char *fname)
/// Return subpath of $XDG_DATA_HOME
///
/// @param[in] fname New component of the path.
+///
+/// @return [allocated] `$XDG_DATA_HOME/nvim/{fname}`
+char *stdpaths_user_data_subpath(const char *fname)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
+{
+ return concat_fnames_realloc(get_xdg_home(kXDGDataHome), fname, true);
+}
+
+/// Return subpath of $XDG_STATE_HOME
+///
+/// @param[in] fname New component of the path.
/// @param[in] trailing_pathseps Amount of trailing path separators to add.
/// @param[in] escape_commas If true, all commas will be escaped.
///
-/// @return [allocated] `$XDG_DATA_HOME/nvim/{fname}`.
-char *stdpaths_user_data_subpath(const char *fname, const size_t trailing_pathseps,
- const bool escape_commas)
+/// @return [allocated] `$XDG_STATE_HOME/nvim/{fname}`.
+char *stdpaths_user_state_subpath(const char *fname, const size_t trailing_pathseps,
+ const bool escape_commas)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
{
- char *ret = concat_fnames_realloc(get_xdg_home(kXDGDataHome), fname, true);
+ char *ret = concat_fnames_realloc(get_xdg_home(kXDGStateHome), fname, true);
const size_t len = strlen(ret);
const size_t numcommas = (escape_commas ? memcnt(ret, ',', len) : 0);
if (numcommas || trailing_pathseps) {
diff --git a/src/nvim/os/stdpaths_defs.h b/src/nvim/os/stdpaths_defs.h
index 44c30df373..f94c511fe7 100644
--- a/src/nvim/os/stdpaths_defs.h
+++ b/src/nvim/os/stdpaths_defs.h
@@ -7,6 +7,7 @@ typedef enum {
kXDGConfigHome, ///< XDG_CONFIG_HOME
kXDGDataHome, ///< XDG_DATA_HOME
kXDGCacheHome, ///< XDG_CACHE_HOME
+ kXDGStateHome, ///< XDG_STATE_HOME
kXDGRuntimeDir, ///< XDG_RUNTIME_DIR
kXDGConfigDirs, ///< XDG_CONFIG_DIRS
kXDGDataDirs, ///< XDG_DATA_DIRS
diff --git a/src/nvim/search.c b/src/nvim/search.c
index bee17e861a..9143632c64 100644
--- a/src/nvim/search.c
+++ b/src/nvim/search.c
@@ -5160,7 +5160,7 @@ static void fuzzy_match_in_list(list_T *const l, char_u *const str, const bool m
int j = 0;
const char_u *p = str;
while (*p != NUL) {
- if (!ascii_iswhite(utf_ptr2char((char *)p))) {
+ if (!ascii_iswhite(utf_ptr2char((char *)p)) || matchseq) {
tv_list_append_number(items[match_count].lmatchpos, matches[j]);
j++;
}
diff --git a/src/nvim/shada.c b/src/nvim/shada.c
index a3d88a4963..abb6c68474 100644
--- a/src/nvim/shada.c
+++ b/src/nvim/shada.c
@@ -1447,7 +1447,7 @@ static const char *shada_get_default_file(void)
FUNC_ATTR_WARN_UNUSED_RESULT
{
if (default_shada_file == NULL) {
- char *shada_dir = stdpaths_user_data_subpath("shada", 0, false);
+ char *shada_dir = stdpaths_user_state_subpath("shada", 0, false);
default_shada_file = concat_fnames_realloc(shada_dir, "main.shada", true);
}
return default_shada_file;
diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c
index f099d1b34e..6f0bd04d39 100644
--- a/src/nvim/spellfile.c
+++ b/src/nvim/spellfile.c
@@ -302,6 +302,7 @@
#define CF_UPPER 0x02
static char *e_spell_trunc = N_("E758: Truncated spell file");
+static char *e_illegal_character_in_word = N_("E1280: Illegal character in word");
static char *e_afftrailing = N_("Trailing text in %s line %d: %s");
static char *e_affname = N_("Affix name too long in %s line %d: %s");
static char *msg_compressing = N_("Compressing word tree...");
@@ -3927,6 +3928,11 @@ static int store_word(spellinfo_T *spin, char_u *word, int flags, int region, co
char_u foldword[MAXWLEN];
int res = OK;
+ // Avoid adding illegal bytes to the word tree.
+ if (!utf_valid_string(word, NULL)) {
+ return FAIL;
+ }
+
(void)spell_casefold(curwin, word, len, foldword, MAXWLEN);
for (const char_u *p = pfxlist; res == OK; p++) {
if (!need_affix || (p != NULL && *p != NUL)) {
@@ -5525,6 +5531,11 @@ void spell_add_word(char_u *word, int len, SpellAddType what, int idx, bool undo
int i;
char_u *spf;
+ if (!utf_valid_string(word, NULL)) {
+ emsg(_(e_illegal_character_in_word));
+ return;
+ }
+
if (idx == 0) { // use internal wordlist
if (int_wordlist == NULL) {
int_wordlist = vim_tempname();
diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c
index db0e6c8762..d82e337aa6 100644
--- a/src/nvim/syntax.c
+++ b/src/nvim/syntax.c
@@ -27,7 +27,7 @@
#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
#include "nvim/indent_c.h"
-#include "nvim/keymap.h"
+#include "nvim/keycodes.h"
#include "nvim/lua/executor.h"
#include "nvim/macros.h"
#include "nvim/mbyte.h"
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index f7d33de4fe..2d3102707c 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -56,7 +56,7 @@
#include "nvim/getchar.h"
#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
-#include "nvim/keymap.h"
+#include "nvim/keycodes.h"
#include "nvim/log.h"
#include "nvim/macros.h"
#include "nvim/main.h"
@@ -404,7 +404,7 @@ void terminal_enter(void)
// Disable these options in terminal-mode. They are nonsense because cursor is
// placed at end of buffer to "follow" output. #11072
- win_T *save_curwin = curwin;
+ handle_T save_curwin = curwin->handle;
bool save_w_p_cul = curwin->w_p_cul;
char_u *save_w_p_culopt = NULL;
char_u save_w_p_culopt_flags = curwin->w_p_culopt_flags;
@@ -442,7 +442,7 @@ void terminal_enter(void)
RedrawingDisabled = s->save_rd;
apply_autocmds(EVENT_TERMLEAVE, NULL, NULL, false, curbuf);
- if (save_curwin == curwin) { // save_curwin may be invalid (window closed)!
+ if (save_curwin == curwin->handle) { // Else: window was closed.
curwin->w_p_cul = save_w_p_cul;
if (save_w_p_culopt) {
xfree(curwin->w_p_culopt);
diff --git a/src/nvim/testdir/test_buffer.vim b/src/nvim/testdir/test_buffer.vim
index a31cdbb49a..9eb768f124 100644
--- a/src/nvim/testdir/test_buffer.vim
+++ b/src/nvim/testdir/test_buffer.vim
@@ -61,4 +61,15 @@ func Test_buffer_scheme()
set shellslash&
endfunc
+" this was using a NULL pointer after failing to use the pattern
+func Test_buf_pattern_invalid()
+ vsplit 0000000
+ silent! buf [0--]\&\zs*\zs*e
+ bwipe!
+
+ vsplit 00000000000000000000000000
+ silent! buf [0--]\&\zs*\zs*e
+ bwipe!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_matchfuzzy.vim b/src/nvim/testdir/test_matchfuzzy.vim
index d53f8b0f4d..533aec8d9a 100644
--- a/src/nvim/testdir/test_matchfuzzy.vim
+++ b/src/nvim/testdir/test_matchfuzzy.vim
@@ -135,6 +135,9 @@ func Test_matchfuzzypos()
" match multiple words (separated by space)
call assert_equal([['foo bar baz'], [[8, 9, 10, 0, 1, 2]], [369]], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('baz foo'))
+ call assert_equal([[], [], []], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('baz foo', {'matchseq': 1}))
+ call assert_equal([['foo bar baz'], [[0, 1, 2, 8, 9, 10]], [369]], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('foo baz'))
+ call assert_equal([['foo bar baz'], [[0, 1, 2, 3, 4, 5, 10]], [326]], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('foo baz', {'matchseq': 1}))
call assert_equal([[], [], []], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('one two'))
call assert_equal([[], [], []], ['foo bar']->matchfuzzypos(" \t "))
call assert_equal([['grace'], [[1, 2, 3, 4, 2, 3, 4, 0, 1, 2, 3, 4]], [657]], ['grace']->matchfuzzypos('race ace grace'))
diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim
index 56ed97cdd9..ce21b8bdc9 100644
--- a/src/nvim/testdir/test_spell.vim
+++ b/src/nvim/testdir/test_spell.vim
@@ -776,14 +776,6 @@ func Test_spell_screendump()
call delete('XtestSpell')
endfunc
-func Test_spell_single_word()
- new
- silent! norm 0R00
- spell! ßÂ
- silent 0norm 0r$ Dvz=
- bwipe!
-endfunc
-
let g:test_data_aff1 = [
\"SET ISO8859-1",
\"TRY esianrtolcdugmphbyfvkwjkqxz-\xEB\xE9\xE8\xEA\xEF\xEE\xE4\xE0\xE2\xF6\xFC\xFB'ESIANRTOLCDUGMPHBYFVKWJKQXZ",
diff --git a/src/nvim/testdir/test_spell_utf8.vim b/src/nvim/testdir/test_spell_utf8.vim
index 3d159f3352..1d323df67e 100644
--- a/src/nvim/testdir/test_spell_utf8.vim
+++ b/src/nvim/testdir/test_spell_utf8.vim
@@ -768,4 +768,10 @@ func Test_spellfile_value()
set spellfile=Xdir/Xtest.utf-8.add,Xtest_other.add
endfunc
+" Invalid bytes may cause trouble when creating the word list.
+func Test_check_for_valid_word()
+ call assert_fails("spellgood! 0\xac", 'E1280:')
+endfunc
+
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_undo.vim b/src/nvim/testdir/test_undo.vim
index 30e00e7ad4..848860649e 100644
--- a/src/nvim/testdir/test_undo.vim
+++ b/src/nvim/testdir/test_undo.vim
@@ -733,4 +733,21 @@ func Test_undofile_cryptmethod_blowfish2()
set undofile& undolevels& cryptmethod&
endfunc
+func Test_undo_mark()
+ new
+ " The undo is applied to the only line.
+ call setline(1, 'hello')
+ call feedkeys("ggyiw$p", 'xt')
+ undo
+ call assert_equal([0, 1, 1, 0], getpos("'["))
+ call assert_equal([0, 1, 1, 0], getpos("']"))
+ " The undo removes the last line.
+ call feedkeys("Goaaaa\<Esc>", 'xt')
+ call feedkeys("obbbb\<Esc>", 'xt')
+ undo
+ call assert_equal([0, 2, 1, 0], getpos("'["))
+ call assert_equal([0, 2, 1, 0], getpos("']"))
+ bwipe!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
index d2dc333855..529eef19a3 100644
--- a/src/nvim/undo.c
+++ b/src/nvim/undo.c
@@ -2418,10 +2418,11 @@ static void u_undoredo(int undo, bool do_buf_event)
changed_lines(top + 1, 0, bot, newsize - oldsize, do_buf_event);
- // set '[ and '] mark
+ // Set the '[ mark.
if (top + 1 < curbuf->b_op_start.lnum) {
curbuf->b_op_start.lnum = top + 1;
}
+ // Set the '] mark.
if (newsize == 0 && top + 1 > curbuf->b_op_end.lnum) {
curbuf->b_op_end.lnum = top + 1;
} else if (top + newsize > curbuf->b_op_end.lnum) {
@@ -2442,6 +2443,14 @@ static void u_undoredo(int undo, bool do_buf_event)
newlist = uep;
}
+ // Ensure the '[ and '] marks are within bounds.
+ if (curbuf->b_op_start.lnum > curbuf->b_ml.ml_line_count) {
+ curbuf->b_op_start.lnum = curbuf->b_ml.ml_line_count;
+ }
+ if (curbuf->b_op_end.lnum > curbuf->b_ml.ml_line_count) {
+ curbuf->b_op_end.lnum = curbuf->b_ml.ml_line_count;
+ }
+
// Adjust Extmarks
ExtmarkUndoObject undo_info;
if (undo) {
diff --git a/src/nvim/vim.h b/src/nvim/vim.h
index 4e269bc9d4..1c05387da3 100644
--- a/src/nvim/vim.h
+++ b/src/nvim/vim.h
@@ -31,7 +31,7 @@ enum { NUMBUFLEN = 65, };
#define ROOT_UID 0
#include "nvim/gettext.h"
-#include "nvim/keymap.h"
+#include "nvim/keycodes.h"
#include "nvim/macros.h"
// special attribute addition: Put message in history