diff options
author | ZyX <kp-pav@yandex.ru> | 2017-07-31 02:05:02 +0300 |
---|---|---|
committer | ZyX <kp-pav@yandex.ru> | 2017-07-31 02:05:02 +0300 |
commit | fbe60af538aa6c723779ae7a816de845460619ae (patch) | |
tree | b541ffeeaa5046de170cac688c72aeb891c61ebb /src | |
parent | 1011462b40502e6039494e70a870f0360f152b1b (diff) | |
parent | 13e8356f52d9dc1da96179ee425168740874c8c7 (diff) | |
download | rneovim-fbe60af538aa6c723779ae7a816de845460619ae.tar.gz rneovim-fbe60af538aa6c723779ae7a816de845460619ae.tar.bz2 rneovim-fbe60af538aa6c723779ae7a816de845460619ae.zip |
Merge branch 'master' into colored-cmdline
Diffstat (limited to 'src')
46 files changed, 1923 insertions, 1067 deletions
diff --git a/src/nvim/README.md b/src/nvim/README.md index 3032913500..1c1c3c364e 100644 --- a/src/nvim/README.md +++ b/src/nvim/README.md @@ -1,14 +1,26 @@ -## Source code overview +Nvim core source +================ -This document is an overview of how Nvim works internally, focusing on parts -that are different from Vim. Since Nvim inherited from Vim, some information in -[its README](https://raw.githubusercontent.com/vim/vim/master/src/README.txt) -still applies. +Module-specific details are documented at the top of each module (`terminal.c`, +`screen.c`, ...). -For module-specific details, read the source code. Some files are extensively -commented at the top (e.g. terminal.c, screen.c). +See `:help development` for more guidelines. -### Source file name conventions +Logs +---- + +Low-level log messages sink to `$NVIM_LOG_FILE`. + +You can use `LOG_CALLSTACK()` anywhere in the source to log the current +stacktrace. (Currently Linux-only.) + +UI events are logged at level 0 (`DEBUG_LOG_LEVEL`). + + rm -rf build/ + make CMAKE_EXTRA_FLAGS="-DMIN_LOG_LEVEL=0" + +Filename conventions +-------------------- The source files use extensions to hint about their purpose. @@ -19,10 +31,12 @@ The source files use extensions to hint about their purpose. - `*.h.generated.h` - exported functions’ declarations. - `*.c.generated.h` - static functions’ declarations. -### Top-level program loops +Nvim lifecycle +-------------- + +Following describes how Nvim processes input. -Let's understand what a Vim-like program does by analyzing the workflow of -a typical editing session: +Consider a typical Vim-like editing session: 01. Vim dispays the welcome screen 02. User types: `:` @@ -154,7 +168,8 @@ modes managed by the `state_enter` loop: - insert mode: `insert_{enter,check,execute}()`(`edit.c`) - terminal mode: `terminal_{enter,execute}()`(`terminal.c`) -### Async event support +Async event support +------------------- One of the features Nvim added is the support for handling arbitrary asynchronous events, which can include: diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 90a564bb6a..724a8578ac 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -1203,8 +1203,8 @@ do_buffer ( */ while (buf == curbuf && !(curwin->w_closing || curwin->w_buffer->b_locked > 0) - && (firstwin != lastwin || first_tabpage->tp_next != NULL)) { - if (win_close(curwin, FALSE) == FAIL) + && (!ONE_WINDOW || first_tabpage->tp_next != NULL)) { + if (win_close(curwin, false) == FAIL) break; } @@ -4428,15 +4428,17 @@ do_arg_all ( continue; } } - /* don't close last window */ - if (firstwin == lastwin - && (first_tabpage->tp_next == NULL || !had_tab)) - use_firstwin = TRUE; - else { + // don't close last window + if (ONE_WINDOW + && (first_tabpage->tp_next == NULL || !had_tab)) { + use_firstwin = true; + } else { win_close(wp, !P_HID(buf) && !bufIsChanged(buf)); - /* check if autocommands removed the next window */ - if (!win_valid(wpnext)) - wpnext = firstwin; /* start all over... */ + // check if autocommands removed the next window + if (!win_valid(wpnext)) { + // start all over... + wpnext = firstwin; + } } } } @@ -4593,7 +4595,7 @@ void ex_buffer_all(exarg_T *eap) - tabline_height() : wp->w_width != Columns) || (had_tab > 0 && wp != firstwin)) - && firstwin != lastwin + && !ONE_WINDOW && !(wp->w_closing || wp->w_buffer->b_locked > 0) ) { win_close(wp, FALSE); diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c index 32e71f61f7..bfb1b94738 100644 --- a/src/nvim/digraph.c +++ b/src/nvim/digraph.c @@ -793,6 +793,7 @@ static digr_T digraphdefault[] = { '/', '-', 0x2020 }, { '/', '=', 0x2021 }, { '.', '.', 0x2025 }, + { ',', '.', 0x2026 }, { '%', '0', 0x2030 }, { '1', '\'', 0x2032 }, { '2', '\'', 0x2033 }, diff --git a/src/nvim/eval.c b/src/nvim/eval.c index afa6711645..40847fb806 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -47,6 +47,7 @@ #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" +#include "nvim/menu.h" #include "nvim/message.h" #include "nvim/misc1.h" #include "nvim/keymap.h" @@ -4239,11 +4240,17 @@ static int eval7( // use its contents. s = deref_func_name((const char *)s, &len, &partial, !evaluate); + // Need to make a copy, in case evaluating the arguments makes + // the name invalid. + s = xmemdupz(s, len); + // Invoke the function. ret = get_func_tv(s, len, rettv, arg, curwin->w_cursor.lnum, curwin->w_cursor.lnum, &len, evaluate, partial, NULL); + xfree(s); + // If evaluate is false rettv->v_type was not set in // get_func_tv, but it's needed in handle_subscript() to parse // what follows. So set it here. @@ -8167,6 +8174,19 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } + +/// "menu_get(path [, modes])" function +static void f_menu_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tv_list_alloc_ret(rettv); + int modes = MENU_ALL_MODES; + if (argvars[1].v_type == VAR_STRING) { + const char_u *const strmodes = (char_u *)tv_get_string(&argvars[1]); + modes = get_menu_cmd_modes(strmodes, false, NULL, NULL); + } + menu_get((char_u *)tv_get_string(&argvars[0]), modes, rettv->vval.v_list); +} + /* * "extend(list, list [, idx])" function * "extend(dict, dict [, action])" function @@ -15140,7 +15160,8 @@ static void f_sockconnect(typval_T *argvars, typval_T *rettv, FunPtr fptr) } const char *error = NULL; - uint64_t id = channel_connect(tcp, address, 50, &error); + eval_format_source_name_line((char *)IObuff, sizeof(IObuff)); + uint64_t id = channel_connect(tcp, address, 50, (char *)IObuff, &error); if (error) { EMSG2(_("connection failed: %s"), error); @@ -22448,8 +22469,9 @@ static inline bool common_job_start(TerminalJobData *data, typval_T *rettv) if (data->rpc) { - // the rpc channel takes over the in and out streams - channel_from_process(proc, data->id); + eval_format_source_name_line((char *)IObuff, sizeof(IObuff)); + // RPC channel takes over the in/out streams. + channel_from_process(proc, data->id, (char *)IObuff); } else { wstream_init(proc->in, 0); if (proc->out) { @@ -22774,3 +22796,11 @@ bool eval_has_provider(const char *name) return false; } + +/// Writes "<sourcing_name>:<sourcing_lnum>" to `buf[bufsize]`. +void eval_format_source_name_line(char *buf, size_t bufsize) +{ + snprintf(buf, bufsize, "%s:%" PRIdLINENR, + (sourcing_name ? sourcing_name : (char_u *)"?"), + (sourcing_name ? sourcing_lnum : 0)); +} diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 30766a0734..0e359fb61c 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -2,10 +2,10 @@ -- -- Keys: -- --- args Number of arguments, list with maximum and minimum number of arguments --- or list with a minimum number of arguments only. Defaults to zero +-- args Number of arguments, list with maximum and minimum number of arguments +-- or list with a minimum number of arguments only. Defaults to zero -- arguments. --- func Name of the C function which implements the VimL function. Defaults to +-- func Name of the C function which implements the VimL function. Defaults to -- `f_{funcname}`. local varargs = function(nr) @@ -29,7 +29,7 @@ return { assert_exception={args={1, 2}}, assert_fails={args={1, 2}}, assert_false={args={1, 2}}, - assert_inrange={args={2, 3}}, + assert_inrange={args={3, 4}}, assert_match={args={2, 3}}, assert_notequal={args={2, 3}}, assert_notmatch={args={2, 3}}, @@ -208,6 +208,7 @@ return { matchstr={args={2, 4}}, matchstrpos={args={2,4}}, max={args=1}, + menu_get={args={1, 2}}, min={args=1}, mkdir={args={1, 3}}, mode={args={0, 1}}, diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 0987cb3915..a555fb77e8 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -2802,16 +2802,18 @@ void ex_z(exarg_T *eap) int j; linenr_T lnum = eap->line2; - /* Vi compatible: ":z!" uses display height, without a count uses - * 'scroll' */ - if (eap->forceit) + // Vi compatible: ":z!" uses display height, without a count uses + // 'scroll' + if (eap->forceit) { bigness = curwin->w_height; - else if (firstwin == lastwin) + } else if (ONE_WINDOW) { bigness = curwin->w_p_scr * 2; - else + } else { bigness = curwin->w_height - 3; - if (bigness < 1) + } + if (bigness < 1) { bigness = 1; + } x = eap->arg; kind = x; diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index 7203fbd97d..46f7a7bc40 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -1,4 +1,4 @@ -bit = require 'bit' +local bit = require 'bit' -- Description of the values below is contained in ex_cmds_defs.h file. local RANGE = 0x001 diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 5d7246581c..d7821fc636 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -844,8 +844,6 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, break; case ET_INTERRUPT: break; - default: - p = vim_strsave((char_u *)_(e_internal)); } saved_sourcing_name = sourcing_name; @@ -3440,6 +3438,11 @@ const char * set_one_cmd_context( xp->xp_pattern = (char_u *)arg; break; + case CMD_messages: + xp->xp_context = EXPAND_MESSAGES; + xp->xp_pattern = (char_u *)arg; + break; + case CMD_history: xp->xp_context = EXPAND_HISTORY; xp->xp_pattern = (char_u *)arg; @@ -4874,6 +4877,7 @@ static struct { #endif { EXPAND_MAPPINGS, "mapping" }, { EXPAND_MENUS, "menu" }, + { EXPAND_MESSAGES, "messages" }, { EXPAND_OWNSYNTAX, "syntax" }, { EXPAND_SYNTIME, "syntime" }, { EXPAND_SETTINGS, "option" }, @@ -5976,7 +5980,7 @@ static void ex_quit(exarg_T *eap) // specified. Example: // :h|wincmd w|1q - don't quit // :h|wincmd w|q - quit - if (only_one_window() && (firstwin == lastwin || eap->addr_count == 0)) { + if (only_one_window() && (ONE_WINDOW || eap->addr_count == 0)) { getout(0); } /* close window; may free buffer */ @@ -6174,12 +6178,14 @@ static void ex_tabonly(exarg_T *eap) */ void tabpage_close(int forceit) { - /* First close all the windows but the current one. If that worked then - * close the last window in this tab, that will close it. */ - if (lastwin != firstwin) - close_others(TRUE, forceit); - if (lastwin == firstwin) + // First close all the windows but the current one. If that worked then + // close the last window in this tab, that will close it. + if (!ONE_WINDOW) { + close_others(true, forceit); + } + if (ONE_WINDOW) { ex_win_close(forceit, curwin, NULL); + } } /* @@ -9593,6 +9599,16 @@ char_u *get_behave_arg(expand_T *xp, int idx) return NULL; } +// Function given to ExpandGeneric() to obtain the possible arguments of the +// ":messages {clear}" command. +char_u *get_messages_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) +{ + if (idx == 0) { + return (char_u *)"clear"; + } + return NULL; +} + static TriState filetype_detect = kNone; static TriState filetype_plugin = kNone; static TriState filetype_indent = kNone; diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c index 4434dbe1a5..9037b3c151 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -374,10 +374,9 @@ int do_intthrow(struct condstack *cstack) return TRUE; } -/* - * Get an exception message that is to be stored in current_exception->value. - */ -char_u *get_exception_string(void *value, int type, char_u *cmdname, int *should_free) +// Get an exception message that is to be stored in current_exception->value. +char_u *get_exception_string(void *value, except_type_T type, char_u *cmdname, + int *should_free) { char_u *ret, *mesg; char_u *p, *val; @@ -435,13 +434,11 @@ char_u *get_exception_string(void *value, int type, char_u *cmdname, int *should } -/* - * Throw a new exception. Return FAIL when out of memory or it was tried to - * throw an illegal user exception. "value" is the exception string for a - * user or interrupt exception, or points to a message list in case of an - * error exception. - */ -static int throw_exception(void *value, int type, char_u *cmdname) +// Throw a new exception. Return FAIL when out of memory or it was tried to +// throw an illegal user exception. "value" is the exception string for a +// user or interrupt exception, or points to a message list in case of an +// error exception. +static int throw_exception(void *value, except_type_T type, char_u *cmdname) { except_T *excp; int should_free; diff --git a/src/nvim/ex_eval.h b/src/nvim/ex_eval.h index f61e01d25b..d5f8737bf3 100644 --- a/src/nvim/ex_eval.h +++ b/src/nvim/ex_eval.h @@ -89,28 +89,29 @@ struct msglist { struct msglist *next; /* next of several messages in a row */ }; +// The exception types. +typedef enum +{ + ET_USER, // exception caused by ":throw" command + ET_ERROR, // error exception + ET_INTERRUPT // interrupt exception triggered by Ctrl-C +} except_type_T; + /* * Structure describing an exception. * (don't use "struct exception", it's used by the math library). */ typedef struct vim_exception except_T; struct vim_exception { - int type; /* exception type */ - char_u *value; /* exception value */ - struct msglist *messages; /* message(s) causing error exception */ - char_u *throw_name; /* name of the throw point */ - linenr_T throw_lnum; /* line number of the throw point */ - except_T *caught; /* next exception on the caught stack */ + except_type_T type; // exception type + char_u *value; // exception value + struct msglist *messages; // message(s) causing error exception + char_u *throw_name; // name of the throw point + linenr_T throw_lnum; // line number of the throw point + except_T *caught; // next exception on the caught stack }; /* - * The exception types. - */ -#define ET_USER 0 /* exception caused by ":throw" command */ -#define ET_ERROR 1 /* error exception */ -#define ET_INTERRUPT 2 /* interrupt exception triggered by Ctrl-C */ - -/* * Structure to save the error/interrupt/exception state between calls to * enter_cleanup() and leave_cleanup(). Must be allocated as an automatic * variable by the (common) caller of these functions. diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 6eb975fea3..4bbcf186ed 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -4377,6 +4377,7 @@ ExpandFromContext ( } tab[] = { { EXPAND_COMMANDS, get_command_name, false, true }, { EXPAND_BEHAVE, get_behave_arg, true, true }, + { EXPAND_MESSAGES, get_messages_arg, true, true }, { EXPAND_HISTORY, get_history_arg, true, true }, { EXPAND_USER_COMMANDS, get_user_commands, false, true }, { EXPAND_USER_ADDR_TYPE, get_user_cmd_addr_type, false, true }, diff --git a/src/nvim/generators/gen_api_ui_events.lua b/src/nvim/generators/gen_api_ui_events.lua index acdb25ca67..d2b90db707 100644 --- a/src/nvim/generators/gen_api_ui_events.lua +++ b/src/nvim/generators/gen_api_ui_events.lua @@ -119,7 +119,7 @@ for i = 1, #events do write_signature(bridge_output, ev, 'UI *ui') bridge_output:write('\n{\n') bridge_output:write(send) - bridge_output:write(' UI_CALL(ui, '..ev.name..', '..argc..', ui'..argv..');\n}\n') + bridge_output:write(' UI_BRIDGE_CALL(ui, '..ev.name..', '..argc..', ui'..argv..');\n}\n') end end @@ -128,6 +128,7 @@ for i = 1, #events do call_output:write('\n{\n') if ev.remote_only then write_arglist(call_output, ev, false) + call_output:write(' UI_LOG('..ev.name..', 0);\n') call_output:write(' ui_event("'..ev.name..'", args);\n') else call_output:write(' UI_CALL') diff --git a/src/nvim/getchar.h b/src/nvim/getchar.h index 28584e0534..e634273e0d 100644 --- a/src/nvim/getchar.h +++ b/src/nvim/getchar.h @@ -5,12 +5,15 @@ #include "nvim/buffer_defs.h" #include "nvim/ex_cmds_defs.h" -/* Values for "noremap" argument of ins_typebuf(). Also used for - * map->m_noremap and menu->noremap[]. */ -#define REMAP_YES 0 /* allow remapping */ -#define REMAP_NONE -1 /* no remapping */ -#define REMAP_SCRIPT -2 /* remap script-local mappings only */ -#define REMAP_SKIP -3 /* no remapping for first char */ +/// Values for "noremap" argument of ins_typebuf(). Also used for +/// map->m_noremap and menu->noremap[]. +/// @addtogroup REMAP_VALUES +/// @{ +#define REMAP_YES 0 ///< allow remapping +#define REMAP_NONE -1 ///< no remapping +#define REMAP_SCRIPT -2 ///< remap script-local mappings only +#define REMAP_SKIP -3 ///< no remapping for first char +/// @} #define KEYLEN_PART_KEY -1 /* keylen value for incomplete key-code */ #define KEYLEN_PART_MAP -2 /* keylen value for incomplete mapping */ diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 6d1bd1de12..f08812600f 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -494,6 +494,7 @@ EXTERN int updating_screen INIT(= FALSE); EXTERN win_T *firstwin; /* first window */ EXTERN win_T *lastwin; /* last window */ EXTERN win_T *prevwin INIT(= NULL); /* previous window */ +# define ONE_WINDOW (firstwin == lastwin) /* * When using this macro "break" only breaks out of the inner loop. Use "goto" * to break out of the tabpage loop. diff --git a/src/nvim/log.c b/src/nvim/log.c index f1dbe61dda..b64aef3cac 100644 --- a/src/nvim/log.c +++ b/src/nvim/log.c @@ -25,6 +25,10 @@ static uv_mutex_t mutex; # include "log.c.generated.h" #endif +#ifdef HAVE_EXECINFO_BACKTRACE +# include <execinfo.h> +#endif + static bool log_try_create(char *fname) { if (fname == NULL || fname[0] == '\0') { @@ -173,6 +177,52 @@ FILE *open_log_file(void) return stderr; } +#ifdef HAVE_EXECINFO_BACKTRACE +void log_callstack(const char *const func_name, const int line_num) +{ + void *trace[100]; + int trace_size = backtrace(trace, ARRAY_SIZE(trace)); + + char exepath[MAXPATHL] = { 0 }; + size_t exepathlen = MAXPATHL; + if (os_exepath(exepath, &exepathlen) != 0) { + abort(); + } + assert(24 + exepathlen < IOSIZE); // Must fit in `cmdbuf` below. + + do_log(DEBUG_LOG_LEVEL, func_name, line_num, true, "trace:"); + + char cmdbuf[IOSIZE + (20 * ARRAY_SIZE(trace))]; + snprintf(cmdbuf, sizeof(cmdbuf), "addr2line -e %s -f -p", exepath); + for (int i = 1; i < trace_size; i++) { + char buf[20]; // 64-bit pointer 0xNNNNNNNNNNNNNNNN with leading space. + snprintf(buf, sizeof(buf), " %p", trace[i]); + xstrlcat(cmdbuf, buf, sizeof(cmdbuf)); + } + // Now we have a command string like: + // addr2line -e /path/to/exe -f -p 0x123 0x456 ... + + log_lock(); + FILE *log_file = open_log_file(); + if (log_file == NULL) { + goto end; + } + + FILE *fp = popen(cmdbuf, "r"); + char linebuf[IOSIZE]; + while (fgets(linebuf, sizeof(linebuf) - 1, fp) != NULL) { + fprintf(log_file, " %s", linebuf); + } + pclose(fp); + + if (log_file != stderr && log_file != stdout) { + fclose(log_file); + } +end: + log_unlock(); +} +#endif + static bool do_log_to_file(FILE *log_file, int log_level, const char *func_name, int line_num, bool eol, const char* fmt, ...) diff --git a/src/nvim/log.h b/src/nvim/log.h index 221f0bbaf6..743a8d17aa 100644 --- a/src/nvim/log.h +++ b/src/nvim/log.h @@ -61,6 +61,10 @@ __VA_ARGS__) #endif +#ifdef HAVE_EXECINFO_BACKTRACE +# define LOG_CALLSTACK() log_callstack(__func__, __LINE__) +#endif + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "log.h.generated.h" #endif diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index 4aee1c8e68..b24770a409 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -932,12 +932,12 @@ int utf_char2len(int c) return 6; } -/* - * Convert Unicode character "c" to UTF-8 string in "buf[]". - * Returns the number of bytes. - * This does not include composing characters. - */ -int utf_char2bytes(int c, char_u *buf) +/// Convert Unicode character to UTF-8 string +/// +/// @param c character to convert to \p buf +/// @param[out] buf UTF-8 string generated from \p c, does not add \0 +/// @return Number of bytes (1-6). Does not include composing characters. +int utf_char2bytes(int c, char_u *const buf) { if (c < 0x80) { /* 7 bits */ buf[0] = c; diff --git a/src/nvim/memory.c b/src/nvim/memory.c index 74c58fb203..328b96fd5c 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -585,7 +585,7 @@ void free_all_mem(void) p_ea = false; if (first_tabpage->tp_next != NULL) do_cmdline_cmd("tabonly!"); - if (firstwin != lastwin) + if (!ONE_WINDOW) do_cmdline_cmd("only!"); /* Free all spell info. */ diff --git a/src/nvim/menu.c b/src/nvim/menu.c index c8e6012e5c..a498916e5e 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -26,7 +26,7 @@ #include "nvim/state.h" #include "nvim/strings.h" #include "nvim/ui.h" - +#include "nvim/eval/typval.h" #define MENUDEPTH 10 /* maximum depth of menus */ @@ -38,8 +38,8 @@ -/* The character for each menu mode */ -static char_u menu_mode_chars[] = {'n', 'v', 's', 'o', 'i', 'c', 't'}; +/// The character for each menu mode +static char_u menu_mode_chars[] = { 'n', 'v', 's', 'o', 'i', 'c', 't' }; static char_u e_notsubmenu[] = N_( "E327: Part of menu-item path is not sub-menu"); @@ -47,17 +47,14 @@ static char_u e_othermode[] = N_("E328: Menu only exists in another mode"); static char_u e_nomenu[] = N_("E329: No menu \"%s\""); -/* - * Do the :menu command and relatives. - */ -void -ex_menu ( - exarg_T *eap /* Ex command arguments */ -) +/// Do the :menu command and relatives. +/// @param eap Ex command arguments +void +ex_menu(exarg_T *eap) { char_u *menu_path; int modes; - char_u *map_to; + char_u *map_to; // command mapped to the menu entry int noremap; bool silent = false; int unmenu; @@ -93,7 +90,8 @@ ex_menu ( } - /* Locate an optional "icon=filename" argument. */ + // Locate an optional "icon=filename" argument + // TODO(nvim): Currently this is only parsed. Should expose it to UIs. if (STRNCMP(arg, "icon=", 5) == 0) { arg += 5; while (*arg != NUL && *arg != ' ') { @@ -107,12 +105,12 @@ ex_menu ( } } - /* - * Fill in the priority table. - */ - for (p = arg; *p; ++p) - if (!ascii_isdigit(*p) && *p != '.') + // Fill in the priority table. + for (p = arg; *p; p++) { + if (!ascii_isdigit(*p) && *p != '.') { break; + } + } if (ascii_iswhite(*p)) { for (i = 0; i < MENUDEPTH && !ascii_iswhite(*arg); ++i) { pri_tab[i] = getdigits_long(&arg); @@ -226,8 +224,7 @@ ex_menu ( menuarg.modes = modes; menuarg.noremap[0] = noremap; menuarg.silent[0] = silent; - add_menu_path(menu_path, &menuarg, pri_tab, map_to - ); + add_menu_path(menu_path, &menuarg, pri_tab, map_to); /* * For the PopUp menu, add a menu for each mode separately. @@ -252,16 +249,18 @@ theend: ; } -/* - * Add the menu with the given name to the menu hierarchy - */ -static int -add_menu_path ( - char_u *menu_path, - vimmenu_T *menuarg, /* passes modes, iconfile, iconidx, - icon_builtin, silent[0], noremap[0] */ - long *pri_tab, - char_u *call_data + +/// Add the menu with the given name to the menu hierarchy +/// +/// @param[out] menuarg menu entry +/// @param[] pri_tab priority table +/// @param[in] call_data Right hand side command +static int +add_menu_path( + const char_u *const menu_path, + vimmenu_T *menuarg, + const long *const pri_tab, + const char_u *const call_data ) { char_u *path_name; @@ -296,8 +295,9 @@ add_menu_path ( if (map_to != NULL) { en_name = name; name = map_to; - } else + } else { en_name = NULL; + } dname = menu_text(name, NULL, NULL); if (*dname == NUL) { /* Only a mnemonic or accelerator is not valid. */ @@ -311,14 +311,15 @@ add_menu_path ( while (menu != NULL) { if (menu_name_equal(name, menu) || menu_name_equal(dname, menu)) { if (*next_name == NUL && menu->children != NULL) { - if (!sys_menu) + if (!sys_menu) { EMSG(_("E330: Menu path must not lead to a sub-menu")); + } goto erret; } - if (*next_name != NUL && menu->children == NULL - ) { - if (!sys_menu) + if (*next_name != NUL && menu->children == NULL) { + if (!sys_menu) { EMSG(_(e_notsubmenu)); + } goto erret; } break; @@ -352,7 +353,7 @@ add_menu_path ( menu->modes = modes; menu->enabled = MENU_ALL_MODES; menu->name = vim_strsave(name); - /* separate mnemonic and accelerator text from actual menu name */ + // separate mnemonic and accelerator text from actual menu name menu->dname = menu_text(name, &menu->mnemonic, &menu->actext); if (en_name != NULL) { menu->en_name = vim_strsave(en_name); @@ -364,9 +365,7 @@ add_menu_path ( menu->priority = pri_tab[pri_idx]; menu->parent = parent; - /* - * Add after menu that has lower priority. - */ + // Add after menu that has lower priority. menu->next = *lower_pri; *lower_pri = menu; @@ -392,8 +391,9 @@ add_menu_path ( name = next_name; xfree(dname); dname = NULL; - if (pri_tab[pri_idx + 1] != -1) - ++pri_idx; + if (pri_tab[pri_idx + 1] != -1) { + pri_idx++; + } } xfree(path_name); @@ -419,8 +419,7 @@ add_menu_path ( // Don't do this for "<Nop>". c = 0; d = 0; - if (amenu && call_data != NULL && *call_data != NUL - ) { + if (amenu && call_data != NULL && *call_data != NUL) { switch (1 << i) { case MENU_VISUAL_MODE: case MENU_SELECT_MODE: @@ -438,9 +437,9 @@ add_menu_path ( if (c != 0) { menu->strings[i] = xmalloc(STRLEN(call_data) + 5 ); menu->strings[i][0] = c; - if (d == 0) + if (d == 0) { STRCPY(menu->strings[i] + 1, call_data); - else { + } else { menu->strings[i][1] = d; STRCPY(menu->strings[i] + 2, call_data); } @@ -452,8 +451,9 @@ add_menu_path ( menu->strings[i][len + 1] = Ctrl_G; menu->strings[i][len + 2] = NUL; } - } else + } else { menu->strings[i] = p; + } menu->noremap[i] = menuarg->noremap[0]; menu->silent[i] = menuarg->silent[0]; } @@ -657,20 +657,109 @@ static void free_menu_string(vimmenu_T *menu, int idx) menu->strings[idx] = NULL; } -/* - * Show the mapping associated with a menu item or hierarchy in a sub-menu. - */ -static int show_menus(char_u *path_name, int modes) +/// Export menus +/// +/// @param[in] menu if null, starts from root_menu +/// @param modes, a choice of \ref MENU_MODES +/// @return a dict with name/commands +/// @see menu_get +static dict_T *menu_get_recursive(const vimmenu_T *menu, int modes) +{ + dict_T *dict; + char buf[sizeof(menu->mnemonic)]; + int mnemonic_len; + + if (!menu || (menu->modes & modes) == 0x0) { + return NULL; + } + + dict = tv_dict_alloc(); + tv_dict_add_str(dict, S_LEN("name"), (char *)menu->dname); + tv_dict_add_nr(dict, S_LEN("priority"), (int)menu->priority); + tv_dict_add_nr(dict, S_LEN("hidden"), menu_is_hidden(menu->dname)); + + if (menu->mnemonic) { + mnemonic_len = utf_char2bytes(menu->mnemonic, (u_char *)buf); + buf[mnemonic_len] = '\0'; + tv_dict_add_str(dict, S_LEN("shortcut"), buf); + } + + if (menu->modes & MENU_TIP_MODE && menu->strings[MENU_INDEX_TIP]) { + tv_dict_add_str(dict, S_LEN("tooltip"), + (char *)menu->strings[MENU_INDEX_TIP]); + } + + if (!menu->children) { + // leaf menu + dict_T *commands = tv_dict_alloc(); + tv_dict_add_dict(dict, S_LEN("mappings"), commands); + + for (int bit = 0; bit < MENU_MODES; bit++) { + if ((menu->modes & modes & (1 << bit)) != 0) { + dict_T *impl = tv_dict_alloc(); + if (*menu->strings[bit] == NUL) { + tv_dict_add_str(impl, S_LEN("rhs"), (char *)"<Nop>"); + } else { + tv_dict_add_str(impl, S_LEN("rhs"), (char *)menu->strings[bit]); + } + tv_dict_add_nr(impl, S_LEN("silent"), menu->silent[bit]); + tv_dict_add_nr(impl, S_LEN("enabled"), + (menu->enabled & (1 << bit)) ? 1 : 0); + tv_dict_add_nr(impl, S_LEN("noremap"), + (menu->noremap[bit] & REMAP_NONE) ? 1 : 0); + tv_dict_add_nr(impl, S_LEN("sid"), + (menu->noremap[bit] & REMAP_SCRIPT) ? 1 : 0); + tv_dict_add_dict(commands, (char *)&menu_mode_chars[bit], 1, impl); + } + } + } else { + // visit recursively all children + list_T *children_list = tv_list_alloc(); + for (menu = menu->children; menu != NULL; menu = menu->next) { + dict_T *dic = menu_get_recursive(menu, modes); + if (dict && tv_dict_len(dict) > 0) { + tv_list_append_dict(children_list, dic); + } + } + tv_dict_add_list(dict, S_LEN("submenus"), children_list); + } + return dict; +} + + +/// Export menus matching path \p path_name +/// +/// @param path_name +/// @param modes supported modes, see \ref MENU_MODES +/// @param[in,out] list must be allocated +/// @return false if could not find path_name +bool menu_get(char_u *const path_name, int modes, list_T *list) { - char_u *p; - char_u *name; vimmenu_T *menu; - vimmenu_T *parent = NULL; + menu = find_menu(root_menu, path_name, modes); + if (!menu) { + return false; + } + for (; menu != NULL; menu = menu->next) { + dict_T *dict = menu_get_recursive(menu, modes); + if (dict && tv_dict_len(dict) > 0) { + tv_list_append_dict(list, dict); + } + } + return true; +} - menu = root_menu; - name = path_name = vim_strsave(path_name); - /* First, find the (sub)menu with the given name */ +/// Find menu matching required name and modes +/// +/// @param menu top menu to start looking from +/// @param name path towards the menu +/// @return menu if \p name is null, found menu or NULL +vimmenu_T * +find_menu(vimmenu_T *menu, char_u * name, int modes) +{ + char_u *p; + while (*name) { p = menu_name_skip(name); while (menu != NULL) { @@ -678,39 +767,46 @@ static int show_menus(char_u *path_name, int modes) /* Found menu */ if (*p != NUL && menu->children == NULL) { EMSG(_(e_notsubmenu)); - xfree(path_name); - return FAIL; + return NULL; } else if ((menu->modes & modes) == 0x0) { EMSG(_(e_othermode)); - xfree(path_name); - return FAIL; + return NULL; } break; } menu = menu->next; } + if (menu == NULL) { EMSG2(_(e_nomenu), name); - xfree(path_name); - return FAIL; + return NULL; } name = p; - parent = menu; menu = menu->children; } - xfree(path_name); + return menu; +} + +/// Show the mapping associated with a menu item or hierarchy in a sub-menu. +static int show_menus(char_u *const path_name, int modes) +{ + vimmenu_T *menu; + + // First, find the (sub)menu with the given name + menu = find_menu(root_menu, path_name, modes); + if (!menu) { + return FAIL; + } /* Now we have found the matching menu, and we list the mappings */ /* Highlight title */ MSG_PUTS_TITLE(_("\n--- Menus ---")); - show_menus_recursive(parent, modes, 0); + show_menus_recursive(menu->parent, modes, 0); return OK; } -/* - * Recursively show the mappings associated with the menus under the given one - */ +/// Recursively show the mappings associated with the menus under the given one static void show_menus_recursive(vimmenu_T *menu, int modes, int depth) { int i; @@ -993,12 +1089,13 @@ char_u *get_menu_names(expand_T *xp, int idx) return str; } -/* - * Skip over this element of the menu path and return the start of the next - * element. Any \ and ^Vs are removed from the current element. - * "name" may be modified. - */ -char_u *menu_name_skip(char_u *name) + +/// Skip over this element of the menu path and return the start of the next +/// element. Any \ and ^Vs are removed from the current element. +/// +/// @param name may be modified. +/// @return start of the next element +char_u *menu_name_skip(char_u *const name) { char_u *p; @@ -1018,16 +1115,16 @@ char_u *menu_name_skip(char_u *name) * Return TRUE when "name" matches with menu "menu". The name is compared in * two ways: raw menu name and menu name without '&'. ignore part after a TAB. */ -static int menu_name_equal(char_u *name, vimmenu_T *menu) +static bool menu_name_equal(const char_u *const name, vimmenu_T *const menu) { if (menu->en_name != NULL && (menu_namecmp(name, menu->en_name) || menu_namecmp(name, menu->en_dname))) - return TRUE; + return true; return menu_namecmp(name, menu->name) || menu_namecmp(name, menu->dname); } -static int menu_namecmp(char_u *name, char_u *mname) +static bool menu_namecmp(const char_u *const name, const char_u *const mname) { int i; @@ -1038,18 +1135,20 @@ static int menu_namecmp(char_u *name, char_u *mname) && (mname[i] == NUL || mname[i] == TAB); } -/* - * Return the modes specified by the given menu command (eg :menu! returns - * MENU_CMDLINE_MODE | MENU_INSERT_MODE). - * If "noremap" is not NULL, then the flag it points to is set according to - * whether the command is a "nore" command. - * If "unmenu" is not NULL, then the flag it points to is set according to - * whether the command is an "unmenu" command. - */ -static int -get_menu_cmd_modes ( - char_u *cmd, - int forceit, /* Was there a "!" after the command? */ + +/// Returns the \ref MENU_MODES specified by menu command `cmd`. +/// (eg :menu! returns MENU_CMDLINE_MODE | MENU_INSERT_MODE) +/// +/// @param[in] cmd string like "nmenu", "vmenu", etc. +/// @param[in] forceit bang (!) was given after the command +/// @param[out] noremap If not NULL, the flag it points to is set according +/// to whether the command is a "nore" command. +/// @param[out] unmenu If not NULL, the flag it points to is set according +/// to whether the command is an "unmenu" command. +int +get_menu_cmd_modes( + const char_u * cmd, + bool forceit, int *noremap, int *unmenu ) @@ -1090,12 +1189,15 @@ get_menu_cmd_modes ( } /* FALLTHROUGH */ default: - --cmd; - if (forceit) /* menu!! */ + cmd--; + if (forceit) { + // menu!! modes = MENU_INSERT_MODE | MENU_CMDLINE_MODE; - else /* menu */ + } else { + // menu modes = MENU_NORMAL_MODE | MENU_VISUAL_MODE | MENU_SELECT_MODE | MENU_OP_PENDING_MODE; + } } if (noremap != NULL) @@ -1201,12 +1303,14 @@ int menu_is_separator(char_u *name) return name[0] == '-' && name[STRLEN(name) - 1] == '-'; } -/* - * Return TRUE if the menu is hidden: Starts with ']' - */ + +/// True if a popup menu or starts with \ref MNU_HIDDEN_CHAR +/// +/// @return true if the menu is hidden static int menu_is_hidden(char_u *name) { - return (name[0] == ']') || (menu_is_popup(name) && name[5] != NUL); + return (name[0] == MNU_HIDDEN_CHAR) + || (menu_is_popup(name) && name[5] != NUL); } /* diff --git a/src/nvim/menu.h b/src/nvim/menu.h index a84b7d812e..5ff979f2bf 100644 --- a/src/nvim/menu.h +++ b/src/nvim/menu.h @@ -6,7 +6,9 @@ #include "nvim/types.h" // for char_u and expand_T #include "nvim/ex_cmds_defs.h" // for exarg_T -/* Indices into vimmenu_T->strings[] and vimmenu_T->noremap[] for each mode */ +/// Indices into vimmenu_T->strings[] and vimmenu_T->noremap[] for each mode +/// \addtogroup MENU_INDEX +/// @{ #define MENU_INDEX_INVALID -1 #define MENU_INDEX_NORMAL 0 #define MENU_INDEX_VISUAL 1 @@ -16,8 +18,12 @@ #define MENU_INDEX_CMDLINE 5 #define MENU_INDEX_TIP 6 #define MENU_MODES 7 +/// @} +/// note MENU_INDEX_TIP is not a 'real' mode -/* Menu modes */ +/// Menu modes +/// \addtogroup MENU_MODES +/// @{ #define MENU_NORMAL_MODE (1 << MENU_INDEX_NORMAL) #define MENU_VISUAL_MODE (1 << MENU_INDEX_VISUAL) #define MENU_SELECT_MODE (1 << MENU_INDEX_SELECT) @@ -26,31 +32,30 @@ #define MENU_CMDLINE_MODE (1 << MENU_INDEX_CMDLINE) #define MENU_TIP_MODE (1 << MENU_INDEX_TIP) #define MENU_ALL_MODES ((1 << MENU_INDEX_TIP) - 1) -/*note MENU_INDEX_TIP is not a 'real' mode*/ +/// @} -/* Start a menu name with this to not include it on the main menu bar */ +/// Start a menu name with this to not include it on the main menu bar #define MNU_HIDDEN_CHAR ']' typedef struct VimMenu vimmenu_T; struct VimMenu { - int modes; /* Which modes is this menu visible for? */ - int enabled; /* for which modes the menu is enabled */ - char_u *name; /* Name of menu, possibly translated */ - char_u *dname; /* Displayed Name ("name" without '&') */ - char_u *en_name; /* "name" untranslated, NULL when "name" - * was not translated */ - char_u *en_dname; /* "dname" untranslated, NULL when "dname" - * was not translated */ - int mnemonic; /* mnemonic key (after '&') */ - char_u *actext; /* accelerator text (after TAB) */ - long priority; /* Menu order priority */ - char_u *strings[MENU_MODES]; /* Mapped string for each mode */ - int noremap[MENU_MODES]; /* A REMAP_ flag for each mode */ - bool silent[MENU_MODES]; /* A silent flag for each mode */ - vimmenu_T *children; /* Children of sub-menu */ - vimmenu_T *parent; /* Parent of menu */ - vimmenu_T *next; /* Next item in menu */ + int modes; ///< Which modes is this menu visible for + int enabled; ///< for which modes the menu is enabled + char_u *name; ///< Name of menu, possibly translated + char_u *dname; ///< Displayed Name ("name" without '&') + char_u *en_name; ///< "name" untranslated, NULL when + ///< was not translated + char_u *en_dname; ///< NULL when "dname" untranslated + int mnemonic; ///< mnemonic key (after '&') + char_u *actext; ///< accelerator text (after TAB) + long priority; ///< Menu order priority + char_u *strings[MENU_MODES]; ///< Mapped string for each mode + int noremap[MENU_MODES]; ///< A \ref REMAP_VALUES flag for each mode + bool silent[MENU_MODES]; ///< A silent flag for each mode + vimmenu_T *children; ///< Children of sub-menu + vimmenu_T *parent; ///< Parent of menu + vimmenu_T *next; ///< Next item in menu }; diff --git a/src/nvim/move.c b/src/nvim/move.c index d5be4cb8c3..81d46a7f17 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -1763,7 +1763,7 @@ int onepage(int dir, long count) loff.fill = 0; if (dir == FORWARD) { - if (firstwin == lastwin && p_window > 0 && p_window < Rows - 1) { + if (ONE_WINDOW && p_window > 0 && p_window < Rows - 1) { /* Vi compatible scrolling */ if (p_window <= 2) ++curwin->w_topline; @@ -1797,7 +1797,7 @@ int onepage(int dir, long count) max_topfill(); continue; } - if (firstwin == lastwin && p_window > 0 && p_window < Rows - 1) { + if (ONE_WINDOW && p_window > 0 && p_window < Rows - 1) { /* Vi compatible scrolling (sort of) */ if (p_window <= 2) --curwin->w_topline; diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index 68ac35bc4e..6fd1af1ba6 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -117,12 +117,15 @@ void channel_teardown(void) /// Creates an API channel by starting a process and connecting to its /// stdin/stdout. stderr is handled by the job infrastructure. /// -/// @param argv The argument vector for the process. [consumed] -/// @return The channel id (> 0), on success. -/// 0, on error. -uint64_t channel_from_process(Process *proc, uint64_t id) +/// @param proc process object +/// @param id (optional) channel id +/// @param source description of source function, rplugin name, TCP addr, etc +/// +/// @return Channel id (> 0), on success. 0, on error. +uint64_t channel_from_process(Process *proc, uint64_t id, char *source) { - Channel *channel = register_channel(kChannelTypeProc, id, proc->events); + Channel *channel = register_channel(kChannelTypeProc, id, proc->events, + source); incref(channel); // process channels are only closed by the exit_cb channel->data.proc = proc; @@ -138,7 +141,8 @@ uint64_t channel_from_process(Process *proc, uint64_t id) /// @param watcher The SocketWatcher ready to accept the connection void channel_from_connection(SocketWatcher *watcher) { - Channel *channel = register_channel(kChannelTypeSocket, 0, NULL); + Channel *channel = register_channel(kChannelTypeSocket, 0, NULL, + watcher->addr); socket_watcher_accept(watcher, &channel->data.stream); incref(channel); // close channel only after the stream is closed channel->data.stream.internal_close_cb = close_cb; @@ -148,8 +152,9 @@ void channel_from_connection(SocketWatcher *watcher) rstream_start(&channel->data.stream, receive_msgpack, channel); } -uint64_t channel_connect(bool tcp, const char *address, - int timeout, const char **error) +/// @param source description of source function, rplugin name, TCP addr, etc +uint64_t channel_connect(bool tcp, const char *address, int timeout, + char *source, const char **error) { if (!tcp) { char *path = fix_fname(address); @@ -161,7 +166,7 @@ uint64_t channel_connect(bool tcp, const char *address, xfree(path); } - Channel *channel = register_channel(kChannelTypeSocket, 0, NULL); + Channel *channel = register_channel(kChannelTypeSocket, 0, NULL, source); if (!socket_connect(&main_loop, &channel->data.stream, tcp, address, timeout, error)) { decref(channel); @@ -329,11 +334,10 @@ bool channel_close(uint64_t id) return true; } -/// Creates an API channel from stdin/stdout. This is used when embedding -/// Neovim +/// Creates an API channel from stdin/stdout. Used to embed Nvim. void channel_from_stdio(void) { - Channel *channel = register_channel(kChannelTypeStdio, 0, NULL); + Channel *channel = register_channel(kChannelTypeStdio, 0, NULL, NULL); incref(channel); // stdio channels are only closed on exit // read stream rstream_init_fd(&main_loop, &channel->data.std.in, 0, CHANNEL_BUFFER_SIZE); @@ -346,7 +350,7 @@ void channel_from_stdio(void) /// when an instance connects to its own named pipe. uint64_t channel_create_internal(void) { - Channel *channel = register_channel(kChannelTypeInternal, 0, NULL); + Channel *channel = register_channel(kChannelTypeInternal, 0, NULL, NULL); incref(channel); // internal channel lives until process exit return channel->id; } @@ -745,9 +749,12 @@ static void close_cb(Stream *stream, void *data) decref(data); } +/// @param source description of source function, rplugin name, TCP addr, etc static Channel *register_channel(ChannelType type, uint64_t id, - MultiQueue *events) + MultiQueue *events, char *source) { + // Jobs and channels share the same id namespace. + assert(id == 0 || !pmap_get(uint64_t)(channels, id)); Channel *rv = xmalloc(sizeof(Channel)); rv->events = events ? events : multiqueue_new_child(main_loop.events); rv->type = type; @@ -761,6 +768,14 @@ static Channel *register_channel(ChannelType type, uint64_t id, kv_init(rv->call_stack); kv_init(rv->delayed_notifications); pmap_put(uint64_t)(channels, rv->id, rv); + + ILOG("new channel %" PRIu64 " (%s): %s", rv->id, + (type == kChannelTypeProc ? "proc" + : (type == kChannelTypeSocket ? "socket" + : (type == kChannelTypeStdio ? "stdio" + : (type == kChannelTypeInternal ? "internal" : "?")))), + (source ? source : "?")); + return rv; } diff --git a/src/nvim/normal.c b/src/nvim/normal.c index d891c74fd2..c1676780d8 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -3657,6 +3657,39 @@ nv_gd ( } } +// Return true if line[offset] is not inside a C-style comment or string, false +// otherwise. +static bool is_ident(char_u *line, int offset) +{ + bool incomment = false; + int instring = 0; + int prev = 0; + + for (int i = 0; i < offset && line[i] != NUL; i++) { + if (instring != 0) { + if (prev != '\\' && line[i] == instring) { + instring = 0; + } + } else if ((line[i] == '"' || line[i] == '\'') && !incomment) { + instring = line[i]; + } else { + if (incomment) { + if (prev == '*' && line[i] == '/') { + incomment = false; + } + } else if (prev == '/' && line[i] == '*') { + incomment = true; + } else if (prev == '/' && line[i] == '/') { + return false; + } + } + + prev = line[i]; + } + + return incomment == false && instring == 0; +} + /* * Search for variable declaration of "ptr[len]". * When "locally" is true in the current function ("gd"), otherwise in the @@ -3683,6 +3716,7 @@ find_decl ( bool retval = true; bool incll; int searchflags = flags_arg; + bool valid; pat = xmalloc(len + 7); @@ -3717,6 +3751,7 @@ find_decl ( /* Search forward for the identifier, ignore comment lines. */ clearpos(&found_pos); for (;; ) { + valid = false; t = searchit(curwin, curbuf, &curwin->w_cursor, FORWARD, pat, 1L, searchflags, RE_LAST, (linenr_T)0, NULL); if (curwin->w_cursor.lnum >= old_pos.lnum) @@ -3747,20 +3782,35 @@ find_decl ( curwin->w_cursor.col = 0; continue; } - if (!locally) /* global search: use first match found */ + valid = is_ident(get_cursor_line_ptr(), curwin->w_cursor.col); + + // If the current position is not a valid identifier and a previous match is + // present, favor that one instead. + if (!valid && found_pos.lnum != 0) { + curwin->w_cursor = found_pos; break; - if (curwin->w_cursor.lnum >= par_pos.lnum) { - /* If we previously found a valid position, use it. */ - if (found_pos.lnum != 0) + } + // global search: use first match found + if (valid && !locally) { + break; + } + if (valid && curwin->w_cursor.lnum >= par_pos.lnum) { + // If we previously found a valid position, use it. + if (found_pos.lnum != 0) { curwin->w_cursor = found_pos; + } break; } - // For finding a local variable and the match is before the "{" search - // to find a later match. For K&R style function declarations this - // skips the function header without types. Remove SEARCH_START from - // flags to avoid getting stuck at one position. - found_pos = curwin->w_cursor; + // For finding a local variable and the match is before the "{" or + // inside a comment, continue searching. For K&R style function + // declarations this skips the function header without types. + if (!valid) { + clearpos(&found_pos); + } else { + found_pos = curwin->w_cursor; + } + // Remove SEARCH_START from flags to avoid getting stuck at one position. searchflags &= ~SEARCH_START; } diff --git a/src/nvim/option.c b/src/nvim/option.c index 37b37e2859..98ca72b2bb 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -2457,12 +2457,11 @@ did_set_string_option ( if ((secure || sandbox != 0) && (options[opt_idx].flags & P_SECURE)) { errmsg = e_secure; - } - /* Check for a "normal" file name in some options. Disallow a path - * separator (slash and/or backslash), wildcards and characters that are - * often illegal in a file name. */ - else if ((options[opt_idx].flags & P_NFNAME) - && vim_strpbrk(*varp, (char_u *)"/\\*?[|<>") != NULL) { + } else if ((options[opt_idx].flags & P_NFNAME) + && vim_strpbrk(*varp, (char_u *)"/\\*?[|;&<>\r\n") != NULL) { + // Check for a "normal" file name in some options. Disallow a path + // separator (slash and/or backslash), wildcards and characters that are + // often illegal in a file name. errmsg = e_invarg; } /* 'backupcopy' */ @@ -4078,7 +4077,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, } /* Change window height NOW */ - if (lastwin != firstwin) { + if (!ONE_WINDOW) { if (pp == &p_wh && curwin->w_height < p_wh) win_setheight((int)p_wh); if (pp == &p_hh && curbuf->b_help && curwin->w_height < p_hh) @@ -4107,7 +4106,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, } /* Change window width NOW */ - if (lastwin != firstwin && curwin->w_width < p_wiw) + if (!ONE_WINDOW && curwin->w_width < p_wiw) win_setwidth((int)p_wiw); } /* 'winminwidth' */ @@ -5239,7 +5238,7 @@ static int put_setbool(FILE *fd, char *cmd, char *name, int value) void comp_col(void) { - int last_has_status = (p_ls == 2 || (p_ls == 1 && firstwin != lastwin)); + int last_has_status = (p_ls == 2 || (p_ls == 1 && !ONE_WINDOW)); sc_col = 0; ru_col = 0; @@ -5718,7 +5717,22 @@ void buf_copy_options(buf_T *buf, int flags) free_buf_options(buf, TRUE); buf->b_p_ro = FALSE; /* don't copy readonly */ buf->b_p_fenc = vim_strsave(p_fenc); - buf->b_p_ff = vim_strsave(p_ff); + switch (*p_ffs) { + case 'm': + buf->b_p_ff = vim_strsave((char_u *)FF_MAC); + break; + case 'd': + buf->b_p_ff = vim_strsave((char_u *)FF_DOS); + break; + case 'u': + buf->b_p_ff = vim_strsave((char_u *)FF_UNIX); + break; + default: + buf->b_p_ff = vim_strsave(p_ff); + } + if (buf->b_p_ff != NULL) { + buf->b_start_ffc = *buf->b_p_ff; + } buf->b_p_bh = empty_option; buf->b_p_bt = empty_option; } else @@ -7039,8 +7053,11 @@ dict_T *get_winbuf_options(const int bufopt) if (opt->flags & P_STRING) { tv_dict_add_str(d, opt->fullname, strlen(opt->fullname), *(const char **)varp); + } else if (opt->flags & P_NUM) { + tv_dict_add_nr(d, opt->fullname, strlen(opt->fullname), + *(long *)varp); } else { - tv_dict_add_nr(d, opt->fullname, strlen(opt->fullname), *varp); + tv_dict_add_nr(d, opt->fullname, strlen(opt->fullname), *(int *)varp); } } } diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index 9d80a43718..32e9a70e57 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -447,7 +447,7 @@ static void out_data_append_to_screen(char *output, size_t remaining, size_t off = 0; int last_row = (int)Rows - 1; - while (off < remaining) { + while (output != NULL && off < remaining) { // Found end of line? if (output[off] == NL) { // Can we start a new line or do we need to continue the last one? @@ -473,7 +473,7 @@ static void out_data_append_to_screen(char *output, size_t remaining, off++; } - if (remaining) { + if (output != NULL && remaining) { if (last_col == 0) { screen_del_lines(0, 0, 1, (int)Rows, NULL); } @@ -496,12 +496,8 @@ static void out_data_cb(Stream *stream, RBuffer *buf, size_t count, void *data, size_t cnt; char *ptr = rbuffer_read_ptr(buf, &cnt); - if (ptr == NULL || cnt == 0) { - // Nothing to read; - return; - } - - if (out_data_decide_throttle(cnt)) { // Skip output above a threshold. + if (ptr != NULL && cnt > 0 + && out_data_decide_throttle(cnt)) { // Skip output above a threshold. // Save the skipped output. If it is the final chunk, we display it later. out_data_ring(ptr, cnt); } else { diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index bd5dfa92cc..e6b1e7b95a 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -383,6 +383,8 @@ static int efm_to_regpat(char_u *efm, int len, efm_T *fmt_ptr, return 0; } +static efm_T *fmt_start = NULL; // cached across qf_parse_line() calls + static void free_efm_list(efm_T **efm_first) { for (efm_T *efm_ptr = *efm_first; efm_ptr != NULL; efm_ptr = *efm_first) { @@ -390,6 +392,8 @@ static void free_efm_list(efm_T **efm_first) vim_regfree(efm_ptr->prog); xfree(efm_ptr); } + + fmt_start = NULL; } // Parse 'errorformat' option @@ -671,7 +675,6 @@ static int qf_parse_line(qf_info_T *qi, char_u *linebuf, size_t linelen, efm_T *fmt_first, qffields_T *fields) { efm_T *fmt_ptr; - static efm_T *fmt_start = NULL; // cached across calls size_t len; int i; int idx = 0; @@ -875,36 +878,38 @@ restofline: qi->qf_multiignore = false; // reset continuation } else if (vim_strchr((char_u *)"CZ", idx) != NULL) { // continuation of multi-line msg - qfline_T *qfprev = qi->qf_lists[qi->qf_curlist].qf_last; - if (qfprev == NULL) { - return QF_FAIL; - } - if (*fields->errmsg && !qi->qf_multiignore) { - size_t len = STRLEN(qfprev->qf_text); - qfprev->qf_text = xrealloc(qfprev->qf_text, - len + STRLEN(fields->errmsg) + 2); - qfprev->qf_text[len] = '\n'; - STRCPY(qfprev->qf_text + len + 1, fields->errmsg); - } - if (qfprev->qf_nr == -1) { - qfprev->qf_nr = fields->enr; - } - if (vim_isprintc(fields->type) && !qfprev->qf_type) { - qfprev->qf_type = fields->type; // only printable chars allowed - } - if (!qfprev->qf_lnum) { - qfprev->qf_lnum = fields->lnum; - } - if (!qfprev->qf_col) { - qfprev->qf_col = fields->col; - } - qfprev->qf_viscol = fields->use_viscol; - if (!qfprev->qf_fnum) { - qfprev->qf_fnum = qf_get_fnum(qi, qi->qf_directory, - *fields->namebuf || qi->qf_directory - ? fields->namebuf - : qi->qf_currfile && fields->valid - ? qi->qf_currfile : 0); + if (!qi->qf_multiignore) { + qfline_T *qfprev = qi->qf_lists[qi->qf_curlist].qf_last; + if (qfprev == NULL) { + return QF_FAIL; + } + if (*fields->errmsg && !qi->qf_multiignore) { + size_t len = STRLEN(qfprev->qf_text); + qfprev->qf_text = xrealloc(qfprev->qf_text, + len + STRLEN(fields->errmsg) + 2); + qfprev->qf_text[len] = '\n'; + STRCPY(qfprev->qf_text + len + 1, fields->errmsg); + } + if (qfprev->qf_nr == -1) { + qfprev->qf_nr = fields->enr; + } + if (vim_isprintc(fields->type) && !qfprev->qf_type) { + qfprev->qf_type = fields->type; // only printable chars allowed + } + if (!qfprev->qf_lnum) { + qfprev->qf_lnum = fields->lnum; + } + if (!qfprev->qf_col) { + qfprev->qf_col = fields->col; + } + qfprev->qf_viscol = fields->use_viscol; + if (!qfprev->qf_fnum) { + qfprev->qf_fnum = qf_get_fnum(qi, qi->qf_directory, + *fields->namebuf || qi->qf_directory + ? fields->namebuf + : qi->qf_currfile && fields->valid + ? qi->qf_currfile : 0); + } } if (idx == 'Z') { qi->qf_multiline = qi->qf_multiignore = false; @@ -967,6 +972,7 @@ qf_init_ext( NULL, 0, 0 }; qffields_T fields = { NULL, NULL, 0, 0L, 0, false, NULL, 0, 0, 0 }; qfline_T *old_last = NULL; + bool adding = false; static efm_T *fmt_first = NULL; char_u *efm; static char_u *last_efm = NULL; @@ -992,6 +998,7 @@ qf_init_ext( qf_new_list(qi, qf_title); } else if (qi->qf_lists[qi->qf_curlist].qf_count > 0) { // Adding to existing list, use last entry. + adding = true; old_last = qi->qf_lists[qi->qf_curlist].qf_last; } @@ -1108,10 +1115,12 @@ qf_init_ext( } EMSG(_(e_readerrf)); error2: - qf_free(qi, qi->qf_curlist); - qi->qf_listcount--; - if (qi->qf_curlist > 0) { - qi->qf_curlist--; + if (!adding) { + qf_free(qi, qi->qf_curlist); + qi->qf_listcount--; + if (qi->qf_curlist > 0) { + qi->qf_curlist--; + } } qf_init_end: if (state.fd != NULL) { @@ -1408,7 +1417,7 @@ void copy_loclist(win_T *from, win_T *to) to->w_llist->qf_curlist = qi->qf_curlist; /* current list */ } -// Get buffer number for file "directory.fname". +// Get buffer number for file "directory/fname". // Also sets the b_has_qf_entry flag. static int qf_get_fnum(qf_info_T *qi, char_u *directory, char_u *fname) { @@ -1869,7 +1878,7 @@ win_found: * If there is only one window and it is the quickfix window, create a * new one above the quickfix window. */ - if (((firstwin == lastwin) && bt_quickfix(curbuf)) || !usable_win) { + if ((ONE_WINDOW && bt_quickfix(curbuf)) || !usable_win) { flags = WSP_ABOVE; if (ll_ref != NULL) flags |= WSP_NEWLOC; @@ -2362,7 +2371,9 @@ static void qf_free(qf_info_T *qi, int idx) qi->qf_lists[idx].qf_index = 0; qf_clean_dir_stack(&qi->qf_dir_stack); + qi->qf_directory = NULL; qf_clean_dir_stack(&qi->qf_file_stack); + qi->qf_currfile = NULL; } /* @@ -3116,7 +3127,7 @@ static char_u *get_mef_name(void) STRCPY(name, p_mef); sprintf((char *)name + (p - p_mef), "%d%d", start, off); STRCAT(name, p + 2); - // Don't accept a symbolic link, its a security risk. + // Don't accept a symbolic link, it's a security risk. FileInfo file_info; bool file_or_link_found = os_fileinfo_link((char *)name, &file_info); if (!file_or_link_found) { @@ -4025,9 +4036,12 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) if ((di = tv_dict_find(what, S_LEN("nr"))) != NULL) { // Use the specified quickfix/location list if (di->di_tv.v_type == VAR_NUMBER) { - qf_idx = (int)di->di_tv.vval.v_number - 1; - if (qf_idx < 0 || qf_idx >= qi->qf_listcount) { - return FAIL; + // for zero use the current list + if (di->di_tv.vval.v_number != 0) { + qf_idx = (int)di->di_tv.vval.v_number - 1; + if (qf_idx < 0 || qf_idx >= qi->qf_listcount) { + return FAIL; + } } flags |= QF_GETLIST_NR; } else { diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index 41070aebf4..847b2f273e 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -3172,61 +3172,56 @@ static int need_clear_zsubexpr = FALSE; /* extmatch subexpressions int regnarrate = 0; #endif -/* - * Internal copy of 'ignorecase'. It is set at each call to vim_regexec(). - * Normally it gets the value of "rm_ic" or "rmm_ic", but when the pattern - * contains '\c' or '\C' the value is overruled. - */ -static int ireg_ic; - -/* - * Similar to ireg_ic, but only for 'combining' characters. Set with \Z flag - * in the regexp. Defaults to false, always. - */ -static int ireg_icombine; - -/* - * Copy of "rmm_maxcol": maximum column to search for a match. Zero when - * there is no maximum. - */ -static colnr_T ireg_maxcol; - -/* - * Sometimes need to save a copy of a line. Since alloc()/free() is very - * slow, we keep one allocated piece of memory and only re-allocate it when - * it's too small. It's freed in bt_regexec_both() when finished. - */ +// Sometimes need to save a copy of a line. Since alloc()/free() is very +// slow, we keep one allocated piece of memory and only re-allocate it when +// it's too small. It's freed in bt_regexec_both() when finished. static char_u *reg_tofree = NULL; static unsigned reg_tofreelen; -/* - * These variables are set when executing a regexp to speed up the execution. - * Which ones are set depends on whether a single-line or multi-line match is - * done: - * single-line multi-line - * reg_match ®match_T NULL - * reg_mmatch NULL ®mmatch_T - * reg_startp reg_match->startp <invalid> - * reg_endp reg_match->endp <invalid> - * reg_startpos <invalid> reg_mmatch->startpos - * reg_endpos <invalid> reg_mmatch->endpos - * reg_win NULL window in which to search - * reg_buf curbuf buffer in which to search - * reg_firstlnum <invalid> first line in which to search - * reg_maxline 0 last line nr - * reg_line_lbr FALSE or TRUE FALSE - */ -static regmatch_T *reg_match; -static regmmatch_T *reg_mmatch; -static char_u **reg_startp = NULL; -static char_u **reg_endp = NULL; -static lpos_T *reg_startpos = NULL; -static lpos_T *reg_endpos = NULL; -static win_T *reg_win; -static buf_T *reg_buf; -static linenr_T reg_firstlnum; -static linenr_T reg_maxline; -static int reg_line_lbr; /* "\n" in string is line break */ +// Structure used to store the execution state of the regex engine. +// Which ones are set depends on whether a single-line or multi-line match is +// done: +// single-line multi-line +// reg_match ®match_T NULL +// reg_mmatch NULL ®mmatch_T +// reg_startp reg_match->startp <invalid> +// reg_endp reg_match->endp <invalid> +// reg_startpos <invalid> reg_mmatch->startpos +// reg_endpos <invalid> reg_mmatch->endpos +// reg_win NULL window in which to search +// reg_buf curbuf buffer in which to search +// reg_firstlnum <invalid> first line in which to search +// reg_maxline 0 last line nr +// reg_line_lbr false or true false +typedef struct { + regmatch_T *reg_match; + regmmatch_T *reg_mmatch; + char_u **reg_startp; + char_u **reg_endp; + lpos_T *reg_startpos; + lpos_T *reg_endpos; + win_T *reg_win; + buf_T *reg_buf; + linenr_T reg_firstlnum; + linenr_T reg_maxline; + bool reg_line_lbr; // "\n" in string is line break + + // Internal copy of 'ignorecase'. It is set at each call to vim_regexec(). + // Normally it gets the value of "rm_ic" or "rmm_ic", but when the pattern + // contains '\c' or '\C' the value is overruled. + bool reg_ic; + + // Similar to rex.reg_ic, but only for 'combining' characters. Set with \Z + // flag in the regexp. Defaults to false, always. + bool reg_icombine; + + // Copy of "rmm_maxcol": maximum column to search for a match. Zero when + // there is no maximum. + colnr_T reg_maxcol; +} regexec_T; + +static regexec_T rex; +static bool rex_in_use = false; /* * "regstack" and "backpos" are used by regmatch(). They are kept over calls @@ -3268,14 +3263,16 @@ void free_regexp_stuff(void) */ static char_u *reg_getline(linenr_T lnum) { - /* when looking behind for a match/no-match lnum is negative. But we - * can't go before line 1 */ - if (reg_firstlnum + lnum < 1) + // when looking behind for a match/no-match lnum is negative. But we + // can't go before line 1 + if (rex.reg_firstlnum + lnum < 1) { return NULL; - if (lnum > reg_maxline) - /* Must have matched the "\n" in the last line. */ + } + if (lnum > rex.reg_maxline) { + // Must have matched the "\n" in the last line. return (char_u *)""; - return ml_get_buf(reg_buf, reg_firstlnum + lnum, FALSE); + } + return ml_get_buf(rex.reg_buf, rex.reg_firstlnum + lnum, false); } static regsave_T behind_pos; @@ -3285,9 +3282,8 @@ static char_u *reg_endzp[NSUBEXP]; /* and end of \z(...\) matches */ static lpos_T reg_startzpos[NSUBEXP]; /* idem, beginning pos */ static lpos_T reg_endzpos[NSUBEXP]; /* idem, end pos */ -/* TRUE if using multi-line regexp. */ -#define REG_MULTI (reg_match == NULL) - +// TRUE if using multi-line regexp. +#define REG_MULTI (rex.reg_match == NULL) /* * Match a regexp against a string. @@ -3305,15 +3301,15 @@ bt_regexec_nl ( bool line_lbr ) { - reg_match = rmp; - reg_mmatch = NULL; - reg_maxline = 0; - reg_line_lbr = line_lbr; - reg_buf = curbuf; - reg_win = NULL; - ireg_ic = rmp->rm_ic; - ireg_icombine = FALSE; - ireg_maxcol = 0; + rex.reg_match = rmp; + rex.reg_mmatch = NULL; + rex.reg_maxline = 0; + rex.reg_line_lbr = line_lbr; + rex.reg_buf = curbuf; + rex.reg_win = NULL; + rex.reg_ic = rmp->rm_ic; + rex.reg_icombine = false; + rex.reg_maxcol = 0; long r = bt_regexec_both(line, col, NULL); assert(r <= INT_MAX); @@ -3336,16 +3332,16 @@ bt_regexec_nl ( static long bt_regexec_multi(regmmatch_T *rmp, win_T *win, buf_T *buf, linenr_T lnum, colnr_T col, proftime_T *tm) { - reg_match = NULL; - reg_mmatch = rmp; - reg_buf = buf; - reg_win = win; - reg_firstlnum = lnum; - reg_maxline = reg_buf->b_ml.ml_line_count - lnum; - reg_line_lbr = FALSE; - ireg_ic = rmp->rmm_ic; - ireg_icombine = FALSE; - ireg_maxcol = rmp->rmm_maxcol; + rex.reg_match = NULL; + rex.reg_mmatch = rmp; + rex.reg_buf = buf; + rex.reg_win = win; + rex.reg_firstlnum = lnum; + rex.reg_maxline = rex.reg_buf->b_ml.ml_line_count - lnum; + rex.reg_line_lbr = false; + rex.reg_ic = rmp->rmm_ic; + rex.reg_icombine = false; + rex.reg_maxcol = rmp->rmm_maxcol; return bt_regexec_both(NULL, col, tm); } @@ -3383,14 +3379,14 @@ static long bt_regexec_both(char_u *line, } if (REG_MULTI) { - prog = (bt_regprog_T *)reg_mmatch->regprog; + prog = (bt_regprog_T *)rex.reg_mmatch->regprog; line = reg_getline((linenr_T)0); - reg_startpos = reg_mmatch->startpos; - reg_endpos = reg_mmatch->endpos; + rex.reg_startpos = rex.reg_mmatch->startpos; + rex.reg_endpos = rex.reg_mmatch->endpos; } else { - prog = (bt_regprog_T *)reg_match->regprog; - reg_startp = reg_match->startp; - reg_endp = reg_match->endp; + prog = (bt_regprog_T *)rex.reg_match->regprog; + rex.reg_startp = rex.reg_match->startp; + rex.reg_endp = rex.reg_match->endp; } /* Be paranoid... */ @@ -3403,19 +3399,22 @@ static long bt_regexec_both(char_u *line, if (prog_magic_wrong()) goto theend; - /* If the start column is past the maximum column: no need to try. */ - if (ireg_maxcol > 0 && col >= ireg_maxcol) + // If the start column is past the maximum column: no need to try. + if (rex.reg_maxcol > 0 && col >= rex.reg_maxcol) { goto theend; + } - /* If pattern contains "\c" or "\C": overrule value of ireg_ic */ - if (prog->regflags & RF_ICASE) - ireg_ic = TRUE; - else if (prog->regflags & RF_NOICASE) - ireg_ic = FALSE; + // If pattern contains "\c" or "\C": overrule value of rex.reg_ic + if (prog->regflags & RF_ICASE) { + rex.reg_ic = true; + } else if (prog->regflags & RF_NOICASE) { + rex.reg_ic = false; + } - /* If pattern contains "\Z" overrule value of ireg_icombine */ - if (prog->regflags & RF_ICOMBINE) - ireg_icombine = TRUE; + // If pattern contains "\Z" overrule value of rex.reg_icombine + if (prog->regflags & RF_ICOMBINE) { + rex.reg_icombine = true; + } /* If there is a "must appear" string, look for it. */ if (prog->regmust != NULL) { @@ -3429,7 +3428,7 @@ static long bt_regexec_both(char_u *line, // This is used very often, esp. for ":global". Use two versions of // the loop to avoid overhead of conditions. - if (!ireg_ic) { + if (!rex.reg_ic) { while ((s = vim_strchr(s, c)) != NULL) { if (cstrncmp(s, prog->regmust, &prog->regmlen) == 0) { break; // Found it. @@ -3463,7 +3462,7 @@ static long bt_regexec_both(char_u *line, c = regline[col]; if (prog->regstart == NUL || prog->regstart == c - || (ireg_ic + || (rex.reg_ic && (((enc_utf8 && utf_fold(prog->regstart) == utf_fold(c))) || (c < 255 && prog->regstart < 255 && mb_tolower(prog->regstart) == mb_tolower(c))))) { @@ -3485,8 +3484,8 @@ static long bt_regexec_both(char_u *line, col = (int)(s - regline); } - /* Check for maximum column to try. */ - if (ireg_maxcol > 0 && col >= ireg_maxcol) { + // Check for maximum column to try. + if (rex.reg_maxcol > 0 && col >= rex.reg_maxcol) { retval = 0; break; } @@ -3583,21 +3582,24 @@ static long regtry(bt_regprog_T *prog, colnr_T col) cleanup_subexpr(); if (REG_MULTI) { - if (reg_startpos[0].lnum < 0) { - reg_startpos[0].lnum = 0; - reg_startpos[0].col = col; + if (rex.reg_startpos[0].lnum < 0) { + rex.reg_startpos[0].lnum = 0; + rex.reg_startpos[0].col = col; + } + if (rex.reg_endpos[0].lnum < 0) { + rex.reg_endpos[0].lnum = reglnum; + rex.reg_endpos[0].col = (int)(reginput - regline); + } else { + // Use line number of "\ze". + reglnum = rex.reg_endpos[0].lnum; } - if (reg_endpos[0].lnum < 0) { - reg_endpos[0].lnum = reglnum; - reg_endpos[0].col = (int)(reginput - regline); - } else - /* Use line number of "\ze". */ - reglnum = reg_endpos[0].lnum; } else { - if (reg_startp[0] == NULL) - reg_startp[0] = regline + col; - if (reg_endp[0] == NULL) - reg_endp[0] = reginput; + if (rex.reg_startp[0] == NULL) { + rex.reg_startp[0] = regline + col; + } + if (rex.reg_endp[0] == NULL) { + rex.reg_endp[0] = reginput; + } } /* Package any found \z(...\) matches for export. Default is none. */ unref_extmatch(re_extmatch_out); @@ -3632,36 +3634,33 @@ static long regtry(bt_regprog_T *prog, colnr_T col) } -/* - * Get class of previous character. - */ +// Get class of previous character. static int reg_prev_class(void) { if (reginput > regline) { return mb_get_class_tab(reginput - 1 - (*mb_head_off)(regline, reginput - 1), - reg_buf->b_chartab); + rex.reg_buf->b_chartab); } return -1; } -/* - * Return TRUE if the current reginput position matches the Visual area. - */ +// Return TRUE if the current reginput position matches the Visual area. static int reg_match_visual(void) { pos_T top, bot; linenr_T lnum; colnr_T col; - win_T *wp = reg_win == NULL ? curwin : reg_win; + win_T *wp = rex.reg_win == NULL ? curwin : rex.reg_win; int mode; colnr_T start, end; colnr_T start2, end2; - /* Check if the buffer is the current buffer. */ - if (reg_buf != curbuf || VIsual.lnum == 0) - return FALSE; + // Check if the buffer is the current buffer. + if (rex.reg_buf != curbuf || VIsual.lnum == 0) { + return false; + } if (VIsual_active) { if (lt(VIsual, wp->w_cursor)) { @@ -3682,9 +3681,10 @@ static int reg_match_visual(void) } mode = curbuf->b_visual.vi_mode; } - lnum = reglnum + reg_firstlnum; - if (lnum < top.lnum || lnum > bot.lnum) - return FALSE; + lnum = reglnum + rex.reg_firstlnum; + if (lnum < top.lnum || lnum > bot.lnum) { + return false; + } if (mode == 'v') { col = (colnr_T)(reginput - regline); @@ -3803,11 +3803,11 @@ regmatch ( next = regnext(scan); op = OP(scan); - /* Check for character class with NL added. */ - if (!reg_line_lbr && WITH_NL(op) && REG_MULTI - && *reginput == NUL && reglnum <= reg_maxline) { + // Check for character class with NL added. + if (!rex.reg_line_lbr && WITH_NL(op) && REG_MULTI + && *reginput == NUL && reglnum <= rex.reg_maxline) { reg_nextline(); - } else if (reg_line_lbr && WITH_NL(op) && *reginput == '\n') { + } else if (rex.reg_line_lbr && WITH_NL(op) && *reginput == '\n') { ADVANCE_REGINPUT(); } else { if (WITH_NL(op)) @@ -3828,26 +3828,29 @@ regmatch ( break; case RE_BOF: - /* We're not at the beginning of the file when below the first - * line where we started, not at the start of the line or we - * didn't start at the first line of the buffer. */ + // We're not at the beginning of the file when below the first + // line where we started, not at the start of the line or we + // didn't start at the first line of the buffer. if (reglnum != 0 || reginput != regline - || (REG_MULTI && reg_firstlnum > 1)) + || (REG_MULTI && rex.reg_firstlnum > 1)) { status = RA_NOMATCH; + } break; case RE_EOF: - if (reglnum != reg_maxline || c != NUL) + if (reglnum != rex.reg_maxline || c != NUL) { status = RA_NOMATCH; + } break; case CURSOR: - /* Check if the buffer is in a window and compare the - * reg_win->w_cursor position to the match position. */ - if (reg_win == NULL - || (reglnum + reg_firstlnum != reg_win->w_cursor.lnum) - || ((colnr_T)(reginput - regline) != reg_win->w_cursor.col)) + // Check if the buffer is in a window and compare the + // rex.reg_win->w_cursor position to the match position. + if (rex.reg_win == NULL + || (reglnum + rex.reg_firstlnum != rex.reg_win->w_cursor.lnum) + || ((colnr_T)(reginput - regline) != rex.reg_win->w_cursor.col)) { status = RA_NOMATCH; + } break; case RE_MARK: @@ -3857,19 +3860,20 @@ regmatch ( int cmp = OPERAND(scan)[1]; pos_T *pos; - pos = getmark_buf(reg_buf, mark, FALSE); - if (pos == NULL /* mark doesn't exist */ - || pos->lnum <= 0 /* mark isn't set in reg_buf */ - || (pos->lnum == reglnum + reg_firstlnum + pos = getmark_buf(rex.reg_buf, mark, false); + if (pos == NULL // mark doesn't exist + || pos->lnum <= 0 // mark isn't set in reg_buf + || (pos->lnum == reglnum + rex.reg_firstlnum ? (pos->col == (colnr_T)(reginput - regline) ? (cmp == '<' || cmp == '>') : (pos->col < (colnr_T)(reginput - regline) ? cmp != '>' : cmp != '<')) - : (pos->lnum < reglnum + reg_firstlnum + : (pos->lnum < reglnum + rex.reg_firstlnum ? cmp != '>' - : cmp != '<'))) + : cmp != '<'))) { status = RA_NOMATCH; + } } break; @@ -3879,11 +3883,12 @@ regmatch ( break; case RE_LNUM: - assert(reglnum + reg_firstlnum >= 0 - && (uintmax_t)(reglnum + reg_firstlnum) <= UINT32_MAX); - if (!REG_MULTI || !re_num_cmp((uint32_t)(reglnum + reg_firstlnum), - scan)) + assert(reglnum + rex.reg_firstlnum >= 0 + && (uintmax_t)(reglnum + rex.reg_firstlnum) <= UINT32_MAX); + if (!REG_MULTI + || !re_num_cmp((uint32_t)(reglnum + rex.reg_firstlnum), scan)) { status = RA_NOMATCH; + } break; case RE_COL: @@ -3894,11 +3899,13 @@ regmatch ( break; case RE_VCOL: - if (!re_num_cmp(win_linetabsize(reg_win == NULL ? curwin : reg_win, + if (!re_num_cmp(win_linetabsize(rex.reg_win == NULL + ? curwin : rex.reg_win, regline, (colnr_T)(reginput - regline)) + 1, - scan)) + scan)) { status = RA_NOMATCH; + } break; case BOW: /* \<word; reginput points to w */ @@ -3908,17 +3915,18 @@ regmatch ( int this_class; // Get class of current and previous char (if it exists). - this_class = mb_get_class_tab(reginput, reg_buf->b_chartab); + this_class = mb_get_class_tab(reginput, rex.reg_buf->b_chartab); if (this_class <= 1) { status = RA_NOMATCH; // Not on a word at all. } else if (reg_prev_class() == this_class) { status = RA_NOMATCH; // Previous char is in same word. } } else { - if (!vim_iswordc_buf(c, reg_buf) || (reginput > regline - && vim_iswordc_buf(reginput[-1 - ], reg_buf))) + if (!vim_iswordc_buf(c, rex.reg_buf) + || (reginput > regline + && vim_iswordc_buf(reginput[-1], rex.reg_buf))) { status = RA_NOMATCH; + } } break; @@ -3929,15 +3937,16 @@ regmatch ( int this_class, prev_class; // Get class of current and previous char (if it exists). - this_class = mb_get_class_tab(reginput, reg_buf->b_chartab); + this_class = mb_get_class_tab(reginput, rex.reg_buf->b_chartab); prev_class = reg_prev_class(); if (this_class == prev_class || prev_class == 0 || prev_class == 1) status = RA_NOMATCH; } else { - if (!vim_iswordc_buf(reginput[-1], reg_buf) - || (reginput[0] != NUL && vim_iswordc_buf(c, reg_buf))) + if (!vim_iswordc_buf(reginput[-1], rex.reg_buf) + || (reginput[0] != NUL && vim_iswordc_buf(c, rex.reg_buf))) { status = RA_NOMATCH; + } } break; /* Matched with EOW */ @@ -3964,17 +3973,20 @@ regmatch ( break; case KWORD: - if (!vim_iswordp_buf(reginput, reg_buf)) + if (!vim_iswordp_buf(reginput, rex.reg_buf)) { status = RA_NOMATCH; - else + } else { ADVANCE_REGINPUT(); + } break; case SKWORD: - if (ascii_isdigit(*reginput) || !vim_iswordp_buf(reginput, reg_buf)) + if (ascii_isdigit(*reginput) + || !vim_iswordp_buf(reginput, rex.reg_buf)) { status = RA_NOMATCH; - else + } else { ADVANCE_REGINPUT(); + } break; case FNAME: @@ -4139,7 +4151,7 @@ regmatch ( opnd = OPERAND(scan); // Inline the first byte, for speed. if (*opnd != *reginput - && (!ireg_ic + && (!rex.reg_ic || (!enc_utf8 && mb_tolower(*opnd) != mb_tolower(*reginput)))) { status = RA_NOMATCH; @@ -4147,8 +4159,8 @@ regmatch ( // match empty string always works; happens when "~" is // empty. } else { - if (opnd[1] == NUL && !(enc_utf8 && ireg_ic)) { - len = 1; /* matched a single byte above */ + if (opnd[1] == NUL && !(enc_utf8 && rex.reg_ic)) { + len = 1; // matched a single byte above } else { // Need to match first byte again for multi-byte. len = (int)STRLEN(opnd); @@ -4160,7 +4172,7 @@ regmatch ( // follows (skips over all composing chars). if (status != RA_NOMATCH && enc_utf8 && UTF_COMPOSINGLIKE(reginput, reginput + len) - && !ireg_icombine + && !rex.reg_icombine && OP(next) != RE_COMPOSING) { // raaron: This code makes a composing character get // ignored, which is the correct behavior (sometimes) @@ -4284,9 +4296,9 @@ regmatch ( status = RA_FAIL; else { rp->rs_no = no; - save_se(&rp->rs_un.sesave, ®_startpos[no], - ®_startp[no]); - /* We simply continue and handle the result when done. */ + save_se(&rp->rs_un.sesave, &rex.reg_startpos[no], + &rex.reg_startp[no]); + // We simply continue and handle the result when done. } } break; @@ -4336,12 +4348,12 @@ regmatch ( no = op - MCLOSE; cleanup_subexpr(); rp = regstack_push(RS_MCLOSE, scan); - if (rp == NULL) + if (rp == NULL) { status = RA_FAIL; - else { + } else { rp->rs_no = no; - save_se(&rp->rs_un.sesave, ®_endpos[no], ®_endp[no]); - /* We simply continue and handle the result when done. */ + save_se(&rp->rs_un.sesave, &rex.reg_endpos[no], &rex.reg_endp[no]); + // We simply continue and handle the result when done. } } break; @@ -4384,41 +4396,40 @@ regmatch ( no = op - BACKREF; cleanup_subexpr(); - if (!REG_MULTI) { /* Single-line regexp */ - if (reg_startp[no] == NULL || reg_endp[no] == NULL) { - /* Backref was not set: Match an empty string. */ + if (!REG_MULTI) { // Single-line regexp + if (rex.reg_startp[no] == NULL || rex.reg_endp[no] == NULL) { + // Backref was not set: Match an empty string. len = 0; } else { - /* Compare current input with back-ref in the same - * line. */ - len = (int)(reg_endp[no] - reg_startp[no]); - if (cstrncmp(reg_startp[no], reginput, &len) != 0) + // Compare current input with back-ref in the same line. + len = (int)(rex.reg_endp[no] - rex.reg_startp[no]); + if (cstrncmp(rex.reg_startp[no], reginput, &len) != 0) { status = RA_NOMATCH; + } } - } else { /* Multi-line regexp */ - if (reg_startpos[no].lnum < 0 || reg_endpos[no].lnum < 0) { - /* Backref was not set: Match an empty string. */ + } else { // Multi-line regexp + if (rex.reg_startpos[no].lnum < 0 || rex.reg_endpos[no].lnum < 0) { + // Backref was not set: Match an empty string. len = 0; } else { - if (reg_startpos[no].lnum == reglnum - && reg_endpos[no].lnum == reglnum) { - /* Compare back-ref within the current line. */ - len = reg_endpos[no].col - reg_startpos[no].col; - if (cstrncmp(regline + reg_startpos[no].col, - reginput, &len) != 0) + if (rex.reg_startpos[no].lnum == reglnum + && rex.reg_endpos[no].lnum == reglnum) { + // Compare back-ref within the current line. + len = rex.reg_endpos[no].col - rex.reg_startpos[no].col; + if (cstrncmp(regline + rex.reg_startpos[no].col, + reginput, &len) != 0) { status = RA_NOMATCH; + } } else { - /* Messy situation: Need to compare between two - * lines. */ - int r = match_with_backref( - reg_startpos[no].lnum, - reg_startpos[no].col, - reg_endpos[no].lnum, - reg_endpos[no].col, - &len); - - if (r != RA_MATCH) + // Messy situation: Need to compare between two lines. + int r = match_with_backref(rex.reg_startpos[no].lnum, + rex.reg_startpos[no].col, + rex.reg_endpos[no].lnum, + rex.reg_endpos[no].col, + &len); + if (r != RA_MATCH) { status = r; + } } } } @@ -4558,7 +4569,7 @@ regmatch ( */ if (OP(next) == EXACTLY) { rst.nextb = *OPERAND(next); - if (ireg_ic) { + if (rex.reg_ic) { if (mb_isupper(rst.nextb)) { rst.nextb_ic = mb_tolower(rst.nextb); } else { @@ -4665,13 +4676,14 @@ regmatch ( break; case NEWL: - if ((c != NUL || !REG_MULTI || reglnum > reg_maxline - || reg_line_lbr) && (c != '\n' || !reg_line_lbr)) + if ((c != NUL || !REG_MULTI || reglnum > rex.reg_maxline + || rex.reg_line_lbr) && (c != '\n' || !rex.reg_line_lbr)) { status = RA_NOMATCH; - else if (reg_line_lbr) + } else if (rex.reg_line_lbr) { ADVANCE_REGINPUT(); - else + } else { reg_nextline(); + } break; case END: @@ -4710,10 +4722,11 @@ regmatch ( break; case RS_MOPEN: - /* Pop the state. Restore pointers when there is no match. */ - if (status == RA_NOMATCH) - restore_se(&rp->rs_un.sesave, ®_startpos[rp->rs_no], - ®_startp[rp->rs_no]); + // Pop the state. Restore pointers when there is no match. + if (status == RA_NOMATCH) { + restore_se(&rp->rs_un.sesave, &rex.reg_startpos[rp->rs_no], + &rex.reg_startp[rp->rs_no]); + } regstack_pop(&scan); break; @@ -4726,10 +4739,11 @@ regmatch ( break; case RS_MCLOSE: - /* Pop the state. Restore pointers when there is no match. */ - if (status == RA_NOMATCH) - restore_se(&rp->rs_un.sesave, ®_endpos[rp->rs_no], - ®_endp[rp->rs_no]); + // Pop the state. Restore pointers when there is no match. + if (status == RA_NOMATCH) { + restore_se(&rp->rs_un.sesave, &rex.reg_endpos[rp->rs_no], + &rex.reg_endp[rp->rs_no]); + } regstack_pop(&scan); break; @@ -5109,10 +5123,11 @@ regrepeat ( ++count; mb_ptr_adv(scan); } - if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > reg_maxline - || reg_line_lbr || count == maxcount) + if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > rex.reg_maxline + || rex.reg_line_lbr || count == maxcount) { break; - ++count; /* count the line-break */ + } + count++; // count the line-break reg_nextline(); scan = reginput; if (got_int) @@ -5130,17 +5145,19 @@ regrepeat ( if (vim_isIDc(PTR2CHAR(scan)) && (testval || !ascii_isdigit(*scan))) { mb_ptr_adv(scan); } else if (*scan == NUL) { - if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > reg_maxline - || reg_line_lbr) + if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > rex.reg_maxline + || rex.reg_line_lbr) { break; + } reg_nextline(); scan = reginput; if (got_int) break; - } else if (reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) - ++scan; - else + } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) { + scan++; + } else { break; + } ++count; } break; @@ -5152,22 +5169,25 @@ regrepeat ( case SKWORD: case SKWORD + ADD_NL: while (count < maxcount) { - if (vim_iswordp_buf(scan, reg_buf) + if (vim_iswordp_buf(scan, rex.reg_buf) && (testval || !ascii_isdigit(*scan))) { mb_ptr_adv(scan); } else if (*scan == NUL) { - if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > reg_maxline - || reg_line_lbr) + if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > rex.reg_maxline + || rex.reg_line_lbr) { break; + } reg_nextline(); scan = reginput; - if (got_int) + if (got_int) { break; - } else if (reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) - ++scan; - else + } + } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) { + scan++; + } else { break; - ++count; + } + count++; } break; @@ -5181,18 +5201,21 @@ regrepeat ( if (vim_isfilec(PTR2CHAR(scan)) && (testval || !ascii_isdigit(*scan))) { mb_ptr_adv(scan); } else if (*scan == NUL) { - if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > reg_maxline - || reg_line_lbr) + if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > rex.reg_maxline + || rex.reg_line_lbr) { break; + } reg_nextline(); scan = reginput; - if (got_int) + if (got_int) { break; - } else if (reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) - ++scan; - else + } + } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) { + scan++; + } else { break; - ++count; + } + count++; } break; @@ -5204,21 +5227,24 @@ regrepeat ( case SPRINT + ADD_NL: while (count < maxcount) { if (*scan == NUL) { - if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > reg_maxline - || reg_line_lbr) + if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > rex.reg_maxline + || rex.reg_line_lbr) { break; + } reg_nextline(); scan = reginput; - if (got_int) + if (got_int) { break; + } } else if (vim_isprintc(PTR2CHAR(scan)) == 1 && (testval || !ascii_isdigit(*scan))) { mb_ptr_adv(scan); - } else if (reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) - ++scan; - else + } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) { + scan++; + } else { break; - ++count; + } + count++; } break; @@ -5229,9 +5255,10 @@ do_class: while (count < maxcount) { int l; if (*scan == NUL) { - if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > reg_maxline - || reg_line_lbr) + if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > rex.reg_maxline + || rex.reg_line_lbr) { break; + } reg_nextline(); scan = reginput; if (got_int) @@ -5240,12 +5267,13 @@ do_class: if (testval != 0) break; scan += l; - } else if ((class_tab[*scan] & mask) == testval) - ++scan; - else if (reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) - ++scan; - else + } else if ((class_tab[*scan] & mask) == testval) { + scan++; + } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) { + scan++; + } else { break; + } ++count; } break; @@ -5323,10 +5351,10 @@ do_class: { int cu, cl; - /* This doesn't do a multi-byte character, because a MULTIBYTECODE - * would have been used for it. It does handle single-byte - * characters, such as latin1. */ - if (ireg_ic) { + // This doesn't do a multi-byte character, because a MULTIBYTECODE + // would have been used for it. It does handle single-byte + // characters, such as latin1. + if (rex.reg_ic) { cu = mb_toupper(*opnd); cl = mb_tolower(*opnd); while (count < maxcount && (*scan == cu || *scan == cl)) { @@ -5350,17 +5378,19 @@ do_class: /* Safety check (just in case 'encoding' was changed since * compiling the program). */ if ((len = (*mb_ptr2len)(opnd)) > 1) { - if (ireg_ic && enc_utf8) + if (rex.reg_ic && enc_utf8) { cf = utf_fold(utf_ptr2char(opnd)); + } while (count < maxcount && (*mb_ptr2len)(scan) >= len) { for (i = 0; i < len; ++i) { if (opnd[i] != scan[i]) { break; } } - if (i < len && (!ireg_ic || !enc_utf8 - || utf_fold(utf_ptr2char(scan)) != cf)) + if (i < len && (!rex.reg_ic || !enc_utf8 + || utf_fold(utf_ptr2char(scan)) != cf)) { break; + } scan += len; ++count; } @@ -5378,18 +5408,21 @@ do_class: while (count < maxcount) { int len; if (*scan == NUL) { - if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > reg_maxline - || reg_line_lbr) + if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > rex.reg_maxline + || rex.reg_line_lbr) { break; + } reg_nextline(); scan = reginput; - if (got_int) + if (got_int) { break; - } else if (reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) - ++scan; - else if (has_mbyte && (len = (*mb_ptr2len)(scan)) > 1) { - if ((cstrchr(opnd, (*mb_ptr2char)(scan)) == NULL) == testval) + } + } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) { + scan++; + } else if (has_mbyte && (len = (*mb_ptr2len)(scan)) > 1) { + if ((cstrchr(opnd, (*mb_ptr2char)(scan)) == NULL) == testval) { break; + } scan += len; } else { if ((cstrchr(opnd, *scan) == NULL) == testval) @@ -5402,13 +5435,14 @@ do_class: case NEWL: while (count < maxcount - && ((*scan == NUL && reglnum <= reg_maxline && !reg_line_lbr - && REG_MULTI) || (*scan == '\n' && reg_line_lbr))) { + && ((*scan == NUL && reglnum <= rex.reg_maxline && !rex.reg_line_lbr + && REG_MULTI) || (*scan == '\n' && rex.reg_line_lbr))) { count++; - if (reg_line_lbr) + if (rex.reg_line_lbr) { ADVANCE_REGINPUT(); - else + } else { reg_nextline(); + } scan = reginput; if (got_int) break; @@ -5458,10 +5492,11 @@ static int prog_magic_wrong(void) { regprog_T *prog; - prog = REG_MULTI ? reg_mmatch->regprog : reg_match->regprog; - if (prog->engine == &nfa_regengine) - /* For NFA matcher we don't check the magic */ - return FALSE; + prog = REG_MULTI ? rex.reg_mmatch->regprog : rex.reg_match->regprog; + if (prog->engine == &nfa_regengine) { + // For NFA matcher we don't check the magic + return false; + } if (UCHARAT(((bt_regprog_T *)prog)->program) != REGMAGIC) { EMSG(_(e_re_corr)); @@ -5479,12 +5514,12 @@ static void cleanup_subexpr(void) { if (need_clear_subexpr) { if (REG_MULTI) { - /* Use 0xff to set lnum to -1 */ - memset(reg_startpos, 0xff, sizeof(lpos_T) * NSUBEXP); - memset(reg_endpos, 0xff, sizeof(lpos_T) * NSUBEXP); + // Use 0xff to set lnum to -1 + memset(rex.reg_startpos, 0xff, sizeof(lpos_T) * NSUBEXP); + memset(rex.reg_endpos, 0xff, sizeof(lpos_T) * NSUBEXP); } else { - memset(reg_startp, 0, sizeof(char_u *) * NSUBEXP); - memset(reg_endp, 0, sizeof(char_u *) * NSUBEXP); + memset(rex.reg_startp, 0, sizeof(char_u *) * NSUBEXP); + memset(rex.reg_endp, 0, sizeof(char_u *) * NSUBEXP); } need_clear_subexpr = FALSE; } @@ -5513,17 +5548,17 @@ static void save_subexpr(regbehind_T *bp) { int i; - /* When "need_clear_subexpr" is set we don't need to save the values, only - * remember that this flag needs to be set again when restoring. */ + // When "need_clear_subexpr" is set we don't need to save the values, only + // remember that this flag needs to be set again when restoring. bp->save_need_clear_subexpr = need_clear_subexpr; if (!need_clear_subexpr) { for (i = 0; i < NSUBEXP; ++i) { if (REG_MULTI) { - bp->save_start[i].se_u.pos = reg_startpos[i]; - bp->save_end[i].se_u.pos = reg_endpos[i]; + bp->save_start[i].se_u.pos = rex.reg_startpos[i]; + bp->save_end[i].se_u.pos = rex.reg_endpos[i]; } else { - bp->save_start[i].se_u.ptr = reg_startp[i]; - bp->save_end[i].se_u.ptr = reg_endp[i]; + bp->save_start[i].se_u.ptr = rex.reg_startp[i]; + bp->save_end[i].se_u.ptr = rex.reg_endp[i]; } } } @@ -5541,11 +5576,11 @@ static void restore_subexpr(regbehind_T *bp) if (!need_clear_subexpr) { for (i = 0; i < NSUBEXP; ++i) { if (REG_MULTI) { - reg_startpos[i] = bp->save_start[i].se_u.pos; - reg_endpos[i] = bp->save_end[i].se_u.pos; + rex.reg_startpos[i] = bp->save_start[i].se_u.pos; + rex.reg_endpos[i] = bp->save_end[i].se_u.pos; } else { - reg_startp[i] = bp->save_start[i].se_u.ptr; - reg_endp[i] = bp->save_end[i].se_u.ptr; + rex.reg_startp[i] = bp->save_start[i].se_u.ptr; + rex.reg_endp[i] = bp->save_end[i].se_u.ptr; } } } @@ -5681,10 +5716,12 @@ static int match_with_backref(linenr_T start_lnum, colnr_T start_col, linenr_T e return RA_NOMATCH; /* doesn't match */ if (bytelen != NULL) *bytelen += len; - if (clnum == end_lnum) - break; /* match and at end! */ - if (reglnum >= reg_maxline) - return RA_NOMATCH; /* text too short */ + if (clnum == end_lnum) { + break; // match and at end! + } + if (reglnum >= rex.reg_maxline) { + return RA_NOMATCH; // text too short + } /* Advance to next line. */ reg_nextline(); @@ -6232,24 +6269,22 @@ static void mb_decompose(int c, int *c1, int *c2, int *c3) } } -/* - * Compare two strings, ignore case if ireg_ic set. - * Return 0 if strings match, non-zero otherwise. - * Correct the length "*n" when composing characters are ignored. - */ +// Compare two strings, ignore case if rex.reg_ic set. +// Return 0 if strings match, non-zero otherwise. +// Correct the length "*n" when composing characters are ignored. static int cstrncmp(char_u *s1, char_u *s2, int *n) { int result; - if (!ireg_ic) + if (!rex.reg_ic) { result = STRNCMP(s1, s2, *n); - else { + } else { assert(*n >= 0); result = mb_strnicmp(s1, s2, (size_t)*n); } - /* if it failed and it's utf8 and we want to combineignore: */ - if (result != 0 && enc_utf8 && ireg_icombine) { + // if it failed and it's utf8 and we want to combineignore: + if (result != 0 && enc_utf8 && rex.reg_icombine) { char_u *str1, *str2; int c1, c2, c11, c12; int junk; @@ -6266,14 +6301,15 @@ static int cstrncmp(char_u *s1, char_u *s2, int *n) /* decompose the character if necessary, into 'base' characters * because I don't care about Arabic, I will hard-code the Hebrew * which I *do* care about! So sue me... */ - if (c1 != c2 && (!ireg_ic || utf_fold(c1) != utf_fold(c2))) { - /* decomposition necessary? */ + if (c1 != c2 && (!rex.reg_ic || utf_fold(c1) != utf_fold(c2))) { + // decomposition necessary? mb_decompose(c1, &c11, &junk, &junk); mb_decompose(c2, &c12, &junk, &junk); c1 = c11; c2 = c12; - if (c11 != c12 && (!ireg_ic || utf_fold(c11) != utf_fold(c12))) + if (c11 != c12 && (!rex.reg_ic || utf_fold(c11) != utf_fold(c12))) { break; + } } } result = c2 - c1; @@ -6291,7 +6327,7 @@ static inline char_u *cstrchr(const char_u *const s, const int c) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE { - if (!ireg_ic) { + if (!rex.reg_ic) { return vim_strchr(s, c); } @@ -6419,14 +6455,18 @@ char_u *regtilde(char_u *source, int magic) static int can_f_submatch = FALSE; /* TRUE when submatch() can be used */ -/* These pointers are used instead of reg_match and reg_mmatch for - * reg_submatch(). Needed for when the substitution string is an expression - * that contains a call to substitute() and submatch(). */ -static regmatch_T *submatch_match; -static regmmatch_T *submatch_mmatch; -static linenr_T submatch_firstlnum; -static linenr_T submatch_maxline; -static int submatch_line_lbr; +// These pointers are used for reg_submatch(). Needed for when the +// substitution string is an expression that contains a call to substitute() +// and submatch(). +typedef struct { + regmatch_T *sm_match; + regmmatch_T *sm_mmatch; + linenr_T sm_firstlnum; + linenr_T sm_maxline; + int sm_line_lbr; +} regsubmatch_T; + +static regsubmatch_T rsm; // can only be used when can_f_submatch is true /// Put the submatches in "argv[0]" which is a list passed into call_func() by /// vim_regsub_both(). @@ -6447,11 +6487,11 @@ static int fill_submatch_list(int argc, typval_T *argv, int argcount) // There are always 10 list items in staticList10_T. li = argv->vval.v_list->lv_first; for (i = 0; i < 10; i++) { - s = submatch_match->startp[i]; - if (s == NULL || submatch_match->endp[i] == NULL) { + s = rsm.sm_match->startp[i]; + if (s == NULL || rsm.sm_match->endp[i] == NULL) { s = NULL; } else { - s = vim_strnsave(s, (int)(submatch_match->endp[i] - s)); + s = vim_strnsave(s, (int)(rsm.sm_match->endp[i] - s)); } li->li_tv.v_type = VAR_STRING; li->li_tv.vval.v_string = s; @@ -6488,23 +6528,55 @@ static void clear_submatch_list(staticList10_T *sl) int vim_regsub(regmatch_T *rmp, char_u *source, typval_T *expr, char_u *dest, int copy, int magic, int backslash) { - reg_match = rmp; - reg_mmatch = NULL; - reg_maxline = 0; - reg_buf = curbuf; - reg_line_lbr = true; - return vim_regsub_both(source, expr, dest, copy, magic, backslash); + regexec_T rex_save; + bool rex_in_use_save = rex_in_use; + + if (rex_in_use) { + // Being called recursively, save the state. + rex_save = rex; + } + rex_in_use = true; + + rex.reg_match = rmp; + rex.reg_mmatch = NULL; + rex.reg_maxline = 0; + rex.reg_buf = curbuf; + rex.reg_line_lbr = true; + int result = vim_regsub_both(source, expr, dest, copy, magic, backslash); + + rex_in_use = rex_in_use_save; + if (rex_in_use) { + rex = rex_save; + } + + return result; } int vim_regsub_multi(regmmatch_T *rmp, linenr_T lnum, char_u *source, char_u *dest, int copy, int magic, int backslash) { - reg_match = NULL; - reg_mmatch = rmp; - reg_buf = curbuf; /* always works on the current buffer! */ - reg_firstlnum = lnum; - reg_maxline = curbuf->b_ml.ml_line_count - lnum; - reg_line_lbr = false; - return vim_regsub_both(source, NULL, dest, copy, magic, backslash); + regexec_T rex_save; + bool rex_in_use_save = rex_in_use; + + if (rex_in_use) { + // Being called recursively, save the state. + rex_save = rex; + } + rex_in_use = true; + + rex.reg_match = NULL; + rex.reg_mmatch = rmp; + rex.reg_buf = curbuf; // always works on the current buffer! + rex.reg_firstlnum = lnum; + rex.reg_maxline = curbuf->b_ml.ml_line_count - lnum; + rex.reg_line_lbr = false; + int result = vim_regsub_both(source, NULL, dest, copy, magic, backslash); + + rex_in_use = rex_in_use_save; + if (rex_in_use) { + rex = rex_save; + } + + return result; } static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, @@ -6533,8 +6605,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, dst = dest; // When the substitute part starts with "\=" evaluate it as an expression. - if (expr != NULL || (source[0] == '\\' && source[1] == '=' - && !can_f_submatch)) { // can't do this recursively + if (expr != NULL || (source[0] == '\\' && source[1] == '=')) { // To make sure that the length doesn't change between checking the // length and copying the string, and to speed up things, the // resulting string is saved from the call with "copy" == FALSE to the @@ -6547,24 +6618,23 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, eval_result = NULL; } } else { - win_T *save_reg_win; - int save_ireg_ic; - bool prev_can_f_submatch = can_f_submatch; + int prev_can_f_submatch = can_f_submatch; + regsubmatch_T rsm_save; xfree(eval_result); - /* The expression may contain substitute(), which calls us - * recursively. Make sure submatch() gets the text from the first - * level. Don't need to save "reg_buf", because - * vim_regexec_multi() can't be called recursively. */ - submatch_match = reg_match; - submatch_mmatch = reg_mmatch; - submatch_firstlnum = reg_firstlnum; - submatch_maxline = reg_maxline; - submatch_line_lbr = reg_line_lbr; - save_reg_win = reg_win; - save_ireg_ic = ireg_ic; + // The expression may contain substitute(), which calls us + // recursively. Make sure submatch() gets the text from the first + // level. + if (can_f_submatch) { + rsm_save = rsm; + } can_f_submatch = true; + rsm.sm_match = rex.reg_match; + rsm.sm_mmatch = rex.reg_mmatch; + rsm.sm_firstlnum = rex.reg_firstlnum; + rsm.sm_maxline = rex.reg_maxline; + rsm.sm_line_lbr = rex.reg_line_lbr; if (expr != NULL) { typval_T argv[2]; @@ -6574,29 +6644,25 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, rettv.v_type = VAR_STRING; rettv.vval.v_string = NULL; - if (prev_can_f_submatch) { - // can't do this recursively - } else { - argv[0].v_type = VAR_LIST; - argv[0].vval.v_list = &matchList.sl_list; - matchList.sl_list.lv_len = 0; - if (expr->v_type == VAR_FUNC) { - s = expr->vval.v_string; - call_func(s, (int)STRLEN(s), &rettv, 1, argv, - fill_submatch_list, 0L, 0L, &dummy, - true, NULL, NULL); - } else if (expr->v_type == VAR_PARTIAL) { - partial_T *partial = expr->vval.v_partial; - - s = partial_name(partial); - call_func(s, (int)STRLEN(s), &rettv, 1, argv, - fill_submatch_list, 0L, 0L, &dummy, - true, partial, NULL); - } - if (matchList.sl_list.lv_len > 0) { - // fill_submatch_list() was called. - clear_submatch_list(&matchList); - } + argv[0].v_type = VAR_LIST; + argv[0].vval.v_list = &matchList.sl_list; + matchList.sl_list.lv_len = 0; + if (expr->v_type == VAR_FUNC) { + s = expr->vval.v_string; + call_func(s, (int)STRLEN(s), &rettv, 1, argv, + fill_submatch_list, 0L, 0L, &dummy, + true, NULL, NULL); + } else if (expr->v_type == VAR_PARTIAL) { + partial_T *partial = expr->vval.v_partial; + + s = partial_name(partial); + call_func(s, (int)STRLEN(s), &rettv, 1, argv, + fill_submatch_list, 0L, 0L, &dummy, + true, partial, NULL); + } + if (matchList.sl_list.lv_len > 0) { + // fill_submatch_list() was called. + clear_submatch_list(&matchList); } char buf[NUMBUFLEN]; eval_result = (char_u *)tv_get_string_buf_chk(&rettv, buf); @@ -6612,22 +6678,23 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int had_backslash = FALSE; for (s = eval_result; *s != NUL; mb_ptr_adv(s)) { - /* Change NL to CR, so that it becomes a line break, - * unless called from vim_regexec_nl(). - * Skip over a backslashed character. */ - if (*s == NL && !submatch_line_lbr) + // Change NL to CR, so that it becomes a line break, + // unless called from vim_regexec_nl(). + // Skip over a backslashed character. + if (*s == NL && !rsm.sm_line_lbr) { *s = CAR; - else if (*s == '\\' && s[1] != NUL) { - ++s; + } else if (*s == '\\' && s[1] != NUL) { + s++; /* Change NL to CR here too, so that this works: * :s/abc\\\ndef/\="aaa\\\nbbb"/ on text: * abc\ * def * Not when called from vim_regexec_nl(). */ - if (*s == NL && !submatch_line_lbr) + if (*s == NL && !rsm.sm_line_lbr) { *s = CAR; - had_backslash = TRUE; + } + had_backslash = true; } } if (had_backslash && backslash) { @@ -6640,14 +6707,10 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, dst += STRLEN(eval_result); } - reg_match = submatch_match; - reg_mmatch = submatch_mmatch; - reg_firstlnum = submatch_firstlnum; - reg_maxline = submatch_maxline; - reg_line_lbr = submatch_line_lbr; - reg_win = save_reg_win; - ireg_ic = save_ireg_ic; - can_f_submatch = FALSE; + can_f_submatch = prev_can_f_submatch; + if (can_f_submatch) { + rsm = rsm_save; + } } } else while ((c = *src++) != NUL) { @@ -6745,43 +6808,50 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, dst++; } else { if (REG_MULTI) { - clnum = reg_mmatch->startpos[no].lnum; - if (clnum < 0 || reg_mmatch->endpos[no].lnum < 0) + clnum = rex.reg_mmatch->startpos[no].lnum; + if (clnum < 0 || rex.reg_mmatch->endpos[no].lnum < 0) { s = NULL; - else { - s = reg_getline(clnum) + reg_mmatch->startpos[no].col; - if (reg_mmatch->endpos[no].lnum == clnum) - len = reg_mmatch->endpos[no].col - - reg_mmatch->startpos[no].col; - else + } else { + s = reg_getline(clnum) + rex.reg_mmatch->startpos[no].col; + if (rex.reg_mmatch->endpos[no].lnum == clnum) { + len = rex.reg_mmatch->endpos[no].col + - rex.reg_mmatch->startpos[no].col; + } else { len = (int)STRLEN(s); + } } } else { - s = reg_match->startp[no]; - if (reg_match->endp[no] == NULL) + s = rex.reg_match->startp[no]; + if (rex.reg_match->endp[no] == NULL) { s = NULL; - else - len = (int)(reg_match->endp[no] - s); + } else { + len = (int)(rex.reg_match->endp[no] - s); + } } if (s != NULL) { for (;; ) { if (len == 0) { if (REG_MULTI) { - if (reg_mmatch->endpos[no].lnum == clnum) + if (rex.reg_mmatch->endpos[no].lnum == clnum) { break; - if (copy) + } + if (copy) { *dst = CAR; - ++dst; + } + dst++; s = reg_getline(++clnum); - if (reg_mmatch->endpos[no].lnum == clnum) - len = reg_mmatch->endpos[no].col; - else + if (rex.reg_mmatch->endpos[no].lnum == clnum) { + len = rex.reg_mmatch->endpos[no].col; + } else { len = (int)STRLEN(s); - } else + } + } else { break; - } else if (*s == NUL) { /* we hit NUL. */ - if (copy) + } + } else if (*s == NUL) { // we hit NUL. + if (copy) { EMSG(_(e_re_damg)); + } goto exit; } else { if (backslash && (*s == CAR || *s == '\\')) { @@ -6855,16 +6925,16 @@ exit: static char_u *reg_getline_submatch(linenr_T lnum) { char_u *s; - linenr_T save_first = reg_firstlnum; - linenr_T save_max = reg_maxline; + linenr_T save_first = rex.reg_firstlnum; + linenr_T save_max = rex.reg_maxline; - reg_firstlnum = submatch_firstlnum; - reg_maxline = submatch_maxline; + rex.reg_firstlnum = rsm.sm_firstlnum; + rex.reg_maxline = rsm.sm_maxline; s = reg_getline(lnum); - reg_firstlnum = save_first; - reg_maxline = save_max; + rex.reg_firstlnum = save_first; + rex.reg_maxline = save_max; return s; } @@ -6883,39 +6953,41 @@ char_u *reg_submatch(int no) if (!can_f_submatch || no < 0) return NULL; - if (submatch_match == NULL) { + if (rsm.sm_match == NULL) { ssize_t len; /* * First round: compute the length and allocate memory. * Second round: copy the text. */ - for (round = 1; round <= 2; ++round) { - lnum = submatch_mmatch->startpos[no].lnum; - if (lnum < 0 || submatch_mmatch->endpos[no].lnum < 0) + for (round = 1; round <= 2; round++) { + lnum = rsm.sm_mmatch->startpos[no].lnum; + if (lnum < 0 || rsm.sm_mmatch->endpos[no].lnum < 0) { return NULL; + } - s = reg_getline_submatch(lnum) + submatch_mmatch->startpos[no].col; - if (s == NULL) /* anti-crash check, cannot happen? */ + s = reg_getline_submatch(lnum) + rsm.sm_mmatch->startpos[no].col; + if (s == NULL) { // anti-crash check, cannot happen? break; - if (submatch_mmatch->endpos[no].lnum == lnum) { - /* Within one line: take form start to end col. */ - len = submatch_mmatch->endpos[no].col - - submatch_mmatch->startpos[no].col; - if (round == 2) + } + if (rsm.sm_mmatch->endpos[no].lnum == lnum) { + // Within one line: take form start to end col. + len = rsm.sm_mmatch->endpos[no].col - rsm.sm_mmatch->startpos[no].col; + if (round == 2) { STRLCPY(retval, s, len + 1); - ++len; + } + len++; } else { - /* Multiple lines: take start line from start col, middle - * lines completely and end line up to end col. */ - len = STRLEN(s); + // Multiple lines: take start line from start col, middle + // lines completely and end line up to end col. + len = (ssize_t)STRLEN(s); if (round == 2) { STRCPY(retval, s); retval[len] = '\n'; } - ++len; - ++lnum; - while (lnum < submatch_mmatch->endpos[no].lnum) { + len++; + lnum++; + while (lnum < rsm.sm_mmatch->endpos[no].lnum) { s = reg_getline_submatch(lnum++); if (round == 2) STRCPY(retval + len, s); @@ -6924,10 +6996,11 @@ char_u *reg_submatch(int no) retval[len] = '\n'; ++len; } - if (round == 2) + if (round == 2) { STRNCPY(retval + len, reg_getline_submatch(lnum), - submatch_mmatch->endpos[no].col); - len += submatch_mmatch->endpos[no].col; + rsm.sm_mmatch->endpos[no].col); + } + len += rsm.sm_mmatch->endpos[no].col; if (round == 2) { retval[len] = NUL; // -V595 } @@ -6939,11 +7012,12 @@ char_u *reg_submatch(int no) } } } else { - s = submatch_match->startp[no]; - if (s == NULL || submatch_match->endp[no] == NULL) + s = rsm.sm_match->startp[no]; + if (s == NULL || rsm.sm_match->endp[no] == NULL) { retval = NULL; - else - retval = vim_strnsave(s, (int)(submatch_match->endp[no] - s)); + } else { + retval = vim_strnsave(s, (int)(rsm.sm_match->endp[no] - s)); + } } return retval; @@ -6965,15 +7039,15 @@ list_T *reg_submatch_list(int no) list_T *list; const char *s; - if (submatch_match == NULL) { - slnum = submatch_mmatch->startpos[no].lnum; - elnum = submatch_mmatch->endpos[no].lnum; + if (rsm.sm_match == NULL) { + slnum = rsm.sm_mmatch->startpos[no].lnum; + elnum = rsm.sm_mmatch->endpos[no].lnum; if (slnum < 0 || elnum < 0) { return NULL; } - colnr_T scol = submatch_mmatch->startpos[no].col; - colnr_T ecol = submatch_mmatch->endpos[no].col; + colnr_T scol = rsm.sm_mmatch->startpos[no].col; + colnr_T ecol = rsm.sm_mmatch->endpos[no].col; list = tv_list_alloc(); @@ -6990,12 +7064,12 @@ list_T *reg_submatch_list(int no) tv_list_append_string(list, s, ecol); } } else { - s = (const char *)submatch_match->startp[no]; - if (s == NULL || submatch_match->endp[no] == NULL) { + s = (const char *)rsm.sm_match->startp[no]; + if (s == NULL || rsm.sm_match->endp[no] == NULL) { return NULL; } list = tv_list_alloc(); - tv_list_append_string(list, s, (const char *)submatch_match->endp[no] - s); + tv_list_append_string(list, s, (const char *)rsm.sm_match->endp[no] - s); } return list; @@ -7149,6 +7223,19 @@ static void report_re_switch(char_u *pat) /// @return TRUE if there is a match, FALSE if not. static int vim_regexec_both(regmatch_T *rmp, char_u *line, colnr_T col, bool nl) { + regexec_T rex_save; + bool rex_in_use_save = rex_in_use; + + if (rex_in_use) { + // Being called recursively, save the state. + rex_save = rex; + } + rex_in_use = true; + rex.reg_startp = NULL; + rex.reg_endp = NULL; + rex.reg_startpos = NULL; + rex.reg_endpos = NULL; + int result = rmp->regprog->engine->regexec_nl(rmp, line, col, nl); // NFA engine aborted because it's very slow, use backtracking engine instead. @@ -7170,6 +7257,11 @@ static int vim_regexec_both(regmatch_T *rmp, char_u *line, colnr_T col, bool nl) p_re = save_p_re; } + rex_in_use = rex_in_use_save; + if (rex_in_use) { + rex = rex_save; + } + return result > 0; } @@ -7217,6 +7309,15 @@ long vim_regexec_multi( proftime_T *tm /* timeout limit or NULL */ ) { + regexec_T rex_save; + bool rex_in_use_save = rex_in_use; + + if (rex_in_use) { + // Being called recursively, save the state. + rex_save = rex; + } + rex_in_use = true; + int result = rmp->regprog->engine->regexec_multi(rmp, win, buf, lnum, col, tm); @@ -7240,5 +7341,10 @@ long vim_regexec_multi( p_re = save_p_re; } + rex_in_use = rex_in_use_save; + if (rex_in_use) { + rex = rex_save; + } + return result <= 0 ? 0 : result; } diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c index 491693a371..5d708febea 100644 --- a/src/nvim/regexp_nfa.c +++ b/src/nvim/regexp_nfa.c @@ -4882,7 +4882,7 @@ static long find_match_text(colnr_T startcol, int regstart, char_u *match_text) int c2_len = PTR2LEN(s2); int c2 = PTR2CHAR(s2); - if ((c1 != c2 && (!ireg_ic || mb_tolower(c1) != mb_tolower(c2))) + if ((c1 != c2 && (!rex.reg_ic || mb_tolower(c1) != mb_tolower(c2))) || c1_len != c2_len) { match = false; break; @@ -4895,13 +4895,13 @@ static long find_match_text(colnr_T startcol, int regstart, char_u *match_text) && !(enc_utf8 && utf_iscomposing(PTR2CHAR(s2)))) { cleanup_subexpr(); if (REG_MULTI) { - reg_startpos[0].lnum = reglnum; - reg_startpos[0].col = col; - reg_endpos[0].lnum = reglnum; - reg_endpos[0].col = s2 - regline; + rex.reg_startpos[0].lnum = reglnum; + rex.reg_startpos[0].col = col; + rex.reg_endpos[0].lnum = reglnum; + rex.reg_endpos[0].col = s2 - regline; } else { - reg_startp[0] = regline + col; - reg_endp[0] = s2; + rex.reg_startp[0] = regline + col; + rex.reg_endp[0] = s2; } return 1L; } @@ -5116,8 +5116,8 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, case NFA_MATCH: { // If the match ends before a composing characters and - // ireg_icombine is not set, that is not really a match. - if (enc_utf8 && !ireg_icombine && utf_iscomposing(curc)) { + // rex.reg_icombine is not set, that is not really a match. + if (enc_utf8 && !rex.reg_icombine && utf_iscomposing(curc)) { break; } nfa_match = true; @@ -5400,15 +5400,15 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, int this_class; // Get class of current and previous char (if it exists). - this_class = mb_get_class_tab(reginput, reg_buf->b_chartab); + this_class = mb_get_class_tab(reginput, rex.reg_buf->b_chartab); if (this_class <= 1) { result = false; } else if (reg_prev_class() == this_class) { result = false; } - } else if (!vim_iswordc_buf(curc, reg_buf) + } else if (!vim_iswordc_buf(curc, rex.reg_buf) || (reginput > regline - && vim_iswordc_buf(reginput[-1], reg_buf))) { + && vim_iswordc_buf(reginput[-1], rex.reg_buf))) { result = false; } if (result) { @@ -5425,15 +5425,15 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, int this_class, prev_class; // Get class of current and previous char (if it exists). - this_class = mb_get_class_tab(reginput, reg_buf->b_chartab); + this_class = mb_get_class_tab(reginput, rex.reg_buf->b_chartab); prev_class = reg_prev_class(); if (this_class == prev_class || prev_class == 0 || prev_class == 1) { result = false; } - } else if (!vim_iswordc_buf(reginput[-1], reg_buf) + } else if (!vim_iswordc_buf(reginput[-1], rex.reg_buf) || (reginput[0] != NUL - && vim_iswordc_buf(curc, reg_buf))) { + && vim_iswordc_buf(curc, rex.reg_buf))) { result = false; } if (result) { @@ -5444,14 +5444,14 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, case NFA_BOF: if (reglnum == 0 && reginput == regline - && (!REG_MULTI || reg_firstlnum == 1)) { + && (!REG_MULTI || rex.reg_firstlnum == 1)) { add_here = true; add_state = t->state->out; } break; case NFA_EOF: - if (reglnum == reg_maxline && curc == NUL) { + if (reglnum == rex.reg_maxline && curc == NUL) { add_here = true; add_state = t->state->out; } @@ -5475,7 +5475,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, // (no preceding character). len += mb_char2len(mc); } - if (ireg_icombine && len == 0) { + if (rex.reg_icombine && len == 0) { // If \Z was present, then ignore composing characters. // When ignoring the base character this always matches. if (sta->c != curc) { @@ -5526,14 +5526,14 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, } case NFA_NEWL: - if (curc == NUL && !reg_line_lbr && REG_MULTI - && reglnum <= reg_maxline) { + if (curc == NUL && !rex.reg_line_lbr && REG_MULTI + && reglnum <= rex.reg_maxline) { go_to_nextline = true; // Pass -1 for the offset, which means taking the position // at the start of the next line. add_state = t->state->out; add_off = -1; - } else if (curc == '\n' && reg_line_lbr) { + } else if (curc == '\n' && rex.reg_line_lbr) { // match \n as if it is an ordinary character add_state = t->state->out; add_off = 1; @@ -5574,7 +5574,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, result = result_if_matched; break; } - if (ireg_ic) { + if (rex.reg_ic) { int curc_low = mb_tolower(curc); int done = false; @@ -5591,7 +5591,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, } } else if (state->c < 0 ? check_char_class(state->c, curc) : (curc == state->c - || (ireg_ic && mb_tolower(curc) + || (rex.reg_ic && mb_tolower(curc) == mb_tolower(state->c)))) { result = result_if_matched; break; @@ -5639,13 +5639,13 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, break; case NFA_KWORD: // \k - result = vim_iswordp_buf(reginput, reg_buf); + result = vim_iswordp_buf(reginput, rex.reg_buf); ADD_STATE_IF_MATCH(t->state); break; case NFA_SKWORD: // \K result = !ascii_isdigit(curc) - && vim_iswordp_buf(reginput, reg_buf); + && vim_iswordp_buf(reginput, rex.reg_buf); ADD_STATE_IF_MATCH(t->state); break; @@ -5760,24 +5760,24 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, break; case NFA_LOWER_IC: // [a-z] - result = ri_lower(curc) || (ireg_ic && ri_upper(curc)); + result = ri_lower(curc) || (rex.reg_ic && ri_upper(curc)); ADD_STATE_IF_MATCH(t->state); break; case NFA_NLOWER_IC: // [^a-z] result = curc != NUL - && !(ri_lower(curc) || (ireg_ic && ri_upper(curc))); + && !(ri_lower(curc) || (rex.reg_ic && ri_upper(curc))); ADD_STATE_IF_MATCH(t->state); break; case NFA_UPPER_IC: // [A-Z] - result = ri_upper(curc) || (ireg_ic && ri_lower(curc)); + result = ri_upper(curc) || (rex.reg_ic && ri_lower(curc)); ADD_STATE_IF_MATCH(t->state); break; case NFA_NUPPER_IC: // [^A-Z] result = curc != NUL - && !(ri_upper(curc) || (ireg_ic && ri_lower(curc))); + && !(ri_upper(curc) || (rex.reg_ic && ri_lower(curc))); ADD_STATE_IF_MATCH(t->state); break; @@ -5851,13 +5851,15 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, case NFA_LNUM_GT: case NFA_LNUM_LT: assert(t->state->val >= 0 - && !((reg_firstlnum > 0 && reglnum > LONG_MAX - reg_firstlnum) - || (reg_firstlnum <0 && reglnum < LONG_MIN + reg_firstlnum)) - && reglnum + reg_firstlnum >= 0); + && !((rex.reg_firstlnum > 0 + && reglnum > LONG_MAX - rex.reg_firstlnum) + || (rex.reg_firstlnum < 0 + && reglnum < LONG_MIN + rex.reg_firstlnum)) + && reglnum + rex.reg_firstlnum >= 0); result = (REG_MULTI && nfa_re_num_cmp((uintmax_t)t->state->val, t->state->c - NFA_LNUM, - (uintmax_t)(reglnum + reg_firstlnum))); + (uintmax_t)(reglnum + rex.reg_firstlnum))); if (result) { add_here = true; add_state = t->state->out; @@ -5893,7 +5895,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, } result = false; - win_T *wp = reg_win == NULL ? curwin : reg_win; + win_T *wp = rex.reg_win == NULL ? curwin : rex.reg_win; if (op == 1 && col - 1 > t->state->val && col > 100) { long ts = wp->w_buffer->b_p_ts; @@ -5920,18 +5922,18 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, case NFA_MARK_GT: case NFA_MARK_LT: { - pos_T *pos = getmark_buf(reg_buf, t->state->val, FALSE); + pos_T *pos = getmark_buf(rex.reg_buf, t->state->val, false); // Compare the mark position to the match position. result = (pos != NULL // mark doesn't exist && pos->lnum > 0 // mark isn't set in reg_buf - && (pos->lnum == reglnum + reg_firstlnum + && (pos->lnum == reglnum + rex.reg_firstlnum ? (pos->col == (colnr_T)(reginput - regline) ? t->state->c == NFA_MARK : (pos->col < (colnr_T)(reginput - regline) ? t->state->c == NFA_MARK_GT : t->state->c == NFA_MARK_LT)) - : (pos->lnum < reglnum + reg_firstlnum + : (pos->lnum < reglnum + rex.reg_firstlnum ? t->state->c == NFA_MARK_GT : t->state->c == NFA_MARK_LT))); if (result) { @@ -5942,10 +5944,10 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, } case NFA_CURSOR: - result = (reg_win != NULL - && (reglnum + reg_firstlnum == reg_win->w_cursor.lnum) + result = (rex.reg_win != NULL + && (reglnum + rex.reg_firstlnum == rex.reg_win->w_cursor.lnum) && ((colnr_T)(reginput - regline) - == reg_win->w_cursor.col)); + == rex.reg_win->w_cursor.col)); if (result) { add_here = true; add_state = t->state->out; @@ -5995,13 +5997,13 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, #endif result = (c == curc); - if (!result && ireg_ic) { + if (!result && rex.reg_ic) { result = mb_tolower(c) == mb_tolower(curc); } - // If ireg_icombine is not set only skip over the character + // If rex.reg_icombine is not set only skip over the character // itself. When it is set skip over composing characters. - if (result && enc_utf8 && !ireg_icombine) { + if (result && enc_utf8 && !rex.reg_icombine) { clen = utf_ptr2len(reginput); } @@ -6109,8 +6111,8 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, && ((toplevel && reglnum == 0 && clen != 0 - && (ireg_maxcol == 0 - || (colnr_T)(reginput - regline) < ireg_maxcol)) + && (rex.reg_maxcol == 0 + || (colnr_T)(reginput - regline) < rex.reg_maxcol)) || (nfa_endp != NULL && (REG_MULTI ? (reglnum < nfa_endp->se_u.pos.lnum @@ -6145,7 +6147,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, // Checking if the required start character matches is // cheaper than adding a state that won't match. c = PTR2CHAR(reginput + clen); - if (c != prog->regstart && (!ireg_ic || mb_tolower(c) + if (c != prog->regstart && (!rex.reg_ic || mb_tolower(c) != mb_tolower(prog->regstart))) { #ifdef REGEXP_DEBUG fprintf(log_fd, @@ -6271,34 +6273,37 @@ static long nfa_regtry(nfa_regprog_T *prog, colnr_T col, proftime_T *tm) cleanup_subexpr(); if (REG_MULTI) { for (i = 0; i < subs.norm.in_use; i++) { - reg_startpos[i].lnum = subs.norm.list.multi[i].start_lnum; - reg_startpos[i].col = subs.norm.list.multi[i].start_col; + rex.reg_startpos[i].lnum = subs.norm.list.multi[i].start_lnum; + rex.reg_startpos[i].col = subs.norm.list.multi[i].start_col; - reg_endpos[i].lnum = subs.norm.list.multi[i].end_lnum; - reg_endpos[i].col = subs.norm.list.multi[i].end_col; + rex.reg_endpos[i].lnum = subs.norm.list.multi[i].end_lnum; + rex.reg_endpos[i].col = subs.norm.list.multi[i].end_col; } - if (reg_startpos[0].lnum < 0) { - reg_startpos[0].lnum = 0; - reg_startpos[0].col = col; + if (rex.reg_startpos[0].lnum < 0) { + rex.reg_startpos[0].lnum = 0; + rex.reg_startpos[0].col = col; + } + if (rex.reg_endpos[0].lnum < 0) { + // pattern has a \ze but it didn't match, use current end + rex.reg_endpos[0].lnum = reglnum; + rex.reg_endpos[0].col = (int)(reginput - regline); + } else { + // Use line number of "\ze". + reglnum = rex.reg_endpos[0].lnum; } - if (reg_endpos[0].lnum < 0) { - /* pattern has a \ze but it didn't match, use current end */ - reg_endpos[0].lnum = reglnum; - reg_endpos[0].col = (int)(reginput - regline); - } else - /* Use line number of "\ze". */ - reglnum = reg_endpos[0].lnum; } else { for (i = 0; i < subs.norm.in_use; i++) { - reg_startp[i] = subs.norm.list.line[i].start; - reg_endp[i] = subs.norm.list.line[i].end; + rex.reg_startp[i] = subs.norm.list.line[i].start; + rex.reg_endp[i] = subs.norm.list.line[i].end; } - if (reg_startp[0] == NULL) - reg_startp[0] = regline + col; - if (reg_endp[0] == NULL) - reg_endp[0] = reginput; + if (rex.reg_startp[0] == NULL) { + rex.reg_startp[0] = regline + col; + } + if (rex.reg_endp[0] == NULL) { + rex.reg_endp[0] = reginput; + } } /* Package any found \z(...\) matches for export. Default is none. */ @@ -6352,14 +6357,14 @@ static long nfa_regexec_both(char_u *line, colnr_T startcol, proftime_T *tm) colnr_T col = startcol; if (REG_MULTI) { - prog = (nfa_regprog_T *)reg_mmatch->regprog; - line = reg_getline((linenr_T)0); /* relative to the cursor */ - reg_startpos = reg_mmatch->startpos; - reg_endpos = reg_mmatch->endpos; + prog = (nfa_regprog_T *)rex.reg_mmatch->regprog; + line = reg_getline((linenr_T)0); // relative to the cursor + rex.reg_startpos = rex.reg_mmatch->startpos; + rex.reg_endpos = rex.reg_mmatch->endpos; } else { - prog = (nfa_regprog_T *)reg_match->regprog; - reg_startp = reg_match->startp; - reg_endp = reg_match->endp; + prog = (nfa_regprog_T *)rex.reg_match->regprog; + rex.reg_startp = rex.reg_match->startp; + rex.reg_endp = rex.reg_match->endp; } /* Be paranoid... */ @@ -6368,15 +6373,17 @@ static long nfa_regexec_both(char_u *line, colnr_T startcol, proftime_T *tm) goto theend; } - /* If pattern contains "\c" or "\C": overrule value of ireg_ic */ - if (prog->regflags & RF_ICASE) - ireg_ic = TRUE; - else if (prog->regflags & RF_NOICASE) - ireg_ic = FALSE; + // If pattern contains "\c" or "\C": overrule value of rex.reg_ic + if (prog->regflags & RF_ICASE) { + rex.reg_ic = true; + } else if (prog->regflags & RF_NOICASE) { + rex.reg_ic = false; + } - /* If pattern contains "\Z" overrule value of ireg_icombine */ - if (prog->regflags & RF_ICOMBINE) - ireg_icombine = TRUE; + // If pattern contains "\Z" overrule value of rex.reg_icombine + if (prog->regflags & RF_ICOMBINE) { + rex.reg_icombine = true; + } regline = line; reglnum = 0; /* relative to line */ @@ -6405,17 +6412,17 @@ static long nfa_regexec_both(char_u *line, colnr_T startcol, proftime_T *tm) if (skip_to_start(prog->regstart, &col) == FAIL) return 0L; - /* If match_text is set it contains the full text that must match. - * Nothing else to try. Doesn't handle combining chars well. */ - if (prog->match_text != NULL - && !ireg_icombine - ) + // If match_text is set it contains the full text that must match. + // Nothing else to try. Doesn't handle combining chars well. + if (prog->match_text != NULL && !rex.reg_icombine) { return find_match_text(col, prog->regstart, prog->match_text); + } } - /* If the start column is past the maximum column: no need to try. */ - if (ireg_maxcol > 0 && col >= ireg_maxcol) + // If the start column is past the maximum column: no need to try. + if (rex.reg_maxcol > 0 && col >= rex.reg_maxcol) { goto theend; + } nstate = prog->nstate; for (i = 0; i < nstate; ++i) { @@ -6567,15 +6574,15 @@ nfa_regexec_nl ( bool line_lbr ) { - reg_match = rmp; - reg_mmatch = NULL; - reg_maxline = 0; - reg_line_lbr = line_lbr; - reg_buf = curbuf; - reg_win = NULL; - ireg_ic = rmp->rm_ic; - ireg_icombine = FALSE; - ireg_maxcol = 0; + rex.reg_match = rmp; + rex.reg_mmatch = NULL; + rex.reg_maxline = 0; + rex.reg_line_lbr = line_lbr; + rex.reg_buf = curbuf; + rex.reg_win = NULL; + rex.reg_ic = rmp->rm_ic; + rex.reg_icombine = false; + rex.reg_maxcol = 0; return nfa_regexec_both(line, col, NULL); } @@ -6616,16 +6623,16 @@ nfa_regexec_nl ( static long nfa_regexec_multi(regmmatch_T *rmp, win_T *win, buf_T *buf, linenr_T lnum, colnr_T col, proftime_T *tm) { - reg_match = NULL; - reg_mmatch = rmp; - reg_buf = buf; - reg_win = win; - reg_firstlnum = lnum; - reg_maxline = reg_buf->b_ml.ml_line_count - lnum; - reg_line_lbr = FALSE; - ireg_ic = rmp->rmm_ic; - ireg_icombine = FALSE; - ireg_maxcol = rmp->rmm_maxcol; + rex.reg_match = NULL; + rex.reg_mmatch = rmp; + rex.reg_buf = buf; + rex.reg_win = win; + rex.reg_firstlnum = lnum; + rex.reg_maxline = rex.reg_buf->b_ml.ml_line_count - lnum; + rex.reg_line_lbr = false; + rex.reg_ic = rmp->rmm_ic; + rex.reg_icombine = false; + rex.reg_maxcol = rmp->rmm_maxcol; return nfa_regexec_both(NULL, col, tm); } diff --git a/src/nvim/screen.c b/src/nvim/screen.c index a8353153fd..bcc996679b 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -991,7 +991,7 @@ static void win_update(win_T *wp) * first. */ if (mid_start == 0) { mid_end = wp->w_height; - if (lastwin == firstwin) { + if (ONE_WINDOW) { /* Clear the screen when it was not done by win_del_lines() or * win_ins_lines() above, "screen_cleared" is FALSE or MAYBE * then. */ @@ -7160,7 +7160,7 @@ static int fillchar_status(int *attr, win_T *wp) * window differs, or the fillchars differ, or this is not the * current window */ if (*attr != 0 && ((win_hl_attr(wp, HLF_S) != win_hl_attr(wp, HLF_SNC) - || !is_curwin || firstwin == lastwin) + || !is_curwin || ONE_WINDOW) || (fill_stl != fill_stlnc))) { return fill; } diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index 117ba52eb6..39ffabc024 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -79,7 +79,11 @@ let $NVIM_LOG_FILE = exists($NVIM_LOG_FILE) ? $NVIM_LOG_FILE : 'Xnvim.log' func RunTheTest(test) echo 'Executing ' . a:test if exists("*SetUp") - call SetUp() + try + call SetUp() + catch + call add(v:errors, 'Caught exception in SetUp() before ' . a:test . ': ' . v:exception . ' @ ' . v:throwpoint) + endtry endif call add(s:messages, 'Executing ' . a:test) @@ -94,7 +98,11 @@ func RunTheTest(test) endtry if exists("*TearDown") - call TearDown() + try + call TearDown() + catch + call add(v:errors, 'Caught exception in TearDown() after ' . a:test . ': ' . v:exception . ' @ ' . v:throwpoint) + endtry endif " Close any extra windows and make the current one not modified. diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index 1103778107..d55170c27c 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -14,6 +14,7 @@ source test_float_func.vim source test_functions.vim source test_goto.vim source test_jumps.vim +source test_fileformat.vim source test_lambda.vim source test_menu.vim source test_mapping.vim diff --git a/src/nvim/testdir/test_bufwintabinfo.vim b/src/nvim/testdir/test_bufwintabinfo.vim index 5c916e2dd7..1c9350c416 100644 --- a/src/nvim/testdir/test_bufwintabinfo.vim +++ b/src/nvim/testdir/test_bufwintabinfo.vim @@ -87,9 +87,17 @@ function Test_get_buf_options() endfunc function Test_get_win_options() + if has('folding') + set foldlevel=999 + endif + set list let opts = getwinvar(1, '&') call assert_equal(v:t_dict, type(opts)) call assert_equal(0, opts.linebreak) + call assert_equal(1, opts.list) + if has('folding') + call assert_equal(999, opts.foldlevel) + endif if has('signs') call assert_equal('auto', opts.signcolumn) endif @@ -97,7 +105,12 @@ function Test_get_win_options() let opts = gettabwinvar(1, 1, '&') call assert_equal(v:t_dict, type(opts)) call assert_equal(0, opts.linebreak) + call assert_equal(1, opts.list) if has('signs') call assert_equal('auto', opts.signcolumn) endif + set list& + if has('folding') + set foldlevel=0 + endif endfunc diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index 0c9d1297d6..c3bfae1b7b 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -130,6 +130,11 @@ func Test_getcompletion() let l = getcompletion('dark', 'highlight') call assert_equal([], l) + let l = getcompletion('', 'messages') + call assert_true(index(l, 'clear') >= 0) + let l = getcompletion('not', 'messages') + call assert_equal([], l) + if has('cscope') let l = getcompletion('', 'cscope') let cmds = ['add', 'find', 'help', 'kill', 'reset', 'show'] @@ -204,3 +209,26 @@ func Test_expand_star_star() bwipe! call delete('a', 'rf') endfunc + +func Test_paste_in_cmdline() + let @a = "def" + call feedkeys(":abc \<C-R>a ghi\<C-B>\"\<CR>", 'tx') + call assert_equal('"abc def ghi', @:) + + new + call setline(1, 'asdf.x /tmp/some verylongword a;b-c*d ') + + call feedkeys(":aaa \<C-R>\<C-W> bbb\<C-B>\"\<CR>", 'tx') + call assert_equal('"aaa asdf bbb', @:) + + call feedkeys("ft:aaa \<C-R>\<C-F> bbb\<C-B>\"\<CR>", 'tx') + call assert_equal('"aaa /tmp/some bbb', @:) + + set incsearch + call feedkeys("fy:aaa veryl\<C-R>\<C-W> bbb\<C-B>\"\<CR>", 'tx') + call assert_equal('"aaa verylongword bbb', @:) + + call feedkeys("f;:aaa \<C-R>\<C-A> bbb\<C-B>\"\<CR>", 'tx') + call assert_equal('"aaa a;b-c*d bbb', @:) + bwipe! +endfunc diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim index 82c5d21bd0..d32facaa98 100644 --- a/src/nvim/testdir/test_expr.vim +++ b/src/nvim/testdir/test_expr.vim @@ -384,9 +384,10 @@ func Test_substitute_expr() \ {-> submatch(2) . submatch(3) . submatch(1)}, '')) func Recurse() - return substitute('yyy', 'y*', {-> g:val}, '') + return substitute('yyy', 'y\(.\)y', {-> submatch(1)}, '') endfunc - call assert_equal('--', substitute('xxx', 'x*', {-> '-' . Recurse() . '-'}, '')) + " recursive call works + call assert_equal('-y-x-', substitute('xxx', 'x\(.\)x', {-> '-' . Recurse() . '-' . submatch(1) . '-'}, '')) endfunc func Test_invalid_submatch() @@ -418,6 +419,9 @@ func Test_function_with_funcref() let s:fref = function(s:f) call assert_equal(v:t_string, s:fref('x')) call assert_fails("call function('s:f')", 'E700:') + + call assert_fails("call function('foo()')", 'E475:') + call assert_fails("call function('foo()')", 'foo()') endfunc func Test_funcref() diff --git a/src/nvim/testdir/test_fileformat.vim b/src/nvim/testdir/test_fileformat.vim new file mode 100644 index 0000000000..584f20cdfc --- /dev/null +++ b/src/nvim/testdir/test_fileformat.vim @@ -0,0 +1,17 @@ +" Test behavior of fileformat after bwipeout of last buffer + +func Test_fileformat_after_bw() + bwipeout + set fileformat& + if &fileformat == 'dos' + let test_fileformats = 'unix' + elseif &fileformat == 'unix' + let test_fileformats = 'mac' + else " must be mac + let test_fileformats = 'dos' + endif + exec 'set fileformats='.test_fileformats + bwipeout! + call assert_equal(test_fileformats, &fileformat) + set fileformats& +endfunc diff --git a/src/nvim/testdir/test_goto.vim b/src/nvim/testdir/test_goto.vim index b6ac5720c3..2573401707 100644 --- a/src/nvim/testdir/test_goto.vim +++ b/src/nvim/testdir/test_goto.vim @@ -1,22 +1,277 @@ " Test commands that jump somewhere. -func Test_geeDEE() +" Create a new buffer using "lines" and place the cursor on the word after the +" first occurrence of return and invoke "cmd". The cursor should now be +" positioned at the given line and col. +func XTest_goto_decl(cmd, lines, line, col) new - call setline(1, ["Filename x;", "", "int Filename", "int func() {", "Filename y;"]) - /y;/ - normal gD - call assert_equal(1, line('.')) + call setline(1, a:lines) + /return/ + normal! W + execute 'norm! ' . a:cmd + call assert_equal(a:line, line('.')) + call assert_equal(a:col, col('.')) quit! endfunc -func Test_gee_dee() - new - call setline(1, ["int x;", "", "int func(int x)", "{", " return x;", "}"]) - /return/ - normal $hgd - call assert_equal(3, line('.')) - call assert_equal(14, col('.')) - quit! +func Test_gD() + let lines = [ + \ 'int x;', + \ '', + \ 'int func(void)', + \ '{', + \ ' return x;', + \ '}', + \ ] + call XTest_goto_decl('gD', lines, 1, 5) +endfunc + +func Test_gD_too() + let lines = [ + \ 'Filename x;', + \ '', + \ 'int Filename', + \ 'int func() {', + \ ' Filename x;', + \ ' return x;', + \ ] + call XTest_goto_decl('gD', lines, 1, 10) +endfunc + +func Test_gD_comment() + let lines = [ + \ '/* int x; */', + \ 'int x;', + \ '', + \ 'int func(void)', + \ '{', + \ ' return x;', + \ '}', + \ ] + call XTest_goto_decl('gD', lines, 2, 5) +endfunc + +func Test_gD_inline_comment() + let lines = [ + \ 'int y /* , x */;', + \ 'int x;', + \ '', + \ 'int func(void)', + \ '{', + \ ' return x;', + \ '}', + \ ] + call XTest_goto_decl('gD', lines, 2, 5) +endfunc + +func Test_gD_string() + let lines = [ + \ 'char *s[] = "x";', + \ 'int x = 1;', + \ '', + \ 'int func(void)', + \ '{', + \ ' return x;', + \ '}', + \ ] + call XTest_goto_decl('gD', lines, 2, 5) +endfunc + +func Test_gD_string_same_line() + let lines = [ + \ 'char *s[] = "x", int x = 1;', + \ '', + \ 'int func(void)', + \ '{', + \ ' return x;', + \ '}', + \ ] + call XTest_goto_decl('gD', lines, 1, 22) +endfunc + +func Test_gD_char() + let lines = [ + \ "char c = 'x';", + \ 'int x = 1;', + \ '', + \ 'int func(void)', + \ '{', + \ ' return x;', + \ '}', + \ ] + call XTest_goto_decl('gD', lines, 2, 5) +endfunc + +func Test_gd() + let lines = [ + \ 'int x;', + \ '', + \ 'int func(int x)', + \ '{', + \ ' return x;', + \ '}', + \ ] + call XTest_goto_decl('gd', lines, 3, 14) +endfunc + +func Test_gd_not_local() + let lines = [ + \ 'int func1(void)', + \ '{', + \ ' return x;', + \ '}', + \ '', + \ 'int func2(int x)', + \ '{', + \ ' return x;', + \ '}', + \ ] + call XTest_goto_decl('gd', lines, 3, 10) +endfunc + +func Test_gd_kr_style() + let lines = [ + \ 'int func(x)', + \ ' int x;', + \ '{', + \ ' return x;', + \ '}', + \ ] + call XTest_goto_decl('gd', lines, 2, 7) +endfunc + +func Test_gd_missing_braces() + let lines = [ + \ 'def func1(a)', + \ ' a + 1', + \ 'end', + \ '', + \ 'a = 1', + \ '', + \ 'def func2()', + \ ' return a', + \ 'end', + \ ] + call XTest_goto_decl('gd', lines, 1, 11) +endfunc + +func Test_gd_comment() + let lines = [ + \ 'int func(void)', + \ '{', + \ ' /* int x; */', + \ ' int x;', + \ ' return x;', + \ '}', + \] + call XTest_goto_decl('gd', lines, 4, 7) +endfunc + +func Test_gd_comment_in_string() + let lines = [ + \ 'int func(void)', + \ '{', + \ ' char *s ="//"; int x;', + \ ' int x;', + \ ' return x;', + \ '}', + \] + call XTest_goto_decl('gd', lines, 3, 22) +endfunc + +func Test_gd_string_in_comment() + set comments= + let lines = [ + \ 'int func(void)', + \ '{', + \ ' /* " */ int x;', + \ ' int x;', + \ ' return x;', + \ '}', + \] + call XTest_goto_decl('gd', lines, 3, 15) + set comments& +endfunc + +func Test_gd_inline_comment() + let lines = [ + \ 'int func(/* x is an int */ int x)', + \ '{', + \ ' return x;', + \ '}', + \ ] + call XTest_goto_decl('gd', lines, 1, 32) +endfunc + +func Test_gd_inline_comment_only() + let lines = [ + \ 'int func(void) /* one lonely x */', + \ '{', + \ ' return x;', + \ '}', + \ ] + call XTest_goto_decl('gd', lines, 3, 10) +endfunc + +func Test_gd_inline_comment_body() + let lines = [ + \ 'int func(void)', + \ '{', + \ ' int y /* , x */;', + \ '', + \ ' for (/* int x = 0 */; y < 2; y++);', + \ '', + \ ' int x = 0;', + \ '', + \ ' return x;', + \ '}', + \ ] + call XTest_goto_decl('gd', lines, 7, 7) +endfunc + +func Test_gd_trailing_multiline_comment() + let lines = [ + \ 'int func(int x) /* x is an int */', + \ '{', + \ ' return x;', + \ '}', + \ ] + call XTest_goto_decl('gd', lines, 1, 14) +endfunc + +func Test_gd_trailing_comment() + let lines = [ + \ 'int func(int x) // x is an int', + \ '{', + \ ' return x;', + \ '}', + \ ] + call XTest_goto_decl('gd', lines, 1, 14) +endfunc + +func Test_gd_string() + let lines = [ + \ 'int func(void)', + \ '{', + \ ' char *s = "x";', + \ ' int x = 1;', + \ '', + \ ' return x;', + \ '}', + \ ] + call XTest_goto_decl('gd', lines, 4, 7) +endfunc + +func Test_gd_string_only() + let lines = [ + \ 'int func(void)', + \ '{', + \ ' char *s = "x";', + \ '', + \ ' return x;', + \ '}', + \ ] + call XTest_goto_decl('gd', lines, 5, 10) endfunc " Check that setting 'cursorline' does not change curswant diff --git a/src/nvim/testdir/test_matchadd_conceal.vim b/src/nvim/testdir/test_matchadd_conceal.vim index c788689e33..c11f1a84a9 100644 --- a/src/nvim/testdir/test_matchadd_conceal.vim +++ b/src/nvim/testdir/test_matchadd_conceal.vim @@ -277,6 +277,7 @@ function! Test_matchadd_and_syn_conceal() call assert_notequal(screenattr(1, 11) , screenattr(1, 12)) call assert_equal(screenattr(1, 11) , screenattr(1, 32)) call matchadd('CheckedByCoq', '\%<2l\%>9c\%<16c') + redraw! call assert_equal(expect, s:screenline(1)) call assert_notequal(screenattr(1, 10) , screenattr(1, 11)) call assert_notequal(screenattr(1, 11) , screenattr(1, 12)) diff --git a/src/nvim/testdir/test_nested_function.vim b/src/nvim/testdir/test_nested_function.vim index f881730529..7e301ed33e 100644 --- a/src/nvim/testdir/test_nested_function.vim +++ b/src/nvim/testdir/test_nested_function.vim @@ -1,32 +1,42 @@ "Tests for nested functions " -function! NestedFunc() - fu! Func1() +func NestedFunc() + func! Func1() let g:text .= 'Func1 ' - endfunction + endfunc call Func1() - fu! s:func2() + func! s:func2() let g:text .= 's:func2 ' - endfunction + endfunc call s:func2() - fu! s:_func3() + func! s:_func3() let g:text .= 's:_func3 ' - endfunction + endfunc call s:_func3() let fn = 'Func4' - fu! {fn}() + func! {fn}() let g:text .= 'Func4 ' - endfunction + endfunc call {fn}() let fn = 'func5' - fu! s:{fn}() + func! s:{fn}() let g:text .= 's:func5' - endfunction + endfunc call s:{fn}() -endfunction +endfunc -function! Test_nested_functions() +func Test_nested_functions() let g:text = '' call NestedFunc() call assert_equal('Func1 s:func2 s:_func3 Func4 s:func5', g:text) endfunction + +func Test_nested_argument() + func g:X() + let g:Y = function('sort') + endfunc + let g:Y = function('sort') + echo g:Y([], g:X()) + delfunc g:X + unlet g:Y +endfunc diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim index 6261625801..4747d5704d 100644 --- a/src/nvim/testdir/test_normal.vim +++ b/src/nvim/testdir/test_normal.vim @@ -1255,21 +1255,27 @@ func! Test_normal22_zet() " Test for ZZ " let shell = &shell " let &shell = 'sh' - call writefile(['1', '2'], 'Xfile') + + " Remove any stale test files from previous run. + for file in ['Xfile_Test_normal22_zet'] + call delete(file) + endfor + + call writefile(['1', '2'], 'Xfile_Test_normal22_zet') let args = ' --headless -u NONE -N -U NONE -i NONE --noplugins' - call system(v:progpath . args . ' -c "%d" -c ":norm! ZZ" Xfile') - let a = readfile('Xfile') + call system(v:progpath . args . ' -c "%d" -c ":norm! ZZ" Xfile_Test_normal22_zet') + let a = readfile('Xfile_Test_normal22_zet') call assert_equal([], a) " Test for ZQ - call writefile(['1', '2'], 'Xfile') - call system(v:progpath . args . ' -c "%d" -c ":norm! ZQ" Xfile') - let a = readfile('Xfile') + call writefile(['1', '2'], 'Xfile_Test_normal22_zet') + call system(v:progpath . args . ' -c "%d" -c ":norm! ZQ" Xfile_Test_normal22_zet') + let a = readfile('Xfile_Test_normal22_zet') call assert_equal(['1', '2'], a) - " clean up - for file in ['Xfile'] - call delete(file) - endfor + " Nvim: This sometimes hangs the TSAN build. + " for file in ['Xfile_Test_normal22_zet'] + " call delete(file) + " endfor " let &shell = shell endfunc diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 75ab01f013..aff5fc2eed 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -6,7 +6,7 @@ endif set encoding=utf-8 -function! s:setup_commands(cchar) +func s:setup_commands(cchar) if a:cchar == 'c' command! -nargs=* -bang Xlist <mods>clist<bang> <args> command! -nargs=* Xgetexpr <mods>cgetexpr <args> @@ -68,10 +68,10 @@ function! s:setup_commands(cchar) let g:Xgetlist = function('getloclist', [0]) let g:Xsetlist = function('setloclist', [0]) endif -endfunction +endfunc " Tests for the :clist and :llist commands -function XlistTests(cchar) +func XlistTests(cchar) call s:setup_commands(a:cchar) " With an empty list, command should return error @@ -128,17 +128,17 @@ function XlistTests(cchar) let l = split(result, "\n") call assert_equal([' 2 Xtestfile1:1 col 3: Line1', \ ' 3: non-error 2', ' 4 Xtestfile2:2 col 2: Line2'], l) -endfunction +endfunc -function Test_clist() +func Test_clist() call XlistTests('c') call XlistTests('l') -endfunction +endfunc " Tests for the :colder, :cnewer, :lolder and :lnewer commands " Note that this test assumes that a quickfix/location list is " already set by the caller. -function XageTests(cchar) +func XageTests(cchar) call s:setup_commands(a:cchar) " Jumping to a non existent list should return error @@ -171,20 +171,20 @@ function XageTests(cchar) Xnewer 2 let l = g:Xgetlist() call assert_equal('Line3', l[0].text) -endfunction +endfunc -function Test_cage() +func Test_cage() let list = [{'bufnr': 1, 'lnum': 1}] call setqflist(list) call XageTests('c') call setloclist(0, list) call XageTests('l') -endfunction +endfunc " Tests for the :cwindow, :lwindow :cclose, :lclose, :copen and :lopen " commands -function XwindowTests(cchar) +func XwindowTests(cchar) call s:setup_commands(a:cchar) " Create a list with no valid entries @@ -227,16 +227,16 @@ function XwindowTests(cchar) " Calling cwindow should close the quickfix window with no valid errors Xwindow call assert_true(winnr('$') == 1) -endfunction +endfunc -function Test_cwindow() +func Test_cwindow() call XwindowTests('c') call XwindowTests('l') -endfunction +endfunc " Tests for the :cfile, :lfile, :caddfile, :laddfile, :cgetfile and :lgetfile " commands. -function XfileTests(cchar) +func XfileTests(cchar) call s:setup_commands(a:cchar) call writefile(['Xtestfile1:700:10:Line 700', @@ -275,16 +275,16 @@ function XfileTests(cchar) \ l[1].lnum == 333 && l[1].col == 88 && l[1].text ==# 'Line 333') call delete('Xqftestfile1') -endfunction +endfunc -function Test_cfile() +func Test_cfile() call XfileTests('c') call XfileTests('l') -endfunction +endfunc " Tests for the :cbuffer, :lbuffer, :caddbuffer, :laddbuffer, :cgetbuffer and " :lgetbuffer commands. -function XbufferTests(cchar) +func XbufferTests(cchar) call s:setup_commands(a:cchar) enew! @@ -316,26 +316,26 @@ function XbufferTests(cchar) \ l[3].lnum == 750 && l[3].col == 25 && l[3].text ==# 'Line 750') enew! -endfunction +endfunc -function Test_cbuffer() +func Test_cbuffer() call XbufferTests('c') call XbufferTests('l') -endfunction +endfunc -function XexprTests(cchar) +func XexprTests(cchar) call s:setup_commands(a:cchar) call assert_fails('Xexpr 10', 'E777:') -endfunction +endfunc -function Test_cexpr() +func Test_cexpr() call XexprTests('c') call XexprTests('l') -endfunction +endfunc " Tests for :cnext, :cprev, :cfirst, :clast commands -function Xtest_browse(cchar) +func Xtest_browse(cchar) call s:setup_commands(a:cchar) call s:create_test_file('Xqftestfile1') @@ -366,14 +366,14 @@ function Xtest_browse(cchar) call delete('Xqftestfile1') call delete('Xqftestfile2') -endfunction +endfunc -function Test_browse() +func Test_browse() call Xtest_browse('c') call Xtest_browse('l') -endfunction +endfunc -function! s:test_xhelpgrep(cchar) +func s:test_xhelpgrep(cchar) call s:setup_commands(a:cchar) Xhelpgrep quickfix Xopen @@ -385,9 +385,9 @@ function! s:test_xhelpgrep(cchar) call assert_true(w:quickfix_title =~ title_text, w:quickfix_title) " This wipes out the buffer, make sure that doesn't cause trouble. Xclose -endfunction +endfunc -function Test_helpgrep() +func Test_helpgrep() call s:test_xhelpgrep('c') helpclose call s:test_xhelpgrep('l') @@ -425,7 +425,7 @@ func Test_vimgreptitle() augroup! QfBufWinEnter endfunc -function XqfTitleTests(cchar) +func XqfTitleTests(cchar) call s:setup_commands(a:cchar) Xgetexpr ['file:1:1:message'] @@ -444,16 +444,16 @@ function XqfTitleTests(cchar) endif call assert_equal(title, w:quickfix_title) Xclose -endfunction +endfunc " Tests for quickfix window's title -function Test_qf_title() +func Test_qf_title() call XqfTitleTests('c') call XqfTitleTests('l') -endfunction +endfunc " Tests for 'errorformat' -function Test_efm() +func Test_efm() let save_efm = &efm set efm=%EEEE%m,%WWWW%m,%+CCCC%.%#,%-GGGG%.%# cgetexpr ['WWWW', 'EEEE', 'CCCC'] @@ -466,7 +466,7 @@ function Test_efm() let l = strtrans(string(map(getqflist(), '[v:val.text, v:val.valid]'))) call assert_equal("[['W', 1], ['ZZZZ', 0], ['E^@CCCC', 1], ['YYYY', 0]]", l) let &efm = save_efm -endfunction +endfunc " This will test for problems in quickfix: " A. incorrectly copying location lists which caused the location list to show @@ -477,7 +477,7 @@ endfunction " window it belongs to. " " Set up the test environment: -function! ReadTestProtocol(name) +func ReadTestProtocol(name) let base = substitute(a:name, '\v^test://(.*)%(\.[^.]+)?', '\1', '') let word = substitute(base, '\v(.*)\..*', '\1', '') @@ -496,9 +496,9 @@ function! ReadTestProtocol(name) setl nomodifiable setl readonly exe 'doautocmd BufRead ' . substitute(a:name, '\v^test://(.*)', '\1', '') -endfunction +endfunc -function Test_locationlist() +func Test_locationlist() enew augroup testgroup @@ -578,15 +578,15 @@ function Test_locationlist() wincmd n | only augroup! testgroup -endfunction + endfunc -function Test_locationlist_curwin_was_closed() +func Test_locationlist_curwin_was_closed() augroup testgroup au! autocmd BufReadCmd test_curwin.txt call R(expand("<amatch>")) augroup END - function! R(n) + func! R(n) quit endfunc @@ -597,9 +597,9 @@ function Test_locationlist_curwin_was_closed() call assert_fails('lrewind', 'E924:') augroup! testgroup -endfunction + endfunc -function Test_locationlist_cross_tab_jump() +func Test_locationlist_cross_tab_jump() call writefile(['loclistfoo'], 'loclistfoo') call writefile(['loclistbar'], 'loclistbar') set switchbuf=usetab @@ -613,10 +613,10 @@ function Test_locationlist_cross_tab_jump() set switchbuf&vim call delete('loclistfoo') call delete('loclistbar') -endfunction +endfunc " More tests for 'errorformat' -function! Test_efm1() +func Test_efm1() if !has('unix') " The 'errorformat' setting is different on non-Unix systems. " This test works only on Unix-like systems. @@ -734,10 +734,10 @@ function! Test_efm1() call delete('Xerrorfile1') call delete('Xerrorfile2') call delete('Xtestfile') -endfunction + endfunc " Test for quickfix directory stack support -function! s:dir_stack_tests(cchar) +func s:dir_stack_tests(cchar) call s:setup_commands(a:cchar) let save_efm=&efm @@ -779,10 +779,10 @@ function! s:dir_stack_tests(cchar) call assert_equal(5, qf[11].lnum) let &efm=save_efm -endfunction +endfunc " Tests for %D and %X errorformat options -function! Test_efm_dirstack() +func Test_efm_dirstack() " Create the directory stack and files call mkdir('dir1') call mkdir('dir1/a') @@ -814,10 +814,33 @@ function! Test_efm_dirstack() call delete('dir1', 'rf') call delete('dir2', 'rf') call delete('habits1.txt') -endfunction +endfunc + +" Test for resync after continuing an ignored message +func Xefm_ignore_continuations(cchar) + call s:setup_commands(a:cchar) + + let save_efm = &efm + + let &efm = + \ '%Eerror %m %l,' . + \ '%-Wignored %m %l,' . + \ '%+Cmore ignored %m %l,' . + \ '%Zignored end' + Xgetexpr ['ignored warning 1', 'more ignored continuation 2', 'ignored end', 'error resync 4'] + let l = map(g:Xgetlist(), '[v:val.text, v:val.valid, v:val.lnum, v:val.type]') + call assert_equal([['resync', 1, 4, 'E']], l) + + let &efm = save_efm +endfunc + +func Test_efm_ignore_continuations() + call Xefm_ignore_continuations('c') + call Xefm_ignore_continuations('l') +endfunc " Tests for invalid error format specifies -function Xinvalid_efm_Tests(cchar) +func Xinvalid_efm_Tests(cchar) call s:setup_commands(a:cchar) let save_efm = &efm @@ -850,17 +873,17 @@ function Xinvalid_efm_Tests(cchar) call assert_fails('Xexpr ["Entering dir abc", "abc.txt:1:Hello world"]', 'E379:') let &efm = save_efm -endfunction +endfunc -function Test_invalid_efm() +func Test_invalid_efm() call Xinvalid_efm_Tests('c') call Xinvalid_efm_Tests('l') -endfunction +endfunc " TODO: " Add tests for the following formats in 'errorformat' " %r %O -function! Test_efm2() +func Test_efm2() let save_efm = &efm " Test for %s format in efm @@ -946,19 +969,19 @@ function! Test_efm2() call assert_equal('unittests/dbfacadeTest.py', bufname(l[4].bufnr)) let &efm = save_efm -endfunction +endfunc -function XquickfixChangedByAutocmd(cchar) +func XquickfixChangedByAutocmd(cchar) call s:setup_commands(a:cchar) if a:cchar == 'c' let ErrorNr = 'E925' - function! ReadFunc() + func! ReadFunc() colder cgetexpr [] endfunc else let ErrorNr = 'E926' - function! ReadFunc() + func! ReadFunc() lolder lgetexpr [] endfunc @@ -981,10 +1004,10 @@ function XquickfixChangedByAutocmd(cchar) augroup! testgroup endfunc -function Test_quickfix_was_changed_by_autocmd() +func Test_quickfix_was_changed_by_autocmd() call XquickfixChangedByAutocmd('c') call XquickfixChangedByAutocmd('l') -endfunction +endfunc func Test_caddbuffer_to_empty() helpgr quickfix @@ -1006,7 +1029,7 @@ func Test_cgetexpr_works() endfunc " Tests for the setqflist() and setloclist() functions -function SetXlistTests(cchar, bnum) +func SetXlistTests(cchar, bnum) call s:setup_commands(a:cchar) call g:Xsetlist([{'bufnr': a:bnum, 'lnum': 1}, @@ -1041,9 +1064,9 @@ function SetXlistTests(cchar, bnum) call g:Xsetlist([]) let l = g:Xgetlist() call assert_equal(0, len(l)) -endfunction +endfunc -function Test_setqflist() +func Test_setqflist() new Xtestfile | only let bnum = bufnr('%') call setline(1, range(1,5)) @@ -1053,9 +1076,9 @@ function Test_setqflist() enew! call delete('Xtestfile') -endfunction +endfunc -function Xlist_empty_middle(cchar) +func Xlist_empty_middle(cchar) call s:setup_commands(a:cchar) " create three quickfix lists @@ -1078,12 +1101,12 @@ function Xlist_empty_middle(cchar) call assert_equal(matchlen, len(g:Xgetlist())) endfunc -function Test_setqflist_empty_middle() +func Test_setqflist_empty_middle() call Xlist_empty_middle('c') call Xlist_empty_middle('l') -endfunction +endfunc -function Xlist_empty_older(cchar) +func Xlist_empty_older(cchar) call s:setup_commands(a:cchar) " create three quickfix lists @@ -1104,14 +1127,14 @@ function Xlist_empty_older(cchar) call assert_equal(twolen, len(g:Xgetlist())) Xnewer call assert_equal(threelen, len(g:Xgetlist())) -endfunction +endfunc -function Test_setqflist_empty_older() +func Test_setqflist_empty_older() call Xlist_empty_older('c') call Xlist_empty_older('l') -endfunction +endfunc -function! XquickfixSetListWithAct(cchar) +func XquickfixSetListWithAct(cchar) call s:setup_commands(a:cchar) let list1 = [{'filename': 'fnameA', 'text': 'A'}, @@ -1185,12 +1208,12 @@ function! XquickfixSetListWithAct(cchar) call assert_fails("call g:Xsetlist(list1, 0)", 'E928:') endfunc -function Test_quickfix_set_list_with_act() +func Test_quickfix_set_list_with_act() call XquickfixSetListWithAct('c') call XquickfixSetListWithAct('l') -endfunction +endfunc -function XLongLinesTests(cchar) +func XLongLinesTests(cchar) let l = g:Xgetlist() call assert_equal(4, len(l)) @@ -1208,9 +1231,9 @@ function XLongLinesTests(cchar) call assert_equal(10, len(l[3].text)) call g:Xsetlist([], 'r') -endfunction +endfunc -function s:long_lines_tests(cchar) +func s:long_lines_tests(cchar) call s:setup_commands(a:cchar) let testfile = 'samples/quickfix.txt' @@ -1231,22 +1254,22 @@ function s:long_lines_tests(cchar) exe 'edit' testfile exe 'Xbuffer' bufnr('%') call XLongLinesTests(a:cchar) -endfunction +endfunc -function Test_long_lines() +func Test_long_lines() call s:long_lines_tests('c') call s:long_lines_tests('l') -endfunction +endfunc -function! s:create_test_file(filename) +func s:create_test_file(filename) let l = [] for i in range(1, 20) call add(l, 'Line' . i) endfor call writefile(l, a:filename) -endfunction +endfunc -function! Test_switchbuf() +func Test_switchbuf() call s:create_test_file('Xqftestfile1') call s:create_test_file('Xqftestfile2') call s:create_test_file('Xqftestfile3') @@ -1333,9 +1356,9 @@ function! Test_switchbuf() call delete('Xqftestfile1') call delete('Xqftestfile2') call delete('Xqftestfile3') -endfunction +endfunc -function! Xadjust_qflnum(cchar) +func Xadjust_qflnum(cchar) call s:setup_commands(a:cchar) enew | only @@ -1360,17 +1383,17 @@ function! Xadjust_qflnum(cchar) enew! call delete(fname) -endfunction +endfunc -function! Test_adjust_lnum() +func Test_adjust_lnum() call setloclist(0, []) call Xadjust_qflnum('c') call setqflist([]) call Xadjust_qflnum('l') -endfunction +endfunc " Tests for the :grep/:lgrep and :grepadd/:lgrepadd commands -function! s:test_xgrep(cchar) +func s:test_xgrep(cchar) call s:setup_commands(a:cchar) " The following lines are used for the grep test. Don't remove. @@ -1389,9 +1412,9 @@ function! s:test_xgrep(cchar) set makeef=Temp_File_## silent Xgrepadd GrepAdd_Test_Text: test_quickfix.vim call assert_true(len(g:Xgetlist()) == 6) -endfunction +endfunc -function! Test_grep() +func Test_grep() if !has('unix') " The grepprg may not be set on non-Unix systems return @@ -1399,9 +1422,9 @@ function! Test_grep() call s:test_xgrep('c') call s:test_xgrep('l') -endfunction +endfunc -function! Test_two_windows() +func Test_two_windows() " Use one 'errorformat' for two windows. Add an expression to each of them, " make sure they each keep their own state. set efm=%DEntering\ dir\ '%f',%f:%l:%m,%XLeaving\ dir\ '%f' @@ -1427,12 +1450,10 @@ function! Test_two_windows() laddexpr 'one.txt:3:one one one' let loc_one = getloclist(one_id) -echo string(loc_one) call assert_equal('Xone/a/one.txt', bufname(loc_one[1].bufnr)) call assert_equal(3, loc_one[1].lnum) let loc_two = getloclist(two_id) -echo string(loc_two) call assert_equal('Xtwo/a/two.txt', bufname(loc_two[1].bufnr)) call assert_equal(5, loc_two[1].lnum) @@ -1444,7 +1465,7 @@ echo string(loc_two) call delete('Xtwo', 'rf') endfunc -function XbottomTests(cchar) +func XbottomTests(cchar) call s:setup_commands(a:cchar) call g:Xsetlist([{'filename': 'foo', 'lnum': 42}]) @@ -1460,12 +1481,12 @@ function XbottomTests(cchar) endfunc " Tests for the :cbottom and :lbottom commands -function Test_cbottom() +func Test_cbottom() call XbottomTests('c') call XbottomTests('l') -endfunction +endfunc -function HistoryTest(cchar) +func HistoryTest(cchar) call s:setup_commands(a:cchar) call assert_fails(a:cchar . 'older 99', 'E380:') @@ -1505,7 +1526,7 @@ func Test_duplicate_buf() endfunc " Quickfix/Location list set/get properties tests -function Xproperty_tests(cchar) +func Xproperty_tests(cchar) call s:setup_commands(a:cchar) " Error cases @@ -1532,6 +1553,11 @@ function Xproperty_tests(cchar) call assert_equal('N1', g:Xgetlist({'all':1}).title) call g:Xsetlist([], ' ', {'title' : 'N2'}) call assert_equal(qfnr + 1, g:Xgetlist({'all':1}).nr) + + let res = g:Xgetlist({'nr': 0}) + call assert_equal(qfnr + 1, res.nr) + call assert_equal(['nr'], keys(res)) + call g:Xsetlist([], ' ', {'title' : 'N3'}) call assert_equal('N2', g:Xgetlist({'nr':2, 'title':1}).title) @@ -1544,21 +1570,21 @@ function Xproperty_tests(cchar) call assert_equal({}, g:Xgetlist({'abc':1})) if a:cchar == 'l' - call assert_equal({}, getloclist(99, ['title'])) + call assert_equal({}, getloclist(99, {'title': 1})) endif -endfunction + endfunc -function Test_qf_property() +func Test_qf_property() call Xproperty_tests('c') call Xproperty_tests('l') -endfunction + endfunc " Tests for the QuickFixCmdPre/QuickFixCmdPost autocommands -function QfAutoCmdHandler(loc, cmd) +func QfAutoCmdHandler(loc, cmd) call add(g:acmds, a:loc . a:cmd) -endfunction +endfunc -function Test_Autocmd() +func Test_Autocmd() autocmd QuickFixCmdPre * call QfAutoCmdHandler('pre', expand('<amatch>')) autocmd QuickFixCmdPost * call QfAutoCmdHandler('post', expand('<amatch>')) @@ -1586,9 +1612,9 @@ function Test_Autocmd() \ 'precaddbuffer', \ 'postcaddbuffer'] call assert_equal(l, g:acmds) -endfunction +endfunc -function! Test_Autocmd_Exception() +func Test_Autocmd_Exception() set efm=%m lgetexpr '?' @@ -1603,4 +1629,47 @@ function! Test_Autocmd_Exception() call assert_equal('1', getloclist(0)[0].text) set efm&vim -endfunction +endfunc + +func Test_caddbuffer_wrong() + " This used to cause a memory access in freed memory. + let save_efm = &efm + set efm=%EEEE%m,%WWWW,%+CCCC%>%#,%GGGG%.# + cgetexpr ['WWWW', 'EEEE', 'CCCC'] + let &efm = save_efm + caddbuffer + bwipe! +endfunc + +func Test_caddexpr_wrong() + " This used to cause a memory access in freed memory. + cbuffer + cbuffer + copen + let save_efm = &efm + set efm=% + call assert_fails('caddexpr ""', 'E376:') + let &efm = save_efm +endfunc + +func Test_dirstack_cleanup() + " This used to cause a memory access in freed memory. + let save_efm = &efm + lexpr '0' + lopen + fun X(c) + let save_efm=&efm + set efm=%D%f + if a:c == 'c' + caddexpr '::' + else + laddexpr ':0:0' + endif + let &efm=save_efm + endfun + call X('c') + call X('l') + call setqflist([], 'r') + caddbuffer + let &efm = save_efm +endfunc diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index ab78a72909..c29ec09638 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -1007,7 +1007,7 @@ static void tui_flush(UI *ui) size_t nrevents = loop_size(data->loop); if (nrevents > TOO_MANY_EVENTS) { - ILOG("TUI event-queue flooded (thread_events=%zu); purging", nrevents); + WLOG("TUI event-queue flooded (thread_events=%zu); purging", nrevents); // Back-pressure: UI events may accumulate much faster than the terminal // device can serve them. Even if SIGINT/CTRL-C is received, user must still // wait for the TUI event-queue to drain, and if there are ~millions of @@ -1537,11 +1537,11 @@ static void augment_terminfo(TUIData *data, const char *term, bool teraterm = terminfo_is_term_family(term, "teraterm"); bool putty = terminfo_is_term_family(term, "putty"); bool screen = terminfo_is_term_family(term, "screen"); + bool tmux = terminfo_is_term_family(term, "tmux") || !!os_getenv("TMUX"); bool iterm = terminfo_is_term_family(term, "iterm") || terminfo_is_term_family(term, "iTerm.app"); // None of the following work over SSH; see :help TERM . bool iterm_pretending_xterm = xterm && iterm_env; - bool tmux_wrap = screen && !!os_getenv("TMUX"); const char * xterm_version = os_getenv("XTERM_VERSION"); bool true_xterm = xterm && !!xterm_version; @@ -1573,12 +1573,11 @@ static void augment_terminfo(TUIData *data, const char *term, // specific ones. // can use colons like ISO 8613-6:1994/ITU T.416:1993 says. - bool has_colon_rgb = false - // per GNOME bug #685759 and bug #704449 - || (vte_version >= 3600) - || iterm || iterm_pretending_xterm // per analysis of VT100Terminal.m - // per http://invisible-island.net/xterm/xterm.log.html#xterm_282 - || true_xterm; + bool has_colon_rgb = !tmux && !screen + && ((vte_version >= 3600) // per GNOME bug #685759, #704449 + || iterm || iterm_pretending_xterm // per analysis of VT100Terminal.m + // per http://invisible-island.net/xterm/xterm.log.html#xterm_282 + || true_xterm); data->unibi_ext.set_rgb_foreground = unibi_find_ext_str(ut, "setrgbf"); if (-1 == data->unibi_ext.set_rgb_foreground) { @@ -1606,7 +1605,7 @@ static void augment_terminfo(TUIData *data, const char *term, // all panes, which is not particularly desirable. A better approach // would use a tmux control sequence and an extra if(screen) test. data->unibi_ext.set_cursor_color = (int)unibi_add_ext_str( - ut, NULL, TMUX_WRAP(tmux_wrap, "\033]Pl%p1%06x\033\\")); + ut, NULL, TMUX_WRAP(tmux, "\033]Pl%p1%06x\033\\")); } else if (xterm || (vte_version != 0) || rxvt) { // This seems to be supported for a long time in VTE // urxvt also supports this @@ -1690,7 +1689,7 @@ static const char *tui_get_stty_erase(void) if (tcgetattr(input_global_fd(), &t) != -1) { stty_erase[0] = (char)t.c_cc[VERASE]; stty_erase[1] = '\0'; - ILOG("stty/termios:erase=%s", stty_erase); + DLOG("stty/termios:erase=%s", stty_erase); } #endif return stty_erase; @@ -1707,12 +1706,12 @@ static const char *tui_tk_ti_getstr(const char *name, const char *value, } if (strequal(name, "key_backspace")) { - ILOG("libtermkey:kbs=%s", value); + DLOG("libtermkey:kbs=%s", value); if (stty_erase[0] != 0) { return stty_erase; } } else if (strequal(name, "key_dc")) { - ILOG("libtermkey:kdch1=%s", value); + DLOG("libtermkey:kdch1=%s", value); // Vim: "If <BS> and <DEL> are now the same, redefine <DEL>." if (value != NULL && strequal(stty_erase, value)) { return stty_erase[0] == DEL ? CTRL_H_STR : DEL_STR; diff --git a/src/nvim/ui.c b/src/nvim/ui.c index 0a2154438f..a60c061949 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -8,6 +8,7 @@ #include <limits.h> #include "nvim/vim.h" +#include "nvim/log.h" #include "nvim/ui.h" #include "nvim/charset.h" #include "nvim/cursor.h" @@ -59,6 +60,27 @@ static int busy = 0; static int height, width; static int old_mode_idx = -1; +#if MIN_LOG_LEVEL > DEBUG_LOG_LEVEL +# define UI_LOG(funname, ...) +#else +static size_t uilog_seen = 0; +static char uilog_last_event[1024] = { 0 }; +# define UI_LOG(funname, ...) \ + do { \ + if (strequal(uilog_last_event, STR(funname))) { \ + uilog_seen++; \ + } else { \ + if (uilog_seen > 0) { \ + do_log(DEBUG_LOG_LEVEL, "ui", 0, true, \ + "%s (+%zu times...)", uilog_last_event, uilog_seen); \ + } \ + DLOG("ui: " STR(funname)); \ + uilog_seen = 0; \ + xstrlcpy(uilog_last_event, STR(funname), sizeof(uilog_last_event)); \ + } \ + } while (0) +#endif + // UI_CALL invokes a function on all registered UI instances. The functions can // have 0-5 arguments (configurable by SELECT_NTH). // @@ -67,6 +89,7 @@ static int old_mode_idx = -1; # define UI_CALL(funname, ...) \ do { \ flush_cursor_update(); \ + UI_LOG(funname, 0); \ for (size_t i = 0; i < ui_count; i++) { \ UI *ui = uis[i]; \ UI_CALL_MORE(funname, __VA_ARGS__); \ @@ -76,6 +99,7 @@ static int old_mode_idx = -1; # define UI_CALL(...) \ do { \ flush_cursor_update(); \ + UI_LOG(__VA_ARGS__, 0); \ for (size_t i = 0; i < ui_count; i++) { \ UI *ui = uis[i]; \ UI_CALL_HELPER(CNT(__VA_ARGS__), __VA_ARGS__); \ @@ -85,6 +109,7 @@ static int old_mode_idx = -1; #define CNT(...) SELECT_NTH(__VA_ARGS__, MORE, MORE, MORE, MORE, ZERO, ignore) #define SELECT_NTH(a1, a2, a3, a4, a5, a6, ...) a6 #define UI_CALL_HELPER(c, ...) UI_CALL_HELPER2(c, __VA_ARGS__) +// Resolves to UI_CALL_MORE or UI_CALL_ZERO. #define UI_CALL_HELPER2(c, ...) UI_CALL_##c(__VA_ARGS__) #define UI_CALL_MORE(method, ...) if (ui->method) ui->method(ui, __VA_ARGS__) #define UI_CALL_ZERO(method) if (ui->method) ui->method(ui) diff --git a/src/nvim/ui_bridge.c b/src/nvim/ui_bridge.c index 0165db7c0c..5585886612 100644 --- a/src/nvim/ui_bridge.c +++ b/src/nvim/ui_bridge.c @@ -24,30 +24,10 @@ #define UI(b) (((UIBridgeData *)b)->ui) -#if MIN_LOG_LEVEL <= DEBUG_LOG_LEVEL -static size_t uilog_seen = 0; -static argv_callback uilog_event = NULL; -#define UI_CALL(ui, name, argc, ...) \ - do { \ - if (uilog_event == ui_bridge_##name##_event) { \ - uilog_seen++; \ - } else { \ - if (uilog_seen > 0) { \ - DLOG("UI bridge: ...%zu times", uilog_seen); \ - } \ - DLOG("UI bridge: " STR(name)); \ - uilog_seen = 0; \ - uilog_event = ui_bridge_##name##_event; \ - } \ - ((UIBridgeData *)ui)->scheduler( \ - event_create(ui_bridge_##name##_event, argc, __VA_ARGS__), UI(ui)); \ - } while (0) -#else // Schedule a function call on the UI bridge thread. -#define UI_CALL(ui, name, argc, ...) \ +#define UI_BRIDGE_CALL(ui, name, argc, ...) \ ((UIBridgeData *)ui)->scheduler( \ event_create(ui_bridge_##name##_event, argc, __VA_ARGS__), UI(ui)) -#endif #define INT2PTR(i) ((void *)(intptr_t)i) #define PTR2INT(p) ((Integer)(intptr_t)p) @@ -128,7 +108,7 @@ static void ui_bridge_stop(UI *b) { UIBridgeData *bridge = (UIBridgeData *)b; bool stopped = bridge->stopped = false; - UI_CALL(b, stop, 1, b); + UI_BRIDGE_CALL(b, stop, 1, b); for (;;) { uv_mutex_lock(&bridge->mutex); stopped = bridge->stopped; @@ -154,7 +134,7 @@ static void ui_bridge_highlight_set(UI *b, HlAttrs attrs) { HlAttrs *a = xmalloc(sizeof(HlAttrs)); *a = attrs; - UI_CALL(b, highlight_set, 2, b, a); + UI_BRIDGE_CALL(b, highlight_set, 2, b, a); } static void ui_bridge_highlight_set_event(void **argv) { @@ -167,7 +147,7 @@ static void ui_bridge_suspend(UI *b) { UIBridgeData *data = (UIBridgeData *)b; uv_mutex_lock(&data->mutex); - UI_CALL(b, suspend, 1, b); + UI_BRIDGE_CALL(b, suspend, 1, b); data->ready = false; // suspend the main thread until CONTINUE is called by the UI thread while (!data->ready) { diff --git a/src/nvim/version.c b/src/nvim/version.c index 44e9d5e26c..6662dff437 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -605,12 +605,12 @@ static const int included_patches[] = { 127, // 126, // 125, - // 124, + 124, // 123 NA // 122 NA 121, // 120 NA - // 119, + 119, // 118, // 117 NA 116, @@ -629,7 +629,7 @@ static const int included_patches[] = { // 103 NA // 102, // 101, - // 100, + 100, 99, // 98 NA // 97 NA @@ -641,33 +641,33 @@ static const int included_patches[] = { // 91, // 90, // 89 NA - // 88, + 88, // 87 NA // 86, - // 85, - // 84, + 85, + 84, 83, // 82 NA // 81, // 80 NA - // 79, - // 78, + 79, + 78, // 77 NA // 76 NA - // 75, + 75, // 74, - // 73, + 73, // 72 NA // 71 NA // 70 NA - // 69, + 69, 68, // 67 NA 66, // 65 NA 64, - // 63, - // 62, + // 63 NA + 62, // 61 NA 60, // 59 NA @@ -677,13 +677,13 @@ static const int included_patches[] = { // 55 NA // 54 NA 53, - // 52, + 52, // 51 NA // 50 NA 49, // 48 NA 47, - // 46, + 46, // 45 NA // 44, 43, @@ -691,33 +691,33 @@ static const int included_patches[] = { 41, 40, // 39 NA - // 38, + 38, 37, // 36 NA 35, - // 34, + 34, 33, 32, - // 31, + 31, // 30 NA // 29 NA // 28 NA // 27 NA - // 26, - // 25, + 26, + 25, // 24 NA - // 23, + 23, // 22 NA // 21, - // 20, + 20, 19, // 18, - // 17, + 17, // 16 NA // 15 NA // 14 NA // 13 NA - // 12, + 12, // 11 NA // 10 NA // 9 NA @@ -725,8 +725,8 @@ static const int included_patches[] = { // 7 NA 6, // 5 NA - // 4, - // 3, + 4, + 3, 2, 1, 0 @@ -993,7 +993,7 @@ void intro_message(int colon) static char *(lines[]) = { N_(NVIM_VERSION_LONG), "", - N_("by Bram Moolenaar et al."), + N_("by al."), N_("Nvim is open source and freely distributable"), N_("https://neovim.io/community"), "", diff --git a/src/nvim/vim.h b/src/nvim/vim.h index 5d2c27a2f4..62ffc7433e 100644 --- a/src/nvim/vim.h +++ b/src/nvim/vim.h @@ -163,6 +163,7 @@ enum { EXPAND_SYNTIME, EXPAND_USER_ADDR_TYPE, EXPAND_PACKADD, + EXPAND_MESSAGES, }; @@ -319,7 +320,7 @@ enum { FOLD_TEXT_LEN = 51 }; //!< buffer size for get_foldtext() // Lowest number used for window ID. Cannot have this many windows per tab. #define LOWEST_WIN_ID 1000 -#if defined(__FreeBSD__) && defined(S_ISCHR) +#if (defined(__FreeBSD__) || defined(__FreeBSD_kernel__)) && defined(S_ISCHR) # define OPEN_CHR_FILES #endif diff --git a/src/nvim/window.c b/src/nvim/window.c index 29f5412ba0..faf5bceb56 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -193,7 +193,7 @@ newwindow: /* cursor to previous window with wrap around */ case 'W': CHECK_CMDWIN - if (firstwin == lastwin && Prenum != 1) /* just one window */ + if (ONE_WINDOW && Prenum != 1) /* just one window */ beep_flush(); else { if (Prenum) { /* go to specified window */ @@ -574,7 +574,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) oldwin = curwin; /* add a status line when p_ls == 1 and splitting the first window */ - if (lastwin == firstwin && p_ls == 1 && oldwin->w_status_height == 0) { + if (ONE_WINDOW && p_ls == 1 && oldwin->w_status_height == 0) { if (oldwin->w_height <= p_wmh && new_wp == NULL) { EMSG(_(e_noroom)); return FAIL; @@ -1182,7 +1182,7 @@ static void win_exchange(long Prenum) win_T *wp2; int temp; - if (lastwin == firstwin) { /* just one window */ + if (ONE_WINDOW) { /* just one window */ beep_flush(); return; } @@ -1271,7 +1271,7 @@ static void win_rotate(int upwards, int count) frame_T *frp; int n; - if (firstwin == lastwin) { /* nothing to do */ + if (ONE_WINDOW) { /* nothing to do */ beep_flush(); return; } @@ -1343,7 +1343,7 @@ static void win_totop(int size, int flags) int dir; int height = curwin->w_height; - if (lastwin == firstwin) { + if (ONE_WINDOW) { beep_flush(); return; } @@ -1728,7 +1728,7 @@ void close_windows(buf_T *buf, int keep_curwin) ++RedrawingDisabled; - for (win_T *wp = firstwin; wp != NULL && lastwin != firstwin; ) { + for (win_T *wp = firstwin; wp != NULL && !ONE_WINDOW; ) { if (wp->w_buffer == buf && (!keep_curwin || wp != curwin) && !(wp->w_closing || wp->w_buffer->b_locked > 0)) { if (win_close(wp, false) == FAIL) { @@ -1810,7 +1810,7 @@ static bool close_last_window_tabpage(win_T *win, bool free_buf, tabpage_T *prev_curtab) FUNC_ATTR_NONNULL_ARG(1) { - if (firstwin != lastwin) { + if (!ONE_WINDOW) { return false; } buf_T *old_curbuf = curbuf; @@ -2194,7 +2194,7 @@ winframe_remove ( /* * If there is only one window there is nothing to remove. */ - if (tp == NULL ? firstwin == lastwin : tp->tp_firstwin == tp->tp_lastwin) + if (tp == NULL ? ONE_WINDOW : tp->tp_firstwin == tp->tp_lastwin) return NULL; /* @@ -2331,7 +2331,7 @@ win_altframe ( frame_T *frp; int b; - if (tp == NULL ? firstwin == lastwin : tp->tp_firstwin == tp->tp_lastwin) + if (tp == NULL ? ONE_WINDOW : tp->tp_firstwin == tp->tp_lastwin) /* Last window in this tab page, will go to next tab page. */ return alt_tabpage()->tp_curwin->w_frame; @@ -2851,7 +2851,7 @@ close_others ( win_close(wp, !P_HID(wp->w_buffer) && !bufIsChanged(wp->w_buffer)); } - if (message && lastwin != firstwin) + if (message && !ONE_WINDOW) EMSG(_("E445: Other window contains changes")); } @@ -5173,7 +5173,7 @@ last_status ( { /* Don't make a difference between horizontal or vertical split. */ last_status_rec(topframe, (p_ls == 2 - || (p_ls == 1 && (morewin || lastwin != firstwin)))); + || (p_ls == 1 && (morewin || !ONE_WINDOW)))); } static void last_status_rec(frame_T *fr, int statusline) |