diff options
Diffstat (limited to 'src')
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 |