diff options
Diffstat (limited to 'src')
66 files changed, 1659 insertions, 1129 deletions
diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index 57f392f98e..ccf4ae3d02 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -366,7 +366,7 @@ cleanup: /// {"CursorHold", "BufPreWrite", "BufPostWrite"} /// </pre> /// -/// @param event (String|Array) The event or events to register this autocommand +/// @param event (string|array) The event or events to register this autocommand /// @param opts Dictionary of autocommand options: /// - group (string|integer) optional: the autocommand group name or /// id to match against. @@ -375,8 +375,18 @@ cleanup: /// - buffer (integer) optional: buffer number for buffer local autocommands /// |autocmd-buflocal|. Cannot be used with {pattern}. /// - desc (string) optional: description of the autocommand. -/// - callback (function|string) optional: Lua function or Vim function (as string) to -/// execute on event. Cannot be used with {command} +/// - callback (function|string) optional: if a string, the name of a Vimscript function +/// to call when this autocommand is triggered. Otherwise, a Lua function which is +/// called when this autocommand is triggered. Cannot be used with {command}. Lua +/// callbacks can return true to delete the autocommand; in addition, they accept a +/// single table argument with the following keys: +/// - id: (number) the autocommand id +/// - event: (string) the name of the event that triggered the autocommand +/// |autocmd-events| +/// - group: (number|nil) the autocommand group id, if it exists +/// - match: (string) the expanded value of |<amatch>| +/// - buf: (number) the expanded value of |<abuf>| +/// - file: (string) the expanded value of |<afile>| /// - command (string) optional: Vim command to execute on event. Cannot be used with /// {callback} /// - once (boolean) optional: defaults to false. Run the autocommand diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index bcac7fbc4a..48c1bb740d 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -135,7 +135,7 @@ static inline const char *get_deleted_augroup(void) FUNC_ATTR_ALWAYS_INLINE } // Show the autocommands for one AutoPat. -static void aupat_show(AutoPat *ap) +static void aupat_show(AutoPat *ap, event_T event, int previous_group) { // Check for "got_int" (here and at various places below), which is set // when "q" has been hit for the "--more--" prompt @@ -148,6 +148,31 @@ static void aupat_show(AutoPat *ap) return; } + char *name = augroup_name(ap->group); + + msg_putchar('\n'); + if (got_int) { + return; + } + // When switching groups, we need to show the new group information. + if (ap->group != previous_group) { + // show the group name, if it's not the default group + if (ap->group != AUGROUP_DEFAULT) { + if (name == NULL) { + msg_puts_attr(get_deleted_augroup(), HL_ATTR(HLF_E)); + } else { + msg_puts_attr(name, HL_ATTR(HLF_T)); + } + msg_puts(" "); + } + // show the event name + msg_puts_attr(event_nr2name(event), HL_ATTR(HLF_T)); + msg_putchar('\n'); + if (got_int) { + return; + } + } + msg_col = 4; msg_outtrans(ap->pat); @@ -180,53 +205,69 @@ static void aupat_show(AutoPat *ap) } } -static void au_show_for_all_events(int group) +static void au_show_for_all_events(int group, char_u *pat) { FOR_ALL_AUEVENTS(event) { - au_show_for_event(group, event); + au_show_for_event(group, event, pat); } } -static void au_show_for_event(int group, event_T event) +static void au_show_for_event(int group, event_T event, char_u *pat) { // Return early if there are no autocmds for this event if (au_event_is_empty(event)) { return; } + // always need to show group information before the first pattern for the event int previous_group = AUGROUP_ERROR; - FOR_ALL_AUPATS_IN_EVENT(event, ap) { - if (group != AUGROUP_ALL && group != ap->group) { - continue; - } - - char *name = augroup_name(ap->group); - msg_putchar('\n'); - // When switching groups, we need to show the new group information. - if (ap->group != previous_group) { - // show the group name, if it's not the default group - if (ap->group != AUGROUP_DEFAULT) { - if (name == NULL) { - msg_puts_attr(get_deleted_augroup(), HL_ATTR(HLF_E)); - } else { - msg_puts_attr(name, HL_ATTR(HLF_T)); - } - msg_puts(" "); + if (*pat == NUL) { + FOR_ALL_AUPATS_IN_EVENT(event, ap) { + if (group == AUGROUP_ALL || ap->group == group) { + aupat_show(ap, event, previous_group); + previous_group = ap->group; } - - // show the event name - msg_puts_attr(event_nr2name(event), HL_ATTR(HLF_T)); - msg_putchar('\n'); } + return; + } - if (got_int) { - return; + char_u buflocal_pat[BUFLOCAL_PAT_LEN]; // for "<buffer=X>" + // Loop through all the specified patterns. + int patlen = (int)aucmd_pattern_length(pat); + while (patlen) { + // detect special <buffer[=X]> buffer-local patterns + if (aupat_is_buflocal(pat, patlen)) { + // normalize pat into standard "<buffer>#N" form + aupat_normalize_buflocal_pat(buflocal_pat, pat, patlen, aupat_get_buflocal_nr(pat, patlen)); + pat = buflocal_pat; + patlen = (int)STRLEN(buflocal_pat); } - aupat_show(ap); + assert(*pat != NUL); + + // Find AutoPat entries with this pattern. + // always goes at or after the last one, so start at the end. + FOR_ALL_AUPATS_IN_EVENT(event, ap) { + if (ap->pat != NULL) { + // Accept a pattern when: + // - a group was specified and it's that group + // - the length of the pattern matches + // - the pattern matches. + // For <buffer[=X]>, this condition works because we normalize + // all buffer-local patterns. + if ((group == AUGROUP_ALL || ap->group == group) + && ap->patlen == patlen + && STRNCMP(pat, ap->pat, patlen) == 0) { + // Show autocmd's for this autopat, or buflocals <buffer=X> + aupat_show(ap, event, previous_group); + previous_group = ap->group; + } + } + } - previous_group = ap->group; + pat = aucmd_next_pattern(pat, (size_t)patlen); + patlen = (int)aucmd_pattern_length(pat); } } @@ -805,11 +846,11 @@ void do_autocmd(char_u *arg_in, int forceit) msg_puts_title(_("\n--- Autocommands ---")); if (*arg == '*' || *arg == '|' || *arg == NUL) { - au_show_for_all_events(group); + au_show_for_all_events(group, pat); } else { event_T event = event_name2nr(arg, &arg); assert(event < NUM_EVENTS); - au_show_for_event(group, event); + au_show_for_event(group, event, pat); } } else { if (*arg == '*' || *arg == NUL || *arg == '|') { @@ -900,7 +941,6 @@ int do_autocmd_event(event_T event, char_u *pat, bool once, int nested, char_u * assert(*pat != NUL); // Find AutoPat entries with this pattern. - // always goes at or after the last one, so start at the end. prev_ap = &first_autopat[(int)event]; while ((ap = *prev_ap) != NULL) { if (ap->pat != NULL) { @@ -980,6 +1020,7 @@ int autocmd_register(int64_t id, event_T event, char_u *pat, int patlen, int gro patlen = (int)STRLEN(buflocal_pat); } + // always goes at or after the last one, so start at the end. if (last_autopat[(int)event] != NULL) { prev_ap = &last_autopat[(int)event]; } else { @@ -1964,6 +2005,50 @@ void auto_next_pat(AutoPatCmd *apc, int stop_at_last) } } +static bool call_autocmd_callback(const AutoCmd *ac, const AutoPatCmd *apc) +{ + bool ret = false; + Callback callback = ac->exec.callable.cb; + if (callback.type == kCallbackLua) { + Dictionary data = ARRAY_DICT_INIT; + PUT(data, "id", INTEGER_OBJ(ac->id)); + PUT(data, "event", CSTR_TO_OBJ(event_nr2name(apc->event))); + PUT(data, "match", CSTR_TO_OBJ((char *)autocmd_match)); + PUT(data, "file", CSTR_TO_OBJ((char *)autocmd_fname)); + PUT(data, "buf", INTEGER_OBJ(autocmd_bufnr)); + + int group = apc->curpat->group; + switch (group) { + case AUGROUP_ERROR: + abort(); // unreachable + case AUGROUP_DEFAULT: + case AUGROUP_ALL: + case AUGROUP_DELETED: + // omit group in these cases + break; + default: + PUT(data, "group", INTEGER_OBJ(group)); + break; + } + + FIXED_TEMP_ARRAY(args, 1); + args.items[0] = DICTIONARY_OBJ(data); + + Object result = nlua_call_ref(callback.data.luaref, NULL, args, true, NULL); + if (result.type == kObjectTypeBoolean) { + ret = result.data.boolean; + } + api_free_dictionary(data); + api_free_object(result); + } else { + typval_T argsin = TV_INITIAL_VALUE; + typval_T rettv = TV_INITIAL_VALUE; + callback_call(&callback, 0, &argsin, &rettv); + } + + return ret; +} + /// Get next autocommand command. /// Called by do_cmdline() to get the next line for ":if". /// @return allocated string, or NULL for end of autocommands. @@ -2028,16 +2113,11 @@ char_u *getnextac(int c, void *cookie, int indent, bool do_concat) current_sctx = ac->script_ctx; if (ac->exec.type == CALLABLE_CB) { - typval_T argsin = TV_INITIAL_VALUE; - typval_T rettv = TV_INITIAL_VALUE; - if (callback_call(&ac->exec.callable.cb, 0, &argsin, &rettv)) { - if (ac->exec.callable.cb.type == kCallbackLua) { - // If a Lua callback returns 'true' then the autocommand is removed - oneshot = true; - } + if (call_autocmd_callback(ac, acp)) { + // If an autocommand callback returns true, delete the autocommand + oneshot = true; } - // TODO(tjdevries): // // Major Hack Alert: diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index bf592a626d..4ec23244cd 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -174,7 +174,7 @@ int open_buffer(int read_stdin, exarg_T *eap, int flags) if (ml_open(curbuf) == FAIL) { // There MUST be a memfile, otherwise we can't do anything // If we can't create one for the current buffer, take another buffer - close_buffer(NULL, curbuf, 0, false); + close_buffer(NULL, curbuf, 0, false, false); curbuf = NULL; FOR_ALL_BUFFERS(buf) { @@ -402,8 +402,10 @@ bool buf_valid(buf_T *buf) /// there to be only one window with this buffer. e.g. when /// ":quit" is supposed to close the window but autocommands /// close all other windows. +/// @param ignore_abort +/// If true, don't abort even when aborting() returns true. /// @return true when we got to the end and b_nwindows was decremented. -bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last) +bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last, bool ignore_abort) { bool unload_buf = (action != 0); bool del_buf = (action == DOBUF_DEL || action == DOBUF_WIPE); @@ -494,7 +496,8 @@ bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last) return false; } } - if (aborting()) { // autocmds may abort script processing + // autocmds may abort script processing + if (!ignore_abort && aborting()) { return false; } } @@ -552,14 +555,16 @@ bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last) buf->b_nwindows = nwindows; - buf_freeall(buf, (del_buf ? BFA_DEL : 0) + (wipe_buf ? BFA_WIPE : 0)); + buf_freeall(buf, ((del_buf ? BFA_DEL : 0) + + (wipe_buf ? BFA_WIPE : 0) + + (ignore_abort ? BFA_IGNORE_ABORT : 0))); if (!bufref_valid(&bufref)) { // Autocommands may have deleted the buffer. return false; } - if (aborting()) { - // Autocmds may abort script processing. + // autocmds may abort script processing. + if (!ignore_abort && aborting()) { return false; } @@ -660,9 +665,10 @@ void buf_clear(void) /// buf_freeall() - free all things allocated for a buffer that are related to /// the file. Careful: get here with "curwin" NULL when exiting. /// -/// @param flags BFA_DEL buffer is going to be deleted -/// BFA_WIPE buffer is going to be wiped out -/// BFA_KEEP_UNDO do not free undo information +/// @param flags BFA_DEL buffer is going to be deleted +/// BFA_WIPE buffer is going to be wiped out +/// BFA_KEEP_UNDO do not free undo information +/// BFA_IGNORE_ABORT don't abort even when aborting() returns true void buf_freeall(buf_T *buf, int flags) { bool is_curbuf = (buf == curbuf); @@ -706,7 +712,8 @@ void buf_freeall(buf_T *buf, int flags) goto_tabpage_win(the_curtab, the_curwin); unblock_autocmds(); } - if (aborting()) { // autocmds may abort script processing + // autocmds may abort script processing + if ((flags & BFA_IGNORE_ABORT) == 0 && aborting()) { return; } @@ -877,7 +884,7 @@ void handle_swap_exists(bufref_T *old_curbuf) // open a new, empty buffer. swap_exists_action = SEA_NONE; // don't want it again swap_exists_did_quit = true; - close_buffer(curwin, curbuf, DOBUF_UNLOAD, false); + close_buffer(curwin, curbuf, DOBUF_UNLOAD, false, false); if (old_curbuf == NULL || !bufref_valid(old_curbuf) || old_curbuf->br_buf == curbuf) { @@ -1074,7 +1081,7 @@ static int empty_curbuf(int close_others, int forceit, int action) // the old one. But do_ecmd() may have done that already, check // if the buffer still exists. if (buf != curbuf && bufref_valid(&bufref) && buf->b_nwindows == 0) { - close_buffer(NULL, buf, action, false); + close_buffer(NULL, buf, action, false, false); } if (!close_others) { @@ -1259,7 +1266,7 @@ int do_buffer(int action, int start, int dir, int count, int forceit) if (buf != curbuf) { close_windows(buf, false); if (buf != curbuf && bufref_valid(&bufref) && buf->b_nwindows <= 0) { - close_buffer(NULL, buf, action, false); + close_buffer(NULL, buf, action, false, false); } return OK; } @@ -1485,7 +1492,7 @@ void set_curbuf(buf_T *buf, int action) ? action : (action == DOBUF_GOTO && !buf_hide(prevbuf) && !bufIsChanged(prevbuf)) ? DOBUF_UNLOAD : 0, - false); + false, false); if (curwin != previouswin && win_valid(previouswin)) { // autocommands changed curwin, Grr! curwin = previouswin; @@ -2805,7 +2812,7 @@ int setfname(buf_T *buf, char_u *ffname_arg, char_u *sfname_arg, bool message) return FAIL; } // delete from the list - close_buffer(NULL, obuf, DOBUF_WIPE, false); + close_buffer(NULL, obuf, DOBUF_WIPE, false, false); } sfname = vim_strsave(sfname); #ifdef USE_FNAME_CASE @@ -5650,7 +5657,7 @@ void wipe_buffer(buf_T *buf, bool aucmd) // Don't trigger BufDelete autocommands here. block_autocmds(); } - close_buffer(NULL, buf, DOBUF_WIPE, false); + close_buffer(NULL, buf, DOBUF_WIPE, false, true); if (!aucmd) { unblock_autocmds(); } diff --git a/src/nvim/buffer.h b/src/nvim/buffer.h index 9e2ca999e4..7f4bbcc9e5 100644 --- a/src/nvim/buffer.h +++ b/src/nvim/buffer.h @@ -23,7 +23,7 @@ enum getf_retvalues { GETFILE_ERROR = 1, // normal error GETFILE_NOT_WRITTEN = 2, // "not written" error GETFILE_SAME_FILE = 0, // success, same file - GETFILE_OPEN_OTHER = -1, // success, opened another file + GETFILE_OPEN_OTHER = (-1), // success, opened another file GETFILE_UNUSED = 8, }; @@ -58,9 +58,10 @@ enum dobuf_start_values { // flags for buf_freeall() enum bfa_values { - BFA_DEL = 1, // buffer is going to be deleted - BFA_WIPE = 2, // buffer is going to be wiped out - BFA_KEEP_UNDO = 4, // do not free undo information + BFA_DEL = 1, // buffer is going to be deleted + BFA_WIPE = 2, // buffer is going to be wiped out + BFA_KEEP_UNDO = 4, // do not free undo information + BFA_IGNORE_ABORT = 8, // do not abort for aborting() }; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 08ca1a6247..422741cc2f 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -661,7 +661,7 @@ struct file_buffer { // flags for use of ":lmap" and IM control long b_p_iminsert; // input mode for insert long b_p_imsearch; // input mode for search -#define B_IMODE_USE_INSERT -1 // Use b_p_iminsert value for search +#define B_IMODE_USE_INSERT (-1) // Use b_p_iminsert value for search #define B_IMODE_NONE 0 // Input via none #define B_IMODE_LMAP 1 // Input via langmap #define B_IMODE_LAST 1 @@ -1420,7 +1420,7 @@ struct window_S { int w_briopt_list; // additional indent for lists // transform a pointer to a "onebuf" option into a "allbuf" option -#define GLOBAL_WO(p) ((char *)p + sizeof(winopt_T)) +#define GLOBAL_WO(p) ((char *)(p) + sizeof(winopt_T)) long w_scbind_pos; diff --git a/src/nvim/change.c b/src/nvim/change.c index 0644b1d601..44abd69733 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -286,9 +286,11 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, long xtra set_topline(wp, wp->w_topline); } - // Relative numbering may require updating more. + // If lines have been added or removed, relative numbering always + // requires a redraw. if (wp->w_p_rnu && xtra != 0) { - redraw_later(wp, SOME_VALID); + wp->w_last_cursor_lnum_rnu = 0; + redraw_later(wp, VALID); } // Cursor line highlighting probably need to be updated with diff --git a/src/nvim/charset.c b/src/nvim/charset.c index 986a89b0a5..ec1866e9cc 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -425,9 +425,9 @@ char_u *str_foldcase(char_u *str, int orglen, char_u *buf, int buflen) int len = orglen; #define GA_CHAR(i) ((char_u *)ga.ga_data)[i] -#define GA_PTR(i) ((char_u *)ga.ga_data + i) +#define GA_PTR(i) ((char_u *)ga.ga_data + (i)) #define STR_CHAR(i) (buf == NULL ? GA_CHAR(i) : buf[i]) -#define STR_PTR(i) (buf == NULL ? GA_PTR(i) : buf + i) +#define STR_PTR(i) (buf == NULL ? GA_PTR(i) : buf + (i)) // Copy "str" into "buf" or allocated memory, unmodified. if (buf == NULL) { diff --git a/src/nvim/diff.c b/src/nvim/diff.c index a6bbe40999..0b55fb877c 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -743,11 +743,16 @@ static int diff_write_buffer(buf_T *buf, diffin_T *din) for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) { for (char_u *s = ml_get_buf(buf, lnum, false); *s != NUL;) { if (diff_flags & DIFF_ICASE) { + int c; char_u cbuf[MB_MAXBYTES + 1]; - // xdiff doesn't support ignoring case, fold-case the text. - int c = utf_ptr2char(s); - c = utf_fold(c); + if (*s == NL) { + c = NUL; + } else { + // xdiff doesn't support ignoring case, fold-case the text. + c = utf_ptr2char(s); + c = utf_fold(c); + } const int orig_len = utfc_ptr2len(s); if (utf_char2bytes(c, cbuf) != orig_len) { // TODO(Bram): handle byte length difference @@ -759,7 +764,8 @@ static int diff_write_buffer(buf_T *buf, diffin_T *din) s += orig_len; len += orig_len; } else { - ptr[len++] = *s++; + ptr[len++] = *s == NL ? NUL : *s; + s++; } } ptr[len++] = NL; diff --git a/src/nvim/edit.c b/src/nvim/edit.c index da0b577056..3eb4ab9517 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -390,12 +390,9 @@ static void insert_enter(InsertState *s) trigger_modechanged(); stop_insert_mode = false; - // Need to recompute the cursor position, it might move when the cursor - // is on a TAB or special character. - // ptr2cells() treats a TAB character as double-width. - if (ptr2cells(get_cursor_pos_ptr()) > 1) { - curwin->w_valid &= ~VALID_VIRTCOL; - curs_columns(curwin, true); + // need to position cursor again when on a TAB + if (gchar_cursor() == TAB) { + curwin->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL); } // Enable langmap or IME, indicated by 'iminsert'. @@ -1396,6 +1393,7 @@ static void insert_do_complete(InsertState *s) compl_cont_status = 0; } compl_busy = false; + can_si = true; // allow smartindenting } static void insert_do_cindent(InsertState *s) @@ -7330,21 +7328,21 @@ static void mb_replace_pop_ins(int cc) // Not a multi-byte char, put it back. replace_push(c); break; + } + + buf[0] = c; + assert(n > 1); + for (i = 1; i < n; i++) { + buf[i] = replace_pop(); + } + if (utf_iscomposing(utf_ptr2char(buf))) { + ins_bytes_len(buf, n); } else { - buf[0] = c; - assert(n > 1); - for (i = 1; i < n; i++) { - buf[i] = replace_pop(); - } - if (utf_iscomposing(utf_ptr2char(buf))) { - ins_bytes_len(buf, n); - } else { - // Not a composing char, put it back. - for (i = n - 1; i >= 0; i--) { - replace_push(buf[i]); - } - break; + // Not a composing char, put it back. + for (i = n - 1; i >= 0; i--) { + replace_push(buf[i]); } + break; } } } @@ -8054,8 +8052,10 @@ static bool ins_esc(long *count, int cmdchar, bool nomove) State = NORMAL; trigger_modechanged(); - // need to position cursor again (e.g. when on a TAB ) - changed_cline_bef_curs(); + // need to position cursor again when on a TAB + if (gchar_cursor() == TAB) { + curwin->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL); + } setmouse(); ui_cursor_shape(); // may show different cursor shape diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 6c72c2866e..c1905b31d0 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -28,7 +28,6 @@ #include "nvim/eval/typval.h" #include "nvim/eval/userfunc.h" #include "nvim/ex_cmds2.h" -#include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" #include "nvim/ex_session.h" #include "nvim/fileio.h" @@ -41,7 +40,6 @@ #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/os/input.h" -#include "nvim/os/os.h" #include "nvim/os/shell.h" #include "nvim/path.h" #include "nvim/quickfix.h" @@ -3271,7 +3269,7 @@ char_u *get_user_var_name(expand_T *xp, int idx) /// Does not use 'cpo' and always uses 'magic'. /// /// @return TRUE if "pat" matches "text". -static int pattern_match(char_u *pat, char_u *text, bool ic) +int pattern_match(char_u *pat, char_u *text, bool ic) { int matches = 0; regmatch_T regmatch; @@ -5905,144 +5903,6 @@ void get_arglist_as_rettv(aentry_T *arglist, int argcount, typval_T *rettv) } } -/// Prepare "gap" for an assert error and add the sourcing position. -void prepare_assert_error(garray_T *gap) -{ - char buf[NUMBUFLEN]; - - ga_init(gap, 1, 100); - if (sourcing_name != NULL) { - ga_concat(gap, (char *)sourcing_name); - if (sourcing_lnum > 0) { - ga_concat(gap, " "); - } - } - if (sourcing_lnum > 0) { - vim_snprintf(buf, ARRAY_SIZE(buf), "line %" PRId64, (int64_t)sourcing_lnum); - ga_concat(gap, buf); - } - if (sourcing_name != NULL || sourcing_lnum > 0) { - ga_concat(gap, ": "); - } -} - -/// Append "p[clen]" to "gap", escaping unprintable characters. -/// Changes NL to \n, CR to \r, etc. -static void ga_concat_esc(garray_T *gap, const char_u *p, int clen) - FUNC_ATTR_NONNULL_ALL -{ - char_u buf[NUMBUFLEN]; - - if (clen > 1) { - memmove(buf, p, clen); - buf[clen] = NUL; - ga_concat(gap, (char *)buf); - } else { - switch (*p) { - case BS: - ga_concat(gap, "\\b"); break; - case ESC: - ga_concat(gap, "\\e"); break; - case FF: - ga_concat(gap, "\\f"); break; - case NL: - ga_concat(gap, "\\n"); break; - case TAB: - ga_concat(gap, "\\t"); break; - case CAR: - ga_concat(gap, "\\r"); break; - case '\\': - ga_concat(gap, "\\\\"); break; - default: - if (*p < ' ') { - vim_snprintf((char *)buf, NUMBUFLEN, "\\x%02x", *p); - ga_concat(gap, (char *)buf); - } else { - ga_append(gap, *p); - } - break; - } - } -} - -/// Append "str" to "gap", escaping unprintable characters. -/// Changes NL to \n, CR to \r, etc. -static void ga_concat_shorten_esc(garray_T *gap, const char_u *str) - FUNC_ATTR_NONNULL_ARG(1) -{ - char_u buf[NUMBUFLEN]; - - if (str == NULL) { - ga_concat(gap, "NULL"); - return; - } - - for (const char_u *p = str; *p != NUL; p++) { - int same_len = 1; - const char_u *s = p; - const int c = mb_ptr2char_adv(&s); - const int clen = s - p; - while (*s != NUL && c == utf_ptr2char(s)) { - same_len++; - s += clen; - } - if (same_len > 20) { - ga_concat(gap, "\\["); - ga_concat_esc(gap, p, clen); - ga_concat(gap, " occurs "); - vim_snprintf((char *)buf, NUMBUFLEN, "%d", same_len); - ga_concat(gap, (char *)buf); - ga_concat(gap, " times]"); - p = s - 1; - } else { - ga_concat_esc(gap, p, clen); - } - } -} - -/// Fill "gap" with information about an assert error. -void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char_u *exp_str, typval_T *exp_tv, - typval_T *got_tv, assert_type_T atype) -{ - char_u *tofree; - - if (opt_msg_tv->v_type != VAR_UNKNOWN) { - tofree = (char_u *)encode_tv2echo(opt_msg_tv, NULL); - ga_concat(gap, (char *)tofree); - xfree(tofree); - ga_concat(gap, ": "); - } - - if (atype == ASSERT_MATCH || atype == ASSERT_NOTMATCH) { - ga_concat(gap, "Pattern "); - } else if (atype == ASSERT_NOTEQUAL) { - ga_concat(gap, "Expected not equal to "); - } else { - ga_concat(gap, "Expected "); - } - - if (exp_str == NULL) { - tofree = (char_u *)encode_tv2string(exp_tv, NULL); - ga_concat_shorten_esc(gap, tofree); - xfree(tofree); - } else { - ga_concat_shorten_esc(gap, exp_str); - } - - if (atype != ASSERT_NOTEQUAL) { - if (atype == ASSERT_MATCH) { - ga_concat(gap, " does not match "); - } else if (atype == ASSERT_NOTMATCH) { - ga_concat(gap, " does match "); - } else { - ga_concat(gap, " but got "); - } - tofree = (char_u *)encode_tv2string(got_tv, NULL); - ga_concat_shorten_esc(gap, tofree); - xfree(tofree); - } -} - /// Add an assert error to v:errors. void assert_error(garray_T *gap) { @@ -6056,328 +5916,6 @@ void assert_error(garray_T *gap) (const char *)gap->ga_data, (ptrdiff_t)gap->ga_len); } -int assert_equal_common(typval_T *argvars, assert_type_T atype) - FUNC_ATTR_NONNULL_ALL -{ - garray_T ga; - - if (tv_equal(&argvars[0], &argvars[1], false, false) - != (atype == ASSERT_EQUAL)) { - prepare_assert_error(&ga); - fill_assert_error(&ga, &argvars[2], NULL, - &argvars[0], &argvars[1], atype); - assert_error(&ga); - ga_clear(&ga); - return 1; - } - return 0; -} - -int assert_equalfile(typval_T *argvars) - FUNC_ATTR_NONNULL_ALL -{ - char buf1[NUMBUFLEN]; - char buf2[NUMBUFLEN]; - const char *const fname1 = tv_get_string_buf_chk(&argvars[0], buf1); - const char *const fname2 = tv_get_string_buf_chk(&argvars[1], buf2); - garray_T ga; - - if (fname1 == NULL || fname2 == NULL) { - return 0; - } - - IObuff[0] = NUL; - FILE *const fd1 = os_fopen(fname1, READBIN); - char line1[200]; - char line2[200]; - ptrdiff_t lineidx = 0; - if (fd1 == NULL) { - snprintf((char *)IObuff, IOSIZE, (char *)e_notread, fname1); - } else { - FILE *const fd2 = os_fopen(fname2, READBIN); - if (fd2 == NULL) { - fclose(fd1); - snprintf((char *)IObuff, IOSIZE, (char *)e_notread, fname2); - } else { - int64_t linecount = 1; - for (int64_t count = 0;; count++) { - const int c1 = fgetc(fd1); - const int c2 = fgetc(fd2); - if (c1 == EOF) { - if (c2 != EOF) { - STRCPY(IObuff, "first file is shorter"); - } - break; - } else if (c2 == EOF) { - STRCPY(IObuff, "second file is shorter"); - break; - } else { - line1[lineidx] = c1; - line2[lineidx] = c2; - lineidx++; - if (c1 != c2) { - snprintf((char *)IObuff, IOSIZE, - "difference at byte %" PRId64 ", line %" PRId64, - count, linecount); - break; - } - } - if (c1 == NL) { - linecount++; - lineidx = 0; - } else if (lineidx + 2 == (ptrdiff_t)sizeof(line1)) { - memmove(line1, line1 + 100, lineidx - 100); - memmove(line2, line2 + 100, lineidx - 100); - lineidx -= 100; - } - } - fclose(fd1); - fclose(fd2); - } - } - if (IObuff[0] != NUL) { - prepare_assert_error(&ga); - if (argvars[2].v_type != VAR_UNKNOWN) { - char *const tofree = encode_tv2echo(&argvars[2], NULL); - ga_concat(&ga, tofree); - xfree(tofree); - ga_concat(&ga, ": "); - } - ga_concat(&ga, (char *)IObuff); - if (lineidx > 0) { - line1[lineidx] = NUL; - line2[lineidx] = NUL; - ga_concat(&ga, " after \""); - ga_concat(&ga, line1); - if (STRCMP(line1, line2) != 0) { - ga_concat(&ga, "\" vs \""); - ga_concat(&ga, line2); - } - ga_concat(&ga, "\""); - } - assert_error(&ga); - ga_clear(&ga); - return 1; - } - return 0; -} - -int assert_inrange(typval_T *argvars) - FUNC_ATTR_NONNULL_ALL -{ - bool error = false; - - if (argvars[0].v_type == VAR_FLOAT - || argvars[1].v_type == VAR_FLOAT - || argvars[2].v_type == VAR_FLOAT) { - const float_T flower = tv_get_float(&argvars[0]); - const float_T fupper = tv_get_float(&argvars[1]); - const float_T factual = tv_get_float(&argvars[2]); - - if (factual < flower || factual > fupper) { - garray_T ga; - prepare_assert_error(&ga); - if (argvars[3].v_type != VAR_UNKNOWN) { - char_u *const tofree = (char_u *)encode_tv2string(&argvars[3], NULL); - ga_concat(&ga, (char *)tofree); - xfree(tofree); - } else { - char msg[80]; - vim_snprintf(msg, sizeof(msg), "Expected range %g - %g, but got %g", - flower, fupper, factual); - ga_concat(&ga, msg); - } - assert_error(&ga); - ga_clear(&ga); - return 1; - } - } else { - const varnumber_T lower = tv_get_number_chk(&argvars[0], &error); - const varnumber_T upper = tv_get_number_chk(&argvars[1], &error); - const varnumber_T actual = tv_get_number_chk(&argvars[2], &error); - - if (error) { - return 0; - } - if (actual < lower || actual > upper) { - garray_T ga; - prepare_assert_error(&ga); - - char msg[55]; - vim_snprintf(msg, sizeof(msg), - "range %" PRIdVARNUMBER " - %" PRIdVARNUMBER ",", - lower, upper); // -V576 - fill_assert_error(&ga, &argvars[3], (char_u *)msg, NULL, &argvars[2], - ASSERT_INRANGE); - assert_error(&ga); - ga_clear(&ga); - return 1; - } - } - return 0; -} - -/// Common for assert_true() and assert_false(). -int assert_bool(typval_T *argvars, bool is_true) - FUNC_ATTR_NONNULL_ALL -{ - bool error = false; - garray_T ga; - - if ((argvars[0].v_type != VAR_NUMBER - || (tv_get_number_chk(&argvars[0], &error) == 0) == is_true - || error) - && (argvars[0].v_type != VAR_BOOL - || (argvars[0].vval.v_bool - != (BoolVarValue)(is_true - ? kBoolVarTrue - : kBoolVarFalse)))) { - prepare_assert_error(&ga); - fill_assert_error(&ga, &argvars[1], - (char_u *)(is_true ? "True" : "False"), - NULL, &argvars[0], ASSERT_OTHER); - assert_error(&ga); - ga_clear(&ga); - return 1; - } - return 0; -} - -int assert_exception(typval_T *argvars) - FUNC_ATTR_NONNULL_ALL -{ - garray_T ga; - - const char *const error = tv_get_string_chk(&argvars[0]); - if (vimvars[VV_EXCEPTION].vv_str == NULL) { - prepare_assert_error(&ga); - ga_concat(&ga, "v:exception is not set"); - assert_error(&ga); - ga_clear(&ga); - return 1; - } else if (error != NULL - && strstr((char *)vimvars[VV_EXCEPTION].vv_str, error) == NULL) { - prepare_assert_error(&ga); - fill_assert_error(&ga, &argvars[1], NULL, &argvars[0], - &vimvars[VV_EXCEPTION].vv_tv, ASSERT_OTHER); - assert_error(&ga); - ga_clear(&ga); - return 1; - } - return 0; -} - -static void assert_append_cmd_or_arg(garray_T *gap, typval_T *argvars, const char *cmd) - FUNC_ATTR_NONNULL_ALL -{ - if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN) { - char *const tofree = encode_tv2echo(&argvars[2], NULL); - ga_concat(gap, tofree); - xfree(tofree); - } else { - ga_concat(gap, cmd); - } -} - -int assert_beeps(typval_T *argvars, bool no_beep) - FUNC_ATTR_NONNULL_ALL -{ - const char *const cmd = tv_get_string_chk(&argvars[0]); - int ret = 0; - - called_vim_beep = false; - suppress_errthrow = true; - emsg_silent = false; - do_cmdline_cmd(cmd); - if (no_beep ? called_vim_beep : !called_vim_beep) { - garray_T ga; - prepare_assert_error(&ga); - if (no_beep) { - ga_concat(&ga, "command did beep: "); - } else { - ga_concat(&ga, "command did not beep: "); - } - ga_concat(&ga, cmd); - assert_error(&ga); - ga_clear(&ga); - ret = 1; - } - - suppress_errthrow = false; - emsg_on_display = false; - return ret; -} - -int assert_fails(typval_T *argvars) - FUNC_ATTR_NONNULL_ALL -{ - const char *const cmd = tv_get_string_chk(&argvars[0]); - garray_T ga; - int ret = 0; - int save_trylevel = trylevel; - - // trylevel must be zero for a ":throw" command to be considered failed - trylevel = 0; - called_emsg = false; - suppress_errthrow = true; - emsg_silent = true; - - do_cmdline_cmd(cmd); - if (!called_emsg) { - prepare_assert_error(&ga); - ga_concat(&ga, "command did not fail: "); - assert_append_cmd_or_arg(&ga, argvars, cmd); - assert_error(&ga); - ga_clear(&ga); - ret = 1; - } else if (argvars[1].v_type != VAR_UNKNOWN) { - char buf[NUMBUFLEN]; - const char *const error = tv_get_string_buf_chk(&argvars[1], buf); - - if (error == NULL - || strstr((char *)vimvars[VV_ERRMSG].vv_str, error) == NULL) { - prepare_assert_error(&ga); - fill_assert_error(&ga, &argvars[2], NULL, &argvars[1], - &vimvars[VV_ERRMSG].vv_tv, ASSERT_OTHER); - ga_concat(&ga, ": "); - assert_append_cmd_or_arg(&ga, argvars, cmd); - assert_error(&ga); - ga_clear(&ga); - ret = 1; - } - } - - trylevel = save_trylevel; - called_emsg = false; - suppress_errthrow = false; - emsg_silent = false; - emsg_on_display = false; - set_vim_var_string(VV_ERRMSG, NULL, 0); - return ret; -} - -int assert_match_common(typval_T *argvars, assert_type_T atype) - FUNC_ATTR_NONNULL_ALL -{ - char buf1[NUMBUFLEN]; - char buf2[NUMBUFLEN]; - const char *const pat = tv_get_string_buf_chk(&argvars[0], buf1); - const char *const text = tv_get_string_buf_chk(&argvars[1], buf2); - - if (pat == NULL || text == NULL) { - emsg(_(e_invarg)); - } else if (pattern_match((char_u *)pat, (char_u *)text, false) - != (atype == ASSERT_MATCH)) { - garray_T ga; - prepare_assert_error(&ga); - fill_assert_error(&ga, &argvars[2], NULL, &argvars[0], &argvars[1], atype); - assert_error(&ga); - ga_clear(&ga); - return 1; - } - return 0; -} - /// Find a window: When using a Window ID in any tab page, when using a number /// in the current tab page. win_T *find_win_by_nr_or_id(typval_T *vp) @@ -8665,6 +8203,12 @@ int eval_isnamec1(int c) return ASCII_ISALPHA(c) || c == '_'; } +/// Get typval_T v: variable value. +typval_T *get_vim_var_tv(int idx) +{ + return &vimvars[idx].vv_tv; +} + /// Get number v: variable value. varnumber_T get_vim_var_nr(int idx) FUNC_ATTR_PURE { diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index ae9cb3b1e8..f7d9f76534 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -64,6 +64,7 @@ #include "nvim/state.h" #include "nvim/syntax.h" #include "nvim/tag.h" +#include "nvim/testing.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/version.h" @@ -448,90 +449,6 @@ static void f_argv(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/// "assert_beeps(cmd [, error])" function -static void f_assert_beeps(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_beeps(argvars, false); -} - -/// "assert_nobeep(cmd [, error])" function -static void f_assert_nobeep(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_beeps(argvars, true); -} - -/// "assert_equal(expected, actual[, msg])" function -static void f_assert_equal(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_equal_common(argvars, ASSERT_EQUAL); -} - -/// "assert_equalfile(fname-one, fname-two[, msg])" function -static void f_assert_equalfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_equalfile(argvars); -} - -/// "assert_notequal(expected, actual[, msg])" function -static void f_assert_notequal(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_equal_common(argvars, ASSERT_NOTEQUAL); -} - -/// "assert_report(msg) -static void f_assert_report(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - garray_T ga; - - prepare_assert_error(&ga); - ga_concat(&ga, tv_get_string(&argvars[0])); - assert_error(&ga); - ga_clear(&ga); - rettv->vval.v_number = 1; -} - -/// "assert_exception(string[, msg])" function -static void f_assert_exception(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_exception(argvars); -} - -/// "assert_fails(cmd [, error [, msg]])" function -static void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_fails(argvars); -} - -// "assert_false(actual[, msg])" function -static void f_assert_false(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_bool(argvars, false); -} - -/// "assert_inrange(lower, upper[, msg])" function -static void f_assert_inrange(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_inrange(argvars); -} - -/// "assert_match(pattern, actual[, msg])" function -static void f_assert_match(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_match_common(argvars, ASSERT_MATCH); -} - -/// "assert_notmatch(pattern, actual[, msg])" function -static void f_assert_notmatch(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_match_common(argvars, ASSERT_NOTMATCH); -} - -/// "assert_true(actual[, msg])" function -static void f_assert_true(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_bool(argvars, true); -} - /// "atan2()" function static void f_atan2(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -6633,7 +6550,7 @@ static inline uint32_t shuffle_xoshiro128starstar(uint32_t *const x, uint32_t *c uint32_t *const z, uint32_t *const w) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE { -#define ROTL(x, k) ((x << k) | (x >> (32 - k))) +#define ROTL(x, k) (((x) << (k)) | ((x) >> (32 - (k)))) const uint32_t result = ROTL(*y * 5, 7) * 9; const uint32_t t = *y << 9; *z ^= *x; @@ -6785,15 +6702,21 @@ static void f_range(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/// Evaluate "expr" for readdir(). -static varnumber_T readdir_checkitem(typval_T *expr, const char *name) +/// Evaluate "expr" (= "context") for readdir(). +static varnumber_T readdir_checkitem(void *context, const char *name) + FUNC_ATTR_NONNULL_ALL { + typval_T *expr = (typval_T *)context; typval_T save_val; typval_T rettv; typval_T argv[2]; varnumber_T retval = 0; bool error = false; + if (expr->v_type == VAR_UNKNOWN) { + return 1; + } + prepare_vimvar(VV_VAL, &save_val); set_vim_var_string(VV_VAL, name, -1); argv[0].v_type = VAR_STRING; @@ -6819,54 +6742,16 @@ theend: /// "readdir()" function static void f_readdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - typval_T *expr; - const char *path; - garray_T ga; - Directory dir; - tv_list_alloc_ret(rettv, kListLenUnknown); - path = tv_get_string(&argvars[0]); - expr = &argvars[1]; - ga_init(&ga, (int)sizeof(char *), 20); - - if (!os_scandir(&dir, path)) { - smsg(_(e_notopen), path); - } else { - for (;;) { - bool ignore; - - path = os_scandir_next(&dir); - if (path == NULL) { - break; - } - - ignore = (path[0] == '.' - && (path[1] == NUL || (path[1] == '.' && path[2] == NUL))); - if (!ignore && expr->v_type != VAR_UNKNOWN) { - varnumber_T r = readdir_checkitem(expr, path); - - if (r < 0) { - break; - } - if (r == 0) { - ignore = true; - } - } - - if (!ignore) { - ga_grow(&ga, 1); - ((char **)ga.ga_data)[ga.ga_len++] = xstrdup(path); - } - } - - os_closedir(&dir); - } - if (rettv->vval.v_list != NULL && ga.ga_len > 0) { - sort_strings((char_u **)ga.ga_data, ga.ga_len); + const char *path = tv_get_string(&argvars[0]); + typval_T *expr = &argvars[1]; + garray_T ga; + int ret = readdir_core(&ga, path, (void *)expr, readdir_checkitem); + if (ret == OK && ga.ga_len > 0) { for (int i = 0; i < ga.ga_len; i++) { - path = ((const char **)ga.ga_data)[i]; - tv_list_append_string(rettv->vval.v_list, path, -1); + const char *p = ((const char **)ga.ga_data)[i]; + tv_list_append_string(rettv->vval.v_list, p, -1); } } ga_clear_strings(&ga); @@ -10896,24 +10781,6 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) channel_create_event(chan, NULL); } -/// "test_garbagecollect_now()" function -static void f_test_garbagecollect_now(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - // This is dangerous, any Lists and Dicts used internally may be freed - // while still in use. - garbage_collect(true); -} - -/// "test_write_list_log()" function -static void f_test_write_list_log(typval_T *const argvars, typval_T *const rettv, FunPtr fptr) -{ - const char *const fname = tv_get_string_chk(&argvars[0]); - if (fname == NULL) { - return; - } - list_write_log(fname); -} - /// "timer_info([timer])" function static void f_timer_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) { diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index ff803c3553..98aabe89b3 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -2527,9 +2527,9 @@ int do_ecmd(int fnum, char_u *ffname, char_u *sfname, exarg_T *eap, linenr_T new // Close the link to the current buffer. This will set // oldwin->w_buffer to NULL. u_sync(false); - const bool did_decrement = close_buffer(oldwin, curbuf, - (flags & ECMD_HIDE) || curbuf->terminal ? 0 : DOBUF_UNLOAD, - false); + const bool did_decrement + = close_buffer(oldwin, curbuf, (flags & ECMD_HIDE) || curbuf->terminal ? 0 : DOBUF_UNLOAD, + false, false); // Autocommands may have closed the window. if (win_valid(the_curwin)) { @@ -2875,7 +2875,7 @@ int do_ecmd(int fnum, char_u *ffname, char_u *sfname, exarg_T *eap, linenr_T new redraw_curbuf_later(NOT_VALID); // redraw this buffer later } - if (p_im) { + if (p_im && (State & INSERT) == 0) { need_start_insertmode = true; } @@ -3627,15 +3627,22 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle sub_firstline = NULL; - // ~ in the substitute pattern is replaced with the old pattern. - // We do it here once to avoid it to be replaced over and over again. - // But don't do it when it starts with "\=", then it's an expression. assert(sub != NULL); bool sub_needs_free = false; - if (!(sub[0] == '\\' && sub[1] == '=')) { + char_u *sub_copy = NULL; + + // If the substitute pattern starts with "\=" then it's an expression. + // Make a copy, a recursive function may free it. + // Otherwise, '~' in the substitute pattern is replaced with the old + // pattern. We do it here once to avoid it to be replaced over and over + // again. + if (sub[0] == '\\' && sub[1] == '=') { + sub = vim_strsave(sub); + sub_copy = sub; + } else { char_u *source = sub; - sub = regtilde(sub, p_magic); + sub = regtilde(sub, p_magic, preview); // When previewing, the new pattern allocated by regtilde() needs to be freed // in this function because it will not be used or freed by regtilde() later. sub_needs_free = preview && sub != source; @@ -3860,13 +3867,22 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle prompt = xmallocz(ec + 1); memset(prompt, ' ', sc); memset(prompt + sc, '^', ec - sc + 1); - resp = (char_u *)getcmdline_prompt(NUL, prompt, 0, EXPAND_NOTHING, + resp = (char_u *)getcmdline_prompt(-1, prompt, 0, EXPAND_NOTHING, NULL, CALLBACK_NONE); msg_putchar('\n'); xfree(prompt); if (resp != NULL) { typed = *resp; xfree(resp); + } else { + // getcmdline_prompt() returns NULL if there is no command line to return. + typed = NUL; + } + // When ":normal" runs out of characters we get + // an empty line. Use "q" to get out of the + // loop. + if (ex_normal_busy && typed == NUL) { + typed = 'q'; } } else { char_u *orig_line = NULL; @@ -4297,18 +4313,22 @@ skip: #define PUSH_PREVIEW_LINES() \ do { \ - linenr_T match_lines = current_match.end.lnum \ - - current_match.start.lnum +1; \ - if (preview_lines.subresults.size > 0) { \ - linenr_T last = kv_last(preview_lines.subresults).end.lnum; \ - if (last == current_match.start.lnum) { \ - preview_lines.lines_needed += match_lines - 1; \ + if (preview) { \ + linenr_T match_lines = current_match.end.lnum \ + - current_match.start.lnum +1; \ + if (preview_lines.subresults.size > 0) { \ + linenr_T last = kv_last(preview_lines.subresults).end.lnum; \ + if (last == current_match.start.lnum) { \ + preview_lines.lines_needed += match_lines - 1; \ + } else { \ + preview_lines.lines_needed += match_lines; \ + } \ + } else { \ + preview_lines.lines_needed += match_lines; \ } \ - } else { \ - preview_lines.lines_needed += match_lines; \ + kv_push(preview_lines.subresults, current_match); \ } \ - kv_push(preview_lines.subresults, current_match); \ - } while (0) + } while (0) // Push the match to preview_lines. PUSH_PREVIEW_LINES(); @@ -4403,6 +4423,10 @@ skip: } vim_regfree(regmatch.regprog); + xfree(sub_copy); + if (sub_needs_free) { + xfree(sub); + } // Restore the flag values, they can be used for ":&&". subflags.do_all = save_do_all; @@ -4435,10 +4459,6 @@ skip: kv_destroy(preview_lines.subresults); - if (sub_needs_free) { - xfree(sub); - } - return preview_buf; #undef ADJUST_SUB_FIRSTLNUM #undef PUSH_PREVIEW_LINES diff --git a/src/nvim/ex_cmds.h b/src/nvim/ex_cmds.h index 241674c24c..1c95b75001 100644 --- a/src/nvim/ex_cmds.h +++ b/src/nvim/ex_cmds.h @@ -21,7 +21,7 @@ // for lnum argument in do_ecmd() #define ECMD_LASTL (linenr_T)0 // use last position in loaded file -#define ECMD_LAST (linenr_T)-1 // use last position in all files +#define ECMD_LAST ((linenr_T)-1) // use last position in all files #define ECMD_ONE (linenr_T)1 // use first line /// Previous :substitute replacement string definition diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index fe1dec7298..ac1d760bce 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -1470,7 +1470,13 @@ void ex_listdo(exarg_T *eap) size_t qf_idx = qf_get_cur_idx(eap); + // Clear 'shm' to avoid that the file message overwrites + // any output from the command. + p_shm_save = vim_strsave(p_shm); + set_option_value("shm", 0L, "", 0); ex_cnext(eap); + set_option_value("shm", 0L, (char *)p_shm_save, 0); + xfree(p_shm_save); // If jumping to the next quickfix entry fails, quit here. if (qf_get_cur_idx(eap) == qf_idx) { diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h index 92675499be..39dfc1b94c 100644 --- a/src/nvim/ex_cmds_defs.h +++ b/src/nvim/ex_cmds_defs.h @@ -87,8 +87,8 @@ typedef struct exarg exarg_T; // behavior for bad character, "++bad=" argument #define BAD_REPLACE '?' // replace it with '?' (default) -#define BAD_KEEP -1 // leave it -#define BAD_DROP -2 // erase it +#define BAD_KEEP (-1) // leave it +#define BAD_DROP (-2) // erase it typedef void (*ex_func_T)(exarg_T *eap); diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index bb6f3ba395..1f17101aca 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -1248,6 +1248,7 @@ static char_u *do_one_cmd(char_u **cmdlinep, int flags, cstack_T *cstack, LineGe const int save_msg_scroll = msg_scroll; cmdmod_T save_cmdmod; const int save_reg_executing = reg_executing; + const bool save_pending_end_reg_executing = pending_end_reg_executing; char_u *cmd; memset(&ea, 0, sizeof(ea)); @@ -2018,6 +2019,7 @@ doend: undo_cmdmod(&ea, save_msg_scroll); cmdmod = save_cmdmod; reg_executing = save_reg_executing; + pending_end_reg_executing = save_pending_end_reg_executing; if (ea.did_sandbox) { sandbox--; @@ -8529,6 +8531,7 @@ bool save_current_state(save_state_T *sst) sst->save_finish_op = finish_op; sst->save_opcount = opcount; sst->save_reg_executing = reg_executing; + sst->save_pending_end_reg_executing = pending_end_reg_executing; msg_scroll = false; // no msg scrolling in Normal mode restart_edit = 0; // don't go to Insert mode @@ -8559,6 +8562,7 @@ void restore_current_state(save_state_T *sst) finish_op = sst->save_finish_op; opcount = sst->save_opcount; reg_executing = sst->save_reg_executing; + pending_end_reg_executing = sst->save_pending_end_reg_executing; // don't reset msg_didout now msg_didout |= sst->save_msg_didout; diff --git a/src/nvim/ex_docmd.h b/src/nvim/ex_docmd.h index be9f97e27d..874e0e599e 100644 --- a/src/nvim/ex_docmd.h +++ b/src/nvim/ex_docmd.h @@ -29,6 +29,7 @@ typedef struct { bool save_finish_op; long save_opcount; int save_reg_executing; + bool save_pending_end_reg_executing; tasave_T tabuf; } save_state_T; diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 9104a2dd5a..dd6e4630d2 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -6544,7 +6544,7 @@ static int open_cmdwin(void) // win_close() may have already wiped the buffer when 'bh' is // set to 'wipe', autocommands may have closed other windows if (bufref_valid(&bufref) && bufref.br_buf != curbuf) { - close_buffer(NULL, bufref.br_buf, DOBUF_WIPE, false); + close_buffer(NULL, bufref.br_buf, DOBUF_WIPE, false, false); } // Restore window sizes. diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 7905b29876..d4407b4982 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -22,6 +22,7 @@ #include "nvim/diff.h" #include "nvim/edit.h" #include "nvim/eval/userfunc.h" +#include "nvim/eval/typval.h" #include "nvim/ex_cmds.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" @@ -79,7 +80,7 @@ #define FIO_ENDIAN_L 0x80 // little endian #define FIO_NOCONVERT 0x2000 // skip encoding conversion #define FIO_UCSBOM 0x4000 // check for BOM at start of file -#define FIO_ALL -1 // allow all formats +#define FIO_ALL (-1) // allow all formats /* When converting, a read() or write() may leave some bytes to be converted * for the next call. The value is guessed... */ @@ -1014,27 +1015,27 @@ retry: } read_buf_col += n; break; - } else { - // Append whole line and new-line. Change NL - // to NUL to reverse the effect done below. - for (ni = 0; ni < n; ni++) { - if (p[ni] == NL) { - ptr[tlen++] = NUL; - } else { - ptr[tlen++] = p[ni]; - } + } + + // Append whole line and new-line. Change NL + // to NUL to reverse the effect done below. + for (ni = 0; ni < n; ni++) { + if (p[ni] == NL) { + ptr[tlen++] = NUL; + } else { + ptr[tlen++] = p[ni]; } - ptr[tlen++] = NL; - read_buf_col = 0; - if (++read_buf_lnum > from) { - // When the last line didn't have an - // end-of-line don't add it now either. - if (!curbuf->b_p_eol) { - --tlen; - } - size = tlen; - break; + } + ptr[tlen++] = NL; + read_buf_col = 0; + if (++read_buf_lnum > from) { + // When the last line didn't have an + // end-of-line don't add it now either. + if (!curbuf->b_p_eol) { + tlen--; } + size = tlen; + break; } } } @@ -5343,37 +5344,85 @@ static void vim_maketempdir(void) (void)umask(umask_save); } +/// Core part of "readdir()" function. +/// Retrieve the list of files/directories of "path" into "gap". +/// +/// @return OK for success, FAIL for failure. +int readdir_core(garray_T *gap, const char *path, void *context, CheckItem checkitem) + FUNC_ATTR_NONNULL_ARG(1, 2) +{ + ga_init(gap, (int)sizeof(char *), 20); + + Directory dir; + if (!os_scandir(&dir, path)) { + smsg(_(e_notopen), path); + return FAIL; + } + + for (;;) { + const char *p = os_scandir_next(&dir); + if (p == NULL) { + break; + } + + bool ignore = (p[0] == '.' && (p[1] == NUL || (p[1] == '.' && p[2] == NUL))); + if (!ignore && checkitem != NULL) { + varnumber_T r = checkitem(context, p); + if (r < 0) { + break; + } + if (r == 0) { + ignore = true; + } + } + + if (!ignore) { + ga_grow(gap, 1); + ((char **)gap->ga_data)[gap->ga_len++] = xstrdup(p); + } + } + + os_closedir(&dir); + + if (gap->ga_len > 0) { + sort_strings((char_u **)gap->ga_data, gap->ga_len); + } + + return OK; +} + /// Delete "name" and everything in it, recursively. /// -/// @param name The path which should be deleted. +/// @param name The path which should be deleted. /// /// @return 0 for success, -1 if some file was not deleted. int delete_recursive(const char *name) + FUNC_ATTR_NONNULL_ALL { int result = 0; if (os_isrealdir(name)) { - snprintf((char *)NameBuff, MAXPATHL, "%s/*", name); // NOLINT - - char_u **files; - int file_count; - char_u *exp = vim_strsave(NameBuff); - if (gen_expand_wildcards(1, &exp, &file_count, &files, - EW_DIR | EW_FILE | EW_SILENT | EW_ALLLINKS - | EW_DODOT | EW_EMPTYOK) == OK) { - for (int i = 0; i < file_count; i++) { - if (delete_recursive((const char *)files[i]) != 0) { + char *exp = xstrdup(name); + garray_T ga; + if (readdir_core(&ga, exp, NULL, NULL) == OK) { + for (int i = 0; i < ga.ga_len; i++) { + vim_snprintf((char *)NameBuff, MAXPATHL, "%s/%s", exp, ((char_u **)ga.ga_data)[i]); + if (delete_recursive((const char *)NameBuff) != 0) { + // Remember the failure but continue deleting any further + // entries. result = -1; } } - FreeWild(file_count, files); + ga_clear_strings(&ga); + if (os_rmdir(exp) != 0) { + result = -1; + } } else { result = -1; } - xfree(exp); - os_rmdir(name); } else { + // Delete symlink only. result = os_remove(name) == 0 ? 0 : -1; } diff --git a/src/nvim/fileio.h b/src/nvim/fileio.h index 186d0b90ab..62d8b6142e 100644 --- a/src/nvim/fileio.h +++ b/src/nvim/fileio.h @@ -3,6 +3,8 @@ #include "nvim/autocmd.h" #include "nvim/buffer_defs.h" +#include "nvim/eval/typval.h" +#include "nvim/garray.h" #include "nvim/os/os.h" // Values for readfile() flags @@ -17,6 +19,8 @@ #define READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y)) +typedef varnumber_T (*CheckItem)(void *expr, const char *name); + #ifdef INCLUDE_GENERATED_DECLARATIONS // Events for autocommands # include "fileio.h.generated.h" diff --git a/src/nvim/fold.c b/src/nvim/fold.c index 54430d46af..3643ceb460 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -2753,7 +2753,7 @@ static void truncate_fold(win_T *const wp, fold_T *fp, linenr_T end) #define FOLD_END(fp) ((fp)->fd_top + (fp)->fd_len - 1) #define VALID_FOLD(fp, gap) \ ((gap)->ga_len > 0 && (fp) < ((fold_T *)(gap)->ga_data + (gap)->ga_len)) -#define FOLD_INDEX(fp, gap) ((size_t)(fp - ((fold_T *)(gap)->ga_data))) +#define FOLD_INDEX(fp, gap) ((size_t)((fp) - ((fold_T *)(gap)->ga_data))) void foldMoveRange(win_T *const wp, garray_T *gap, const linenr_T line1, const linenr_T line2, const linenr_T dest) { diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index b12b99b7ee..299385cb17 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -1253,7 +1253,14 @@ static int old_mod_mask; // mod_mask for ungotten character static int old_mouse_grid; // mouse_grid related to old_char static int old_mouse_row; // mouse_row related to old_char static int old_mouse_col; // mouse_col related to old_char +static int old_KeyStuffed; // whether old_char was stuffed +static bool can_get_old_char(void) +{ + // If the old character was not stuffed and characters have been added to + // the stuff buffer, need to first get the stuffed characters instead. + return old_char != -1 && (old_KeyStuffed || stuff_empty()); +} /* * Save all three kinds of typeahead, so that the user must type at a prompt. @@ -1454,7 +1461,7 @@ int vgetc(void) * If a character was put back with vungetc, it was already processed. * Return it directly. */ - if (old_char != -1) { + if (can_get_old_char()) { c = old_char; old_char = -1; mod_mask = old_mod_mask; @@ -1660,7 +1667,7 @@ int plain_vgetc(void) */ int vpeekc(void) { - if (old_char != -1) { + if (can_get_old_char()) { return old_char; } return vgetorpeek(false); @@ -2052,7 +2059,9 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) return map_result_nomatch; } -// unget one character (can only be done once!) +/// unget one character (can only be done once!) +/// If the character was stuffed, vgetc() will get it next time it is called. +/// Otherwise vgetc() will only get it when the stuff buffer is empty. void vungetc(int c) { old_char = c; @@ -2060,6 +2069,21 @@ void vungetc(int c) old_mouse_grid = mouse_grid; old_mouse_row = mouse_row; old_mouse_col = mouse_col; + old_KeyStuffed = KeyStuffed; +} + +/// When peeking and not getting a character, reg_executing cannot be cleared +/// yet, so set a flag to clear it later. +void check_end_reg_executing(bool advance) +{ + if (reg_executing != 0 && (typebuf.tb_maplen == 0 || pending_end_reg_executing)) { + if (advance) { + reg_executing = 0; + pending_end_reg_executing = false; + } else { + pending_end_reg_executing = true; + } + } } /// Gets a byte: @@ -2116,9 +2140,7 @@ static int vgetorpeek(bool advance) init_typebuf(); start_stuff(); - if (advance && typebuf.tb_maplen == 0) { - reg_executing = 0; - } + check_end_reg_executing(advance); do { // get a character: 1. from the stuffbuffer if (typeahead_char != 0) { @@ -2145,6 +2167,7 @@ static int vgetorpeek(bool advance) // If a mapped key sequence is found we go back to the start to // try re-mapping. for (;;) { + check_end_reg_executing(advance); // os_breakcheck() is slow, don't use it too often when // inside a mapping. But call it each time for typed // characters. @@ -2330,8 +2353,6 @@ static int vgetorpeek(bool advance) // cmdline window. if (p_im && (State & INSERT)) { c = Ctrl_L; - } else if (exmode_active) { - c = '\n'; } else if ((State & CMDLINE) || (cmdwin_type > 0 && tc == ESC)) { c = Ctrl_C; } else { @@ -2573,7 +2594,7 @@ int inchar(char_u *buf, int maxlen, long wait_time) // Don't use buf[] here, closescript() may have freed typebuf.tb_buf[] // and buf may be pointing inside typebuf.tb_buf[]. if (got_int) { -#define DUM_LEN MAXMAPLEN * 3 + 3 +#define DUM_LEN (MAXMAPLEN * 3 + 3) char_u dum[DUM_LEN + 1]; for (;;) { diff --git a/src/nvim/getchar.h b/src/nvim/getchar.h index f24a4e7c7c..9b8605f1df 100644 --- a/src/nvim/getchar.h +++ b/src/nvim/getchar.h @@ -61,8 +61,8 @@ typedef struct map_arguments MapArguments; #define MAP_ARGUMENTS_INIT { false, false, false, false, false, false, false, \ { 0 }, 0, NULL, 0, LUA_NOREF, false, NULL, 0, NULL } -#define KEYLEN_PART_KEY -1 // keylen value for incomplete key-code -#define KEYLEN_PART_MAP -2 // keylen value for incomplete mapping +#define KEYLEN_PART_KEY (-1) // keylen value for incomplete key-code +#define KEYLEN_PART_MAP (-2) // keylen value for incomplete mapping /// Maximum number of streams to read script from enum { NSCRIPT = 15, }; diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 605a2183f3..4aa49337cf 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -430,7 +430,7 @@ EXTERN win_T *lastwin; // last window EXTERN win_T *prevwin INIT(= NULL); // previous window #define ONE_WINDOW (firstwin == lastwin) #define FOR_ALL_FRAMES(frp, first_frame) \ - for (frp = first_frame; frp != NULL; frp = frp->fr_next) // NOLINT + for ((frp) = first_frame; (frp) != NULL; (frp) = (frp)->fr_next) // NOLINT // When using this macro "break" only breaks out of the inner loop. Use "goto" // to break out of the tabpage loop. @@ -461,7 +461,7 @@ EXTERN tabpage_T *lastused_tabpage; EXTERN bool redraw_tabline INIT(= false); // need to redraw tabline // Iterates over all tabs in the tab list -#define FOR_ALL_TABS(tp) for (tabpage_T *tp = first_tabpage; tp != NULL; tp = tp->tp_next) +#define FOR_ALL_TABS(tp) for (tabpage_T *(tp) = first_tabpage; (tp) != NULL; (tp) = (tp)->tp_next) // All buffers are linked in a list. 'firstbuf' points to the first entry, // 'lastbuf' to the last entry and 'curbuf' to the currently active buffer. @@ -477,7 +477,7 @@ EXTERN buf_T *curbuf INIT(= NULL); // currently active buffer // Iterate through all the signs placed in a buffer #define FOR_ALL_SIGNS_IN_BUF(buf, sign) \ - for (sign = buf->b_signlist; sign != NULL; sign = sign->se_next) // NOLINT + for ((sign) = (buf)->b_signlist; (sign) != NULL; (sign) = (sign)->se_next) // NOLINT // List of files being edited (global argument list). curwin->w_alist points @@ -618,7 +618,7 @@ EXTERN int inhibit_delete_count INIT(= 0); #define DBCS_CHT 950 // taiwan #define DBCS_CHTU 9950 // euc-tw #define DBCS_2BYTE 1 // 2byte- -#define DBCS_DEBUG -1 +#define DBCS_DEBUG (-1) /// Encoding used when 'fencs' is set to "default" EXTERN char_u *fenc_default INIT(= NULL); @@ -643,6 +643,8 @@ EXTERN bool ex_no_reprint INIT(=false); // No need to print after z or p. EXTERN int reg_recording INIT(= 0); // register for recording or zero EXTERN int reg_executing INIT(= 0); // register being executed or zero +// Flag set when peeking a character and found the end of executed register +EXTERN bool pending_end_reg_executing INIT(= false); EXTERN int reg_recorded INIT(= 0); // last recorded register or zero EXTERN int no_mapping INIT(= false); // currently no mapping allowed diff --git a/src/nvim/macros.h b/src/nvim/macros.h index d5611f587b..be1ab935c0 100644 --- a/src/nvim/macros.h +++ b/src/nvim/macros.h @@ -103,7 +103,7 @@ // MB_PTR_BACK(): backup a pointer to the previous character, taking care of // multi-byte characters if needed. Only use with "p" > "s" ! #define MB_PTR_BACK(s, p) \ - (p -= utf_head_off((char_u *)s, (char_u *)p - 1) + 1) + (p -= utf_head_off((char_u *)(s), (char_u *)(p) - 1) + 1) // MB_CHAR2BYTES(): convert character to bytes and advance pointer to bytes #define MB_CHAR2BYTES(c, b) ((b) += utf_char2bytes((c), (b))) @@ -133,7 +133,7 @@ #define ARRAY_LAST_ENTRY(arr) (arr)[ARRAY_SIZE(arr) - 1] // Duplicated in os/win_defs.h to avoid include-order sensitivity. -#define RGB_(r, g, b) ((r << 16) | (g << 8) | b) +#define RGB_(r, g, b) (((r) << 16) | ((g) << 8) | (b)) #define STR_(x) #x #define STR(x) STR_(x) diff --git a/src/nvim/memline.c b/src/nvim/memline.c index 1df32f79e1..5f6e1ea273 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -230,7 +230,7 @@ static linenr_T lowest_marked = 0; #define ML_INSERT 0x12 // insert line #define ML_FIND 0x13 // just find the line #define ML_FLUSH 0x02 // flush locked block -#define ML_SIMPLE(x) (x & 0x10) // DEL, INS or FIND +#define ML_SIMPLE(x) ((x) & 0x10) // DEL, INS or FIND // argument for ml_upd_block0() typedef enum { @@ -2327,95 +2327,88 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, b * We are finished, break the loop here. */ break; - } else { // pointer block full - /* - * split the pointer block - * allocate a new pointer block - * move some of the pointer into the new block - * prepare for updating the parent block - */ - for (;;) { // do this twice when splitting block 1 - hp_new = ml_new_ptr(mfp); - if (hp_new == NULL) { // TODO: try to fix tree - return FAIL; - } - pp_new = hp_new->bh_data; - - if (hp->bh_bnum != 1) { - break; - } - - /* - * if block 1 becomes full the tree is given an extra level - * The pointers from block 1 are moved into the new block. - * block 1 is updated to point to the new block - * then continue to split the new block - */ - memmove(pp_new, pp, (size_t)page_size); - pp->pb_count = 1; - pp->pb_pointer[0].pe_bnum = hp_new->bh_bnum; - pp->pb_pointer[0].pe_line_count = buf->b_ml.ml_line_count; - pp->pb_pointer[0].pe_old_lnum = 1; - pp->pb_pointer[0].pe_page_count = 1; - mf_put(mfp, hp, true, false); // release block 1 - hp = hp_new; // new block is to be split - pp = pp_new; - CHECK(stack_idx != 0, _("stack_idx should be 0")); - ip->ip_index = 0; - ++stack_idx; // do block 1 again later - } - /* - * move the pointers after the current one to the new block - * If there are none, the new entry will be in the new block. - */ - total_moved = pp->pb_count - pb_idx - 1; - if (total_moved) { - memmove(&pp_new->pb_pointer[0], - &pp->pb_pointer[pb_idx + 1], - (size_t)(total_moved) * sizeof(PTR_EN)); - pp_new->pb_count = total_moved; - pp->pb_count -= total_moved - 1; - pp->pb_pointer[pb_idx + 1].pe_bnum = bnum_right; - pp->pb_pointer[pb_idx + 1].pe_line_count = line_count_right; - pp->pb_pointer[pb_idx + 1].pe_page_count = page_count_right; - if (lnum_right) { - pp->pb_pointer[pb_idx + 1].pe_old_lnum = lnum_right; - } - } else { - pp_new->pb_count = 1; - pp_new->pb_pointer[0].pe_bnum = bnum_right; - pp_new->pb_pointer[0].pe_line_count = line_count_right; - pp_new->pb_pointer[0].pe_page_count = page_count_right; - pp_new->pb_pointer[0].pe_old_lnum = lnum_right; - } - pp->pb_pointer[pb_idx].pe_bnum = bnum_left; - pp->pb_pointer[pb_idx].pe_line_count = line_count_left; - pp->pb_pointer[pb_idx].pe_page_count = page_count_left; - if (lnum_left) { - pp->pb_pointer[pb_idx].pe_old_lnum = lnum_left; + } + // pointer block full + // + // split the pointer block + // allocate a new pointer block + // move some of the pointer into the new block + // prepare for updating the parent block + for (;;) { // do this twice when splitting block 1 + hp_new = ml_new_ptr(mfp); + if (hp_new == NULL) { // TODO(vim): try to fix tree + return FAIL; } - lnum_left = 0; - lnum_right = 0; + pp_new = hp_new->bh_data; - /* - * recompute line counts - */ - line_count_right = 0; - for (i = 0; i < (int)pp_new->pb_count; ++i) { - line_count_right += pp_new->pb_pointer[i].pe_line_count; + if (hp->bh_bnum != 1) { + break; } - line_count_left = 0; - for (i = 0; i < (int)pp->pb_count; ++i) { - line_count_left += pp->pb_pointer[i].pe_line_count; + + // if block 1 becomes full the tree is given an extra level + // The pointers from block 1 are moved into the new block. + // block 1 is updated to point to the new block + // then continue to split the new block + memmove(pp_new, pp, (size_t)page_size); + pp->pb_count = 1; + pp->pb_pointer[0].pe_bnum = hp_new->bh_bnum; + pp->pb_pointer[0].pe_line_count = buf->b_ml.ml_line_count; + pp->pb_pointer[0].pe_old_lnum = 1; + pp->pb_pointer[0].pe_page_count = 1; + mf_put(mfp, hp, true, false); // release block 1 + hp = hp_new; // new block is to be split + pp = pp_new; + CHECK(stack_idx != 0, _("stack_idx should be 0")); + ip->ip_index = 0; + stack_idx++; // do block 1 again later + } + // move the pointers after the current one to the new block + // If there are none, the new entry will be in the new block. + total_moved = pp->pb_count - pb_idx - 1; + if (total_moved) { + memmove(&pp_new->pb_pointer[0], + &pp->pb_pointer[pb_idx + 1], + (size_t)(total_moved) * sizeof(PTR_EN)); + pp_new->pb_count = total_moved; + pp->pb_count -= total_moved - 1; + pp->pb_pointer[pb_idx + 1].pe_bnum = bnum_right; + pp->pb_pointer[pb_idx + 1].pe_line_count = line_count_right; + pp->pb_pointer[pb_idx + 1].pe_page_count = page_count_right; + if (lnum_right) { + pp->pb_pointer[pb_idx + 1].pe_old_lnum = lnum_right; } + } else { + pp_new->pb_count = 1; + pp_new->pb_pointer[0].pe_bnum = bnum_right; + pp_new->pb_pointer[0].pe_line_count = line_count_right; + pp_new->pb_pointer[0].pe_page_count = page_count_right; + pp_new->pb_pointer[0].pe_old_lnum = lnum_right; + } + pp->pb_pointer[pb_idx].pe_bnum = bnum_left; + pp->pb_pointer[pb_idx].pe_line_count = line_count_left; + pp->pb_pointer[pb_idx].pe_page_count = page_count_left; + if (lnum_left) { + pp->pb_pointer[pb_idx].pe_old_lnum = lnum_left; + } + lnum_left = 0; + lnum_right = 0; - bnum_left = hp->bh_bnum; - bnum_right = hp_new->bh_bnum; - page_count_left = 1; - page_count_right = 1; - mf_put(mfp, hp, true, false); - mf_put(mfp, hp_new, true, false); + // recompute line counts + line_count_right = 0; + for (i = 0; i < (int)pp_new->pb_count; i++) { + line_count_right += pp_new->pb_pointer[i].pe_line_count; } + line_count_left = 0; + for (i = 0; i < (int)pp->pb_count; i++) { + line_count_left += pp->pb_pointer[i].pe_line_count; + } + + bnum_left = hp->bh_bnum; + bnum_right = hp_new->bh_bnum; + page_count_left = 1; + page_count_right = 1; + mf_put(mfp, hp, true, false); + mf_put(mfp, hp_new, true, false); } /* diff --git a/src/nvim/memory.c b/src/nvim/memory.c index 6cdc4f1fde..c895cc1ec6 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -691,7 +691,7 @@ void free_all_mem(void) bufref_T bufref; set_bufref(&bufref, buf); nextbuf = buf->b_next; - close_buffer(NULL, buf, DOBUF_WIPE, false); + close_buffer(NULL, buf, DOBUF_WIPE, false, false); // Didn't work, try next one. buf = bufref_valid(&bufref) ? nextbuf : firstbuf; } diff --git a/src/nvim/mouse.h b/src/nvim/mouse.h index bf4f9c57e5..04acb88bb3 100644 --- a/src/nvim/mouse.h +++ b/src/nvim/mouse.h @@ -38,8 +38,8 @@ // Direction for nv_mousescroll() and ins_mousescroll() #define MSCR_DOWN 0 // DOWN must be FALSE #define MSCR_UP 1 -#define MSCR_LEFT -1 -#define MSCR_RIGHT -2 +#define MSCR_LEFT (-1) +#define MSCR_RIGHT (-2) #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 8332b8bbd6..9cee2de0c5 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -1517,9 +1517,12 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent) for (;;) { which_button = get_mouse_button(KEY2TERMCAP1(c), &is_click, &is_drag); if (is_drag) { - /* If the next character is the same mouse event then use that - * one. Speeds up dragging the status line. */ - if (vpeekc() != NUL) { + // If the next character is the same mouse event then use that + // one. Speeds up dragging the status line. + // Note: Since characters added to the stuff buffer in the code + // below need to come before the next character, do not do this + // when the current character was stuffed. + if (!KeyStuffed && vpeekc() != NUL) { int nc; int save_mouse_grid = mouse_grid; int save_mouse_row = mouse_row; @@ -2616,7 +2619,7 @@ void may_clear_cmdline(void) } // Routines for displaying a partly typed command -#define SHOWCMD_BUFLEN SHOWCMD_COLS + 1 + 30 +#define SHOWCMD_BUFLEN (SHOWCMD_COLS + 1 + 30) static char_u showcmd_buf[SHOWCMD_BUFLEN]; static char_u old_showcmd_buf[SHOWCMD_BUFLEN]; // For push_showcmd() static bool showcmd_is_clear = true; diff --git a/src/nvim/option.c b/src/nvim/option.c index 9fd0e0b222..551bc1fbdf 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -5140,43 +5140,43 @@ char *set_option_value(const char *const name, const long number, const char *co s = ""; } return set_string_option(opt_idx, s, opt_flags); - } else { - varp = get_varp_scope(&(options[opt_idx]), opt_flags); - if (varp != NULL) { // hidden option is not changed - if (number == 0 && string != NULL) { - int idx; - - // Either we are given a string or we are setting option - // to zero. - for (idx = 0; string[idx] == '0'; idx++) {} - if (string[idx] != NUL || idx == 0) { - // There's another character after zeros or the string - // is empty. In both cases, we are trying to set a - // num option using a string. - semsg(_("E521: Number required: &%s = '%s'"), - name, string); - return NULL; // do nothing as we hit an error - } - } - long numval = number; - if (opt_flags & OPT_CLEAR) { - if ((int *)varp == &curbuf->b_p_ar) { - numval = -1; - } else if ((long *)varp == &curbuf->b_p_ul) { - numval = NO_LOCAL_UNDOLEVEL; - } else if ((long *)varp == &curwin->w_p_so || (long *)varp == &curwin->w_p_siso) { - numval = -1; - } else { - char *s = NULL; - (void)get_option_value(name, &numval, (char_u **)&s, OPT_GLOBAL); - } + } + + varp = get_varp_scope(&(options[opt_idx]), opt_flags); + if (varp != NULL) { // hidden option is not changed + if (number == 0 && string != NULL) { + int idx; + + // Either we are given a string or we are setting option + // to zero. + for (idx = 0; string[idx] == '0'; idx++) {} + if (string[idx] != NUL || idx == 0) { + // There's another character after zeros or the string + // is empty. In both cases, we are trying to set a + // num option using a string. + semsg(_("E521: Number required: &%s = '%s'"), + name, string); + return NULL; // do nothing as we hit an error } - if (flags & P_NUM) { - return set_num_option(opt_idx, varp, numval, NULL, 0, opt_flags); + } + long numval = number; + if (opt_flags & OPT_CLEAR) { + if ((int *)varp == &curbuf->b_p_ar) { + numval = -1; + } else if ((long *)varp == &curbuf->b_p_ul) { + numval = NO_LOCAL_UNDOLEVEL; + } else if ((long *)varp == &curwin->w_p_so || (long *)varp == &curwin->w_p_siso) { + numval = -1; } else { - return set_bool_option(opt_idx, varp, (int)numval, opt_flags); + char *s = NULL; + (void)get_option_value(name, &numval, (char_u **)&s, OPT_GLOBAL); } } + if (flags & P_NUM) { + return set_num_option(opt_idx, varp, numval, NULL, 0, opt_flags); + } else { + return set_bool_option(opt_idx, varp, (int)numval, opt_flags); + } } } return NULL; diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 98c2b84770..6ce95ea690 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -57,7 +57,7 @@ #define ENC_DFLT "utf-8" // end-of-line style -#define EOL_UNKNOWN -1 // not defined yet +#define EOL_UNKNOWN (-1) // not defined yet #define EOL_UNIX 0 // NL #define EOL_DOS 1 // CR NL #define EOL_MAC 2 // CR @@ -899,7 +899,7 @@ enum { }; // Value for b_p_ul indicating the global value must be used. -#define NO_LOCAL_UNDOLEVEL -123456 +#define NO_LOCAL_UNDOLEVEL (-123456) #define SB_MAX 100000 // Maximum 'scrollback' value. diff --git a/src/nvim/os/win_defs.h b/src/nvim/os/win_defs.h index efef77be7b..1ae86d6bbe 100644 --- a/src/nvim/os/win_defs.h +++ b/src/nvim/os/win_defs.h @@ -36,7 +36,7 @@ // Windows defines a RGB macro that produces 0x00bbggrr color values for use // with GDI. Our macro is different, and we don't use GDI. // Duplicated from macros.h to avoid include-order sensitivity. -#define RGB_(r, g, b) ((r << 16) | (g << 8) | b) +#define RGB_(r, g, b) (((r) << 16) | ((g) << 8) | (b)) #ifdef _MSC_VER # ifndef inline diff --git a/src/nvim/pos.h b/src/nvim/pos.h index 51991ed314..c60b008696 100644 --- a/src/nvim/pos.h +++ b/src/nvim/pos.h @@ -1,10 +1,8 @@ #ifndef NVIM_POS_H #define NVIM_POS_H -// for INT_MAX, LONG_MAX et al. -#include <limits.h> - -typedef long linenr_T; // line number type +/// Line number type +typedef long linenr_T; /// Format used to print values which have linenr_T type #define PRIdLINENR "ld" @@ -15,31 +13,29 @@ typedef int colnr_T; /// Maximal (invalid) line number enum { MAXLNUM = 0x7fffffff, }; + /// Maximal column number /// MAXCOL used to be INT_MAX, but with 64 bit ints that results in running /// out of memory when trying to allocate a very long line. enum { MAXCOL = 0x7fffffff, }; -// Minimum line number + +/// Minimum line number enum { MINLNUM = 1, }; -// minimum column number + +/// Minimum column number enum { MINCOL = 1, }; -/* - * position in file or buffer - */ +/// position in file or buffer typedef struct { - linenr_T lnum; // line number - colnr_T col; // column number + linenr_T lnum; ///< line number + colnr_T col; ///< column number colnr_T coladd; } pos_T; - -/* - * Same, but without coladd. - */ +/// position in file or buffer, but without coladd typedef struct { - linenr_T lnum; // line number - colnr_T col; // column number + linenr_T lnum; ///< line number + colnr_T col; ///< column number } lpos_T; #endif // NVIM_POS_H diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index c8d9e91fb5..4ad5e40fee 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -229,28 +229,28 @@ typedef struct vgr_args_S { static char_u *e_no_more_items = (char_u *)N_("E553: No more items"); // Quickfix window check helper macro -#define IS_QF_WINDOW(wp) (bt_quickfix(wp->w_buffer) && wp->w_llist_ref == NULL) +#define IS_QF_WINDOW(wp) (bt_quickfix((wp)->w_buffer) && (wp)->w_llist_ref == NULL) // Location list window check helper macro -#define IS_LL_WINDOW(wp) (bt_quickfix(wp->w_buffer) && wp->w_llist_ref != NULL) +#define IS_LL_WINDOW(wp) (bt_quickfix((wp)->w_buffer) && (wp)->w_llist_ref != NULL) // Quickfix and location list stack check helper macros -#define IS_QF_STACK(qi) (qi->qfl_type == QFLT_QUICKFIX) -#define IS_LL_STACK(qi) (qi->qfl_type == QFLT_LOCATION) -#define IS_QF_LIST(qfl) (qfl->qfl_type == QFLT_QUICKFIX) -#define IS_LL_LIST(qfl) (qfl->qfl_type == QFLT_LOCATION) +#define IS_QF_STACK(qi) ((qi)->qfl_type == QFLT_QUICKFIX) +#define IS_LL_STACK(qi) ((qi)->qfl_type == QFLT_LOCATION) +#define IS_QF_LIST(qfl) ((qfl)->qfl_type == QFLT_QUICKFIX) +#define IS_LL_LIST(qfl) ((qfl)->qfl_type == QFLT_LOCATION) // // Return location list for window 'wp' // For location list window, return the referenced location list // -#define GET_LOC_LIST(wp) (IS_LL_WINDOW(wp) ? wp->w_llist_ref : wp->w_llist) +#define GET_LOC_LIST(wp) (IS_LL_WINDOW(wp) ? (wp)->w_llist_ref : (wp)->w_llist) // Macro to loop through all the items in a quickfix list // Quickfix item index starts from 1, so i below starts at 1 #define FOR_ALL_QFL_ITEMS(qfl, qfp, i) \ - for (i = 1, qfp = qfl->qf_start; /* NOLINT(readability/braces) */ \ - !got_int && i <= qfl->qf_count && qfp != NULL; \ - i++, qfp = qfp->qf_next) + for ((i) = 1, (qfp) = (qfl)->qf_start; /* NOLINT(readability/braces) */ \ + !got_int && (i) <= (qfl)->qf_count && (qfp) != NULL; \ + (i)++, (qfp) = (qfp)->qf_next) // Looking up a buffer can be slow if there are many. Remember the last one @@ -1721,7 +1721,7 @@ static void wipe_qf_buffer(qf_info_T *qi) if (qfbuf != NULL && qfbuf->b_nwindows == 0) { // If the quickfix buffer is not loaded in any window, then // wipe the buffer. - close_buffer(NULL, qfbuf, DOBUF_WIPE, false); + close_buffer(NULL, qfbuf, DOBUF_WIPE, false, false); qi->qf_bufnr = INVALID_QFBUFNR; } } @@ -5843,7 +5843,7 @@ static void wipe_dummy_buffer(buf_T *buf, char_u *dirname_start) static void unload_dummy_buffer(buf_T *buf, char_u *dirname_start) { if (curbuf != buf) { // safety check - close_buffer(NULL, buf, DOBUF_UNLOAD, false); + close_buffer(NULL, buf, DOBUF_UNLOAD, false, true); // When autocommands/'autochdir' option changed directory: go back. restore_start_dir(dirname_start); diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index d3c43e530a..b61c9a2cf5 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -279,15 +279,15 @@ static void init_class_tab(void) done = true; } -# define ri_digit(c) (c < 0x100 && (class_tab[c] & RI_DIGIT)) -# define ri_hex(c) (c < 0x100 && (class_tab[c] & RI_HEX)) -# define ri_octal(c) (c < 0x100 && (class_tab[c] & RI_OCTAL)) -# define ri_word(c) (c < 0x100 && (class_tab[c] & RI_WORD)) -# define ri_head(c) (c < 0x100 && (class_tab[c] & RI_HEAD)) -# define ri_alpha(c) (c < 0x100 && (class_tab[c] & RI_ALPHA)) -# define ri_lower(c) (c < 0x100 && (class_tab[c] & RI_LOWER)) -# define ri_upper(c) (c < 0x100 && (class_tab[c] & RI_UPPER)) -# define ri_white(c) (c < 0x100 && (class_tab[c] & RI_WHITE)) +# define ri_digit(c) ((c) < 0x100 && (class_tab[c] & RI_DIGIT)) +# define ri_hex(c) ((c) < 0x100 && (class_tab[c] & RI_HEX)) +# define ri_octal(c) ((c) < 0x100 && (class_tab[c] & RI_OCTAL)) +# define ri_word(c) ((c) < 0x100 && (class_tab[c] & RI_WORD)) +# define ri_head(c) ((c) < 0x100 && (class_tab[c] & RI_HEAD)) +# define ri_alpha(c) ((c) < 0x100 && (class_tab[c] & RI_ALPHA)) +# define ri_lower(c) ((c) < 0x100 && (class_tab[c] & RI_LOWER)) +# define ri_upper(c) ((c) < 0x100 && (class_tab[c] & RI_UPPER)) +# define ri_white(c) ((c) < 0x100 && (class_tab[c] & RI_WHITE)) // flags for regflags #define RF_ICASE 1 // ignore case @@ -1531,7 +1531,7 @@ static fptr_T do_Lower(int *d, int c) * * The tildes are parsed once before the first call to vim_regsub(). */ -char_u *regtilde(char_u *source, int magic) +char_u *regtilde(char_u *source, int magic, bool preview) { char_u *newsub = source; char_u *tmpsub; @@ -1576,7 +1576,7 @@ char_u *regtilde(char_u *source, int magic) } // Only change reg_prev_sub when not previewing. - if (!(State & CMDPREVIEW)) { + if (!preview) { xfree(reg_prev_sub); if (newsub != source) { // newsub was allocated, just keep it reg_prev_sub = newsub; @@ -2504,7 +2504,8 @@ long vim_regexec_multi( char_u *pat = vim_strsave(((nfa_regprog_T *)rmp->regprog)->pattern); p_re = BACKTRACKING_ENGINE; - vim_regfree(rmp->regprog); + regprog_T *prev_prog = rmp->regprog; + report_re_switch(pat); // checking for \z misuse was already done when compiling for NFA, // allow all here @@ -2512,7 +2513,13 @@ long vim_regexec_multi( rmp->regprog = vim_regcomp(pat, re_flags); reg_do_extmatch = 0; - if (rmp->regprog != NULL) { + if (rmp->regprog == NULL) { + // Somehow compiling the pattern failed now, put back the + // previous one to avoid "regprog" becoming NULL. + rmp->regprog = prev_prog; + } else { + vim_regfree(prev_prog); + rmp->regprog->re_in_use = true; result = rmp->regprog->engine->regexec_multi(rmp, win, buf, lnum, col, tm, timed_out); diff --git a/src/nvim/regexp_defs.h b/src/nvim/regexp_defs.h index 1d112bd64a..913cfb2074 100644 --- a/src/nvim/regexp_defs.h +++ b/src/nvim/regexp_defs.h @@ -34,7 +34,7 @@ // In the NFA engine: how many states are allowed. #define NFA_MAX_STATES 100000 -#define NFA_TOO_EXPENSIVE -1 +#define NFA_TOO_EXPENSIVE (-1) // Which regexp engine to use? Needed for vim_regcomp(). // Must match with 'regexpengine'. diff --git a/src/nvim/screen.c b/src/nvim/screen.c index f922a50260..baeb2fcb03 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -2119,13 +2119,13 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc // draw_state: items that are drawn in sequence: #define WL_START 0 // nothing done yet -#define WL_CMDLINE WL_START + 1 // cmdline window column -#define WL_FOLD WL_CMDLINE + 1 // 'foldcolumn' -#define WL_SIGN WL_FOLD + 1 // column for signs -#define WL_NR WL_SIGN + 1 // line number -#define WL_BRI WL_NR + 1 // 'breakindent' -#define WL_SBR WL_BRI + 1 // 'showbreak' or 'diff' -#define WL_LINE WL_SBR + 1 // text in the line +#define WL_CMDLINE (WL_START + 1) // cmdline window column +#define WL_FOLD (WL_CMDLINE + 1) // 'foldcolumn' +#define WL_SIGN (WL_FOLD + 1) // column for signs +#define WL_NR (WL_SIGN + 1) // line number +#define WL_BRI (WL_NR + 1) // 'breakindent' +#define WL_SBR (WL_BRI + 1) // 'showbreak' or 'diff' +#define WL_LINE (WL_SBR + 1) // text in the line int draw_state = WL_START; // what to draw next int syntax_flags = 0; diff --git a/src/nvim/screen.h b/src/nvim/screen.h index d15e4b7e45..5e86dacd25 100644 --- a/src/nvim/screen.h +++ b/src/nvim/screen.h @@ -65,8 +65,8 @@ extern StlClickDefinition *tab_page_click_defs; /// Size of the tab_page_click_defs array extern long tab_page_click_defs_size; -#define W_ENDCOL(wp) (wp->w_wincol + wp->w_width) -#define W_ENDROW(wp) (wp->w_winrow + wp->w_height) +#define W_ENDCOL(wp) ((wp)->w_wincol + (wp)->w_width) +#define W_ENDROW(wp) ((wp)->w_winrow + (wp)->w_height) #ifdef INCLUDE_GENERATED_DECLARATIONS # include "screen.h.generated.h" diff --git a/src/nvim/search.c b/src/nvim/search.c index a69bd641f8..c30e69391f 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -4830,15 +4830,15 @@ typedef struct { /// bonus if the first letter is matched #define FIRST_LETTER_BONUS 15 /// penalty applied for every letter in str before the first match -#define LEADING_LETTER_PENALTY -5 +#define LEADING_LETTER_PENALTY (-5) /// maximum penalty for leading letters -#define MAX_LEADING_LETTER_PENALTY -15 +#define MAX_LEADING_LETTER_PENALTY (-15) /// penalty for every letter that doesn't match -#define UNMATCHED_LETTER_PENALTY -1 +#define UNMATCHED_LETTER_PENALTY (-1) /// penalty for gap in matching positions (-2 * k) -#define GAP_PENALTY -2 +#define GAP_PENALTY (-2) /// Score for a string that doesn't fuzzy match the pattern -#define SCORE_NONE -9999 +#define SCORE_NONE (-9999) #define FUZZY_MATCH_RECURSION_LIMIT 10 diff --git a/src/nvim/sha256.c b/src/nvim/sha256.c index 9d6a2f2c41..2c6199bf8b 100644 --- a/src/nvim/sha256.c +++ b/src/nvim/sha256.c @@ -73,8 +73,8 @@ static void sha256_process(context_sha256_T *ctx, const char_u data[SHA256_BUFFE GET_UINT32(W[14], data, 56); GET_UINT32(W[15], data, 60); -#define SHR(x, n) ((x & 0xFFFFFFFF) >> n) -#define ROTR(x, n) (SHR(x, n) | (x << (32 - n))) +#define SHR(x, n) (((x) & 0xFFFFFFFF) >> (n)) +#define ROTR(x, n) (SHR(x, n) | ((x) << (32 - (n)))) #define S0(x) (ROTR(x, 7) ^ ROTR(x, 18) ^ SHR(x, 3)) #define S1(x) (ROTR(x, 17) ^ ROTR(x, 19) ^ SHR(x, 10)) @@ -82,17 +82,16 @@ static void sha256_process(context_sha256_T *ctx, const char_u data[SHA256_BUFFE #define S2(x) (ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22)) #define S3(x) (ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25)) -#define F0(x, y, z) ((x & y) | (z & (x | y))) -#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F0(x, y, z) (((x) & (y)) | ((z) & ((x) | (y)))) +#define F1(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) #define R(t) \ - (W[t] = S1(W[t - 2]) + W[t - 7] + \ - S0(W[t - 15]) + W[t - 16]) + (W[t] = S1(W[(t) - 2]) + W[(t) - 7] + S0(W[(t) - 15]) + W[(t) - 16]) #define P(a, b, c, d, e, f, g, h, x, K) { \ - temp1 = h + S3(e) + F1(e, f, g) + K + x; \ + temp1 = (h) + S3(e) + F1(e, f, g) + (K) + (x); \ temp2 = S2(a) + F0(a, b, c); \ - d += temp1; h = temp1 + temp2; \ + (d) += temp1; (h) = temp1 + temp2; \ } A = ctx->state[0]; diff --git a/src/nvim/shada.c b/src/nvim/shada.c index b909888783..6c0add87d3 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -1238,7 +1238,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) // string is close to useless: you can only use it with :& or :~ and // that’s all because s//~ is not available until the first call to // regtilde. Vim was not calling this for some reason. - (void)(char *)regtilde((char_u *)cur_entry.data.sub_string.sub, p_magic); + (void)(char *)regtilde((char_u *)cur_entry.data.sub_string.sub, p_magic, false); // Do not free shada entry: its allocated memory was saved above. break; case kSDItemHistoryEntry: diff --git a/src/nvim/spell.c b/src/nvim/spell.c index e65ca2e6a6..9fa594bc96 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -62,11 +62,11 @@ // Disadvantage: When "the" is typed as "hte" it sounds quite different ("@" // vs "ht") and goes down in the list. // Used when 'spellsuggest' is set to "best". -#define RESCORE(word_score, sound_score) ((3 * word_score + sound_score) / 4) +#define RESCORE(word_score, sound_score) ((3 * (word_score) + (sound_score)) / 4) // Do the opposite: based on a maximum end score and a known sound score, // compute the maximum word score that can be used. -#define MAXSCORE(word_score, sound_score) ((4 * word_score - sound_score) / 3) +#define MAXSCORE(word_score, sound_score) ((4 * (word_score) - (sound_score)) / 3) #include <assert.h> #include <inttypes.h> @@ -122,7 +122,7 @@ #define WF_CAPMASK (WF_ONECAP | WF_ALLCAP | WF_KEEPCAP | WF_FIXCAP) // Result values. Lower number is accepted over higher one. -#define SP_BANNED -1 +#define SP_BANNED (-1) #define SP_RARE 0 #define SP_OK 1 #define SP_LOCAL 2 @@ -176,7 +176,7 @@ typedef struct { #define SUG(ga, i) (((suggest_T *)(ga).ga_data)[i]) // True if a word appears in the list of banned words. -#define WAS_BANNED(su, word) (!HASHITEM_EMPTY(hash_find(&su->su_banned, word))) +#define WAS_BANNED(su, word) (!HASHITEM_EMPTY(hash_find(&(su)->su_banned, word))) // Number of suggestions kept when cleaning up. We need to keep more than // what is displayed, because when rescore_suggestions() is called the score @@ -225,7 +225,7 @@ typedef struct { #define SCORE_SFMAX2 300 // maximum score for second try #define SCORE_SFMAX3 400 // maximum score for third try -#define SCORE_BIG SCORE_INS * 3 // big difference +#define SCORE_BIG (SCORE_INS * 3) // big difference #define SCORE_MAXMAX 999999 // accept any score #define SCORE_LIMITMAX 350 // for spell_edit_score_limit() @@ -3690,7 +3690,7 @@ static void suggest_try_change(suginfo_T *su) // Check the maximum score, if we go over it we won't try this change. #define TRY_DEEPER(su, stack, depth, add) \ - (stack[depth].ts_score + (add) < su->su_maxscore) + ((depth) < MAXWLEN - 1 && (stack)[depth].ts_score + (add) < (su)->su_maxscore) // Try finding suggestions by adding/removing/swapping letters. // @@ -3794,6 +3794,10 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so } } + // The loop may take an indefinite amount of time. Break out after five + // sectonds. TODO(vim): add an option for the time limit. + proftime_T time_limit = profile_setlimit(5000); + // Loop to find all suggestions. At each round we either: // - For the current state try one operation, advance "ts_curi", // increase "depth". @@ -3824,7 +3828,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // At end of a prefix or at start of prefixtree: check for // following word. - if (byts[arridx] == 0 || n == STATE_NOPREFIX) { + if (depth < MAXWLEN - 1 && (byts[arridx] == 0 || n == STATE_NOPREFIX)) { // Set su->su_badflags to the caps type at this position. // Use the caps type until here for the prefix itself. n = nofold_len(fword, sp->ts_fidx, su->su_badptr); @@ -4927,6 +4931,9 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so if (--breakcheckcount == 0) { os_breakcheck(); breakcheckcount = 1000; + if (profile_passed_limit(time_limit)) { + got_int = true; + } } } } @@ -5284,7 +5291,7 @@ static int stp_sal_score(suggest_T *stp, suginfo_T *su, slang_T *slang, char_u * } static sftword_T dumsft; -#define HIKEY2SFT(p) ((sftword_T *)(p - (dumsft.sft_word - (char_u *)&dumsft))) +#define HIKEY2SFT(p) ((sftword_T *)((p) - (dumsft.sft_word - (char_u *)&dumsft))) #define HI2SFT(hi) HIKEY2SFT((hi)->hi_key) // Prepare for calling suggest_try_soundalike(). diff --git a/src/nvim/spell_defs.h b/src/nvim/spell_defs.h index 220f51cd2f..12e44cb7ff 100644 --- a/src/nvim/spell_defs.h +++ b/src/nvim/spell_defs.h @@ -93,9 +93,9 @@ typedef int salfirst_T; // Values for SP_*ERROR are negative, positive values are used by // read_cnt_string(). -#define SP_TRUNCERROR -1 // spell file truncated error -#define SP_FORMERROR -2 // format error in spell file -#define SP_OTHERERROR -3 // other error while reading spell file +#define SP_TRUNCERROR (-1) // spell file truncated error +#define SP_FORMERROR (-2) // format error in spell file +#define SP_OTHERERROR (-3) // other error while reading spell file // Structure used to store words and other info for one language, loaded from // a .spl file. diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index 07058b34fd..3d3e6e728c 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -1850,7 +1850,7 @@ static void spell_reload_one(char_u *fname, bool added_word) // In the postponed prefixes tree wn_flags is used to store the WFP_ flags, // but it must be negative to indicate the prefix tree to tree_add_word(). // Use a negative number with the lower 8 bits zero. -#define PFX_FLAGS -256 +#define PFX_FLAGS (-256) // flags for "condit" argument of store_aff_word() #define CONDIT_COMB 1 // affix must combine diff --git a/src/nvim/state.c b/src/nvim/state.c index 3a7636085b..34e3ddf654 100644 --- a/src/nvim/state.c +++ b/src/nvim/state.c @@ -40,7 +40,7 @@ void state_enter(VimState *s) int key; getkey: - // Expand mappings first by calling vpeekc() directly. + // Apply mappings first by calling vpeekc() directly. // - If vpeekc() returns non-NUL, there is a character already available for processing, so // don't block for events. vgetc() may still block, in case of an incomplete UTF-8 sequence. // - If vpeekc() returns NUL, vgetc() will block, and there are three cases: @@ -76,6 +76,9 @@ getkey: } if (key == K_EVENT) { + // An event handler may use the value of reg_executing. + // Clear it if it should be cleared when getting the next character. + check_end_reg_executing(true); may_sync_undo(); } diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index f829b6a270..d884ad704b 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -181,7 +181,7 @@ static char *(spo_name_tab[SPO_COUNT]) = #define SYN_ITEMS(buf) ((synpat_T *)((buf)->b_syn_patterns.ga_data)) -#define NONE_IDX -2 // value of sp_sync_idx for "NONE" +#define NONE_IDX (-2) // value of sp_sync_idx for "NONE" /* * Flags for b_syn_sync_flags: @@ -267,9 +267,9 @@ static int keepend_level = -1; static char msg_no_items[] = N_("No Syntax items defined for this buffer"); // value of si_idx for keywords -#define KEYWORD_IDX -1 +#define KEYWORD_IDX (-1) // valid of si_cont_list for containing all but contained groups -#define ID_LIST_ALL (int16_t *)-1 +#define ID_LIST_ALL ((int16_t *)-1) static int next_seqnr = 1; // value to use for si_seqnr @@ -2289,55 +2289,52 @@ static void check_state_ends(void) next_match_idx = 0; next_match_col = MAXCOL; break; - } else { - // handle next_list, unless at end of line and no "skipnl" or - // "skipempty" - current_next_list = cur_si->si_next_list; - current_next_flags = cur_si->si_flags; - if (!(current_next_flags & (HL_SKIPNL | HL_SKIPEMPTY)) - && syn_getcurline()[current_col] == NUL) { - current_next_list = NULL; - } + } + + // handle next_list, unless at end of line and no "skipnl" or + // "skipempty" + current_next_list = cur_si->si_next_list; + current_next_flags = cur_si->si_flags; + if (!(current_next_flags & (HL_SKIPNL | HL_SKIPEMPTY)) + && syn_getcurline()[current_col] == NUL) { + current_next_list = NULL; + } + + // When the ended item has "extend", another item with + // "keepend" now needs to check for its end. + had_extend = (cur_si->si_flags & HL_EXTEND); - // When the ended item has "extend", another item with - // "keepend" now needs to check for its end. - had_extend = (cur_si->si_flags & HL_EXTEND); + pop_current_state(); - pop_current_state(); + if (GA_EMPTY(¤t_state)) { + break; + } + if (had_extend && keepend_level >= 0) { + syn_update_ends(false); if (GA_EMPTY(¤t_state)) { break; } + } - if (had_extend && keepend_level >= 0) { - syn_update_ends(false); - if (GA_EMPTY(¤t_state)) { - break; - } - } - - cur_si = &CUR_STATE(current_state.ga_len - 1); + cur_si = &CUR_STATE(current_state.ga_len - 1); - /* - * Only for a region the search for the end continues after - * the end of the contained item. If the contained match - * included the end-of-line, break here, the region continues. - * Don't do this when: - * - "keepend" is used for the contained item - * - not at the end of the line (could be end="x$"me=e-1). - * - "excludenl" is used (HL_HAS_EOL won't be set) - */ - if (cur_si->si_idx >= 0 - && SYN_ITEMS(syn_block)[cur_si->si_idx].sp_type - == SPTYPE_START - && !(cur_si->si_flags & (HL_MATCH | HL_KEEPEND))) { - update_si_end(cur_si, (int)current_col, true); - check_keepend(); - if ((current_next_flags & HL_HAS_EOL) - && keepend_level < 0 - && syn_getcurline()[current_col] == NUL) { - break; - } + // Only for a region the search for the end continues after + // the end of the contained item. If the contained match + // included the end-of-line, break here, the region continues. + // Don't do this when: + // - "keepend" is used for the contained item + // - not at the end of the line (could be end="x$"me=e-1). + // - "excludenl" is used (HL_HAS_EOL won't be set) + if (cur_si->si_idx >= 0 + && SYN_ITEMS(syn_block)[cur_si->si_idx].sp_type == SPTYPE_START + && !(cur_si->si_flags & (HL_MATCH | HL_KEEPEND))) { + update_si_end(cur_si, (int)current_col, true); + check_keepend(); + if ((current_next_flags & HL_HAS_EOL) + && keepend_level < 0 + && syn_getcurline()[current_col] == NUL) { + break; } } } else { diff --git a/src/nvim/syntax_defs.h b/src/nvim/syntax_defs.h index a005f67209..c656f21181 100644 --- a/src/nvim/syntax_defs.h +++ b/src/nvim/syntax_defs.h @@ -7,7 +7,7 @@ #define SST_MAX_ENTRIES 1000 // maximal size for state stack array #define SST_FIX_STATES 7 // size of sst_stack[]. #define SST_DIST 16 // normal distance between entries -#define SST_INVALID (synstate_T *)-1 // invalid syn_state pointer +#define SST_INVALID ((synstate_T *)-1) // invalid syn_state pointer typedef struct syn_state synstate_T; diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index 76c69ad10b..c1a120efa4 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -299,6 +299,40 @@ func Test_WinClosed() unlet g:triggered endfunc +func Test_WinClosed_throws() + vnew + let bnr = bufnr() + call assert_equal(1, bufloaded(bnr)) + augroup test-WinClosed + autocmd WinClosed * throw 'foo' + augroup END + try + close + catch /.*/ + endtry + call assert_equal(0, bufloaded(bnr)) + + autocmd! test-WinClosed + augroup! test-WinClosed +endfunc + +func Test_WinClosed_throws_with_tabs() + tabnew + let bnr = bufnr() + call assert_equal(1, bufloaded(bnr)) + augroup test-WinClosed + autocmd WinClosed * throw 'foo' + augroup END + try + close + catch /.*/ + endtry + call assert_equal(0, bufloaded(bnr)) + + autocmd! test-WinClosed + augroup! test-WinClosed +endfunc + func s:AddAnAutocmd() augroup vimBarTest au BufReadCmd * echo 'hello' diff --git a/src/nvim/testdir/test_cdo.vim b/src/nvim/testdir/test_cdo.vim index aa2e4f1a8c..dbed7df4ac 100644 --- a/src/nvim/testdir/test_cdo.vim +++ b/src/nvim/testdir/test_cdo.vim @@ -1,30 +1,29 @@ " Tests for the :cdo, :cfdo, :ldo and :lfdo commands -if !has('quickfix') - throw 'Skipped: quickfix feature missing' -endif +source check.vim +CheckFeature quickfix " Create the files used by the tests -function SetUp() +func SetUp() call writefile(["Line1", "Line2", "Line3"], 'Xtestfile1') call writefile(["Line1", "Line2", "Line3"], 'Xtestfile2') call writefile(["Line1", "Line2", "Line3"], 'Xtestfile3') -endfunction +endfunc " Remove the files used by the tests -function TearDown() +func TearDown() call delete('Xtestfile1') call delete('Xtestfile2') call delete('Xtestfile3') -endfunction +endfunc " Returns the current line in '<filename> <linenum>L <column>C' format -function GetRuler() +func GetRuler() return expand('%') . ' ' . line('.') . 'L' . ' ' . col('.') . 'C' -endfunction +endfunc " Tests for the :cdo and :ldo commands -function XdoTests(cchar) +func XdoTests(cchar) enew " Shortcuts for calling the cdo and ldo commands @@ -133,10 +132,10 @@ function XdoTests(cchar) exe XdoCmd call assert_equal(['Xtestfile3 3L 1C'], l) -endfunction +endfunc " Tests for the :cfdo and :lfdo commands -function XfdoTests(cchar) +func XfdoTests(cchar) enew " Shortcuts for calling the cfdo and lfdo commands @@ -190,16 +189,28 @@ function XfdoTests(cchar) exe XfdoCmd call assert_equal(['Xtestfile2 2L 5C'], l) -endfunction +endfunc " Tests for cdo and cfdo -function Test_cdo() +func Test_cdo() call XdoTests('c') call XfdoTests('c') -endfunction +endfunc " Tests for ldo and lfdo -function Test_ldo() +func Test_ldo() call XdoTests('l') call XfdoTests('l') -endfunction +endfunc + +" Test for making 'shm' doesn't interfere with the output. +func Test_cdo_print() + enew | only! + cgetexpr ["Xtestfile1:1:Line1", "Xtestfile2:1:Line1", "Xtestfile3:1:Line1"] + cdo print + call assert_equal('Line1', Screenline(&lines)) + call assert_equal('Line1', Screenline(&lines - 3)) + call assert_equal('Line1', Screenline(&lines - 6)) +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim index 10eb979b45..be9a77ee75 100644 --- a/src/nvim/testdir/test_diffmode.vim +++ b/src/nvim/testdir/test_diffmode.vim @@ -1295,4 +1295,41 @@ func Test_diff_filler_cursorcolumn() endfunc +func Test_diff_binary() + CheckScreendump + + let content =<< trim END + call setline(1, ['a', 'b', "c\n", 'd', 'e', 'f', 'g']) + vnew + call setline(1, ['A', 'b', 'c', 'd', 'E', 'f', 'g']) + windo diffthis + wincmd p + norm! gg0 + redraw! + END + call writefile(content, 'Xtest_diff_bin') + let buf = RunVimInTerminal('-S Xtest_diff_bin', {}) + + " Test using internal diff + call VerifyScreenDump(buf, 'Test_diff_bin_01', {}) + + " Test using internal diff and case folding + call term_sendkeys(buf, ":set diffopt+=icase\<cr>") + call term_sendkeys(buf, "\<C-l>") + call VerifyScreenDump(buf, 'Test_diff_bin_02', {}) + " Test using external diff + call term_sendkeys(buf, ":set diffopt=filler\<cr>") + call term_sendkeys(buf, "\<C-l>") + call VerifyScreenDump(buf, 'Test_diff_bin_03', {}) + " Test using external diff and case folding + call term_sendkeys(buf, ":set diffopt=filler,icase\<cr>") + call term_sendkeys(buf, "\<C-l>") + call VerifyScreenDump(buf, 'Test_diff_bin_04', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('Xtest_diff_bin') + set diffopt&vim +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim index 360b3aaaa0..eea5d190b2 100644 --- a/src/nvim/testdir/test_edit.vim +++ b/src/nvim/testdir/test_edit.vim @@ -1627,6 +1627,29 @@ func Test_edit_is_a_directory() call delete(dirname, 'rf') endfunc +" Using :edit without leaving 'insertmode' should not cause Insert mode to be +" re-entered immediately after <C-L> +func Test_edit_insertmode_ex_edit() + CheckRunVimInTerminal + + let lines =<< trim END + set insertmode noruler + inoremap <C-B> <Cmd>edit Xfoo<CR> + END + call writefile(lines, 'Xtest_edit_insertmode_ex_edit') + + let buf = RunVimInTerminal('-S Xtest_edit_insertmode_ex_edit', #{rows: 6}) + call TermWait(buf, 50) + call assert_match('^-- INSERT --\s*$', term_getline(buf, 6)) + call term_sendkeys(buf, "\<C-B>\<C-L>") + call TermWait(buf, 50) + call assert_notmatch('^-- INSERT --\s*$', term_getline(buf, 6)) + + " clean up + call StopVimInTerminal(buf) + call delete('Xtest_edit_insertmode_ex_edit') +endfunc + func Test_edit_browse() " in the GUI this opens a file picker, we only test the terminal behavior CheckNotGui diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index 720c003ace..42271a014d 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -290,6 +290,7 @@ let s:filename_checks = { \ 'kivy': ['file.kv'], \ 'kix': ['file.kix'], \ 'kotlin': ['file.kt', 'file.ktm', 'file.kts'], + \ 'krl': ['file.sub', 'file.Sub', 'file.SUB'], \ 'kscript': ['file.ks'], \ 'kwt': ['file.k'], \ 'lace': ['file.ace', 'file.ACE'], @@ -394,7 +395,7 @@ let s:filename_checks = { \ 'perl': ['file.plx', 'file.al', 'file.psgi', 'gitolite.rc', '.gitolite.rc', 'example.gitolite.rc'], \ 'pf': ['pf.conf'], \ 'pfmain': ['main.cf'], - \ 'php': ['file.php', 'file.php9', 'file.phtml', 'file.ctp'], + \ 'php': ['file.php', 'file.php9', 'file.phtml', 'file.ctp', 'file.phpt'], \ 'lpc': ['file.lpc', 'file.ulpc'], \ 'pike': ['file.pike', 'file.pmod'], \ 'cmod': ['file.cmod'], @@ -842,6 +843,30 @@ func Test_d_file() filetype off endfunc +func Test_dat_file() + filetype on + + call writefile(['&ACCESS'], 'datfile.dat') + split datfile.dat + call assert_equal('krl', &filetype) + bwipe! + call delete('datfile.dat') + + call writefile([' DEFDAT datfile'], 'datfile.Dat') + split datfile.Dat + call assert_equal('krl', &filetype) + bwipe! + call delete('datfile.Dat') + + call writefile(['', 'defdat datfile'], 'datfile.DAT') + split datfile.DAT + call assert_equal('krl', &filetype) + bwipe! + call delete('datfile.DAT') + + filetype off +endfunc + func Test_dep3patch_file() filetype on @@ -1284,6 +1309,30 @@ func Test_pp_file() filetype off endfunc +func Test_src_file() + filetype on + + call writefile(['&ACCESS'], 'srcfile.src') + split srcfile.src + call assert_equal('krl', &filetype) + bwipe! + call delete('srcfile.src') + + call writefile([' DEF srcfile()'], 'srcfile.Src') + split srcfile.Src + call assert_equal('krl', &filetype) + bwipe! + call delete('srcfile.Src') + + call writefile(['', 'global def srcfile()'], 'srcfile.SRC') + split srcfile.SRC + call assert_equal('krl', &filetype) + bwipe! + call delete('srcfile.SRC') + + filetype off +endfunc + func Test_tex_file() filetype on diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index c2b5653a29..f8be250f73 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -1620,10 +1620,6 @@ func Test_platform_name() endfunc func Test_readdir() - if isdirectory('Xdir') - call delete('Xdir', 'rf') - endif - call mkdir('Xdir') call writefile([], 'Xdir/foo.txt') call writefile([], 'Xdir/bar.txt') @@ -1653,6 +1649,30 @@ func Test_readdir() call delete('Xdir', 'rf') endfunc +func Test_delete_rf() + call mkdir('Xdir') + call writefile([], 'Xdir/foo.txt') + call writefile([], 'Xdir/bar.txt') + call mkdir('Xdir/[a-1]') " issue #696 + call writefile([], 'Xdir/[a-1]/foo.txt') + call writefile([], 'Xdir/[a-1]/bar.txt') + call assert_true(filereadable('Xdir/foo.txt')) + call assert_true('Xdir/[a-1]/foo.txt'->filereadable()) + + call assert_equal(0, delete('Xdir', 'rf')) + call assert_false(filereadable('Xdir/foo.txt')) + call assert_false(filereadable('Xdir/[a-1]/foo.txt')) + + if has('unix') + call mkdir('Xdir/Xdir2', 'p') + silent !chmod 555 Xdir + call assert_equal(-1, delete('Xdir/Xdir2', 'rf')) + call assert_equal(-1, delete('Xdir', 'rf')) + silent !chmod 755 Xdir + call assert_equal(0, delete('Xdir', 'rf')) + endif +endfunc + func Test_call() call assert_equal(3, call('len', [123])) call assert_equal(3, 'len'->call([123])) diff --git a/src/nvim/testdir/test_highlight.vim b/src/nvim/testdir/test_highlight.vim index 5588e515e1..efdf44a0d6 100644 --- a/src/nvim/testdir/test_highlight.vim +++ b/src/nvim/testdir/test_highlight.vim @@ -615,6 +615,14 @@ func Test_cursorcolumn_insert_on_tab() call TermWait(buf) call VerifyScreenDump(buf, 'Test_cursorcolumn_insert_on_tab_2', {}) + call term_sendkeys(buf, "\<C-O>") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_cursorcolumn_insert_on_tab_3', {}) + + call term_sendkeys(buf, 'i') + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_cursorcolumn_insert_on_tab_2', {}) + call StopVimInTerminal(buf) call delete('Xcuc_insert_on_tab') endfunc diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim index 186fa8871f..24eaf9e8b1 100644 --- a/src/nvim/testdir/test_ins_complete.vim +++ b/src/nvim/testdir/test_ins_complete.vim @@ -707,4 +707,23 @@ func Test_z1_complete_no_history() close! endfunc +func FooBarComplete(findstart, base) + if a:findstart + return col('.') - 1 + else + return ["Foo", "Bar", "}"] + endif +endfunc + +func Test_complete_smartindent() + new + setlocal smartindent completefunc=FooBarComplete + + exe "norm! o{\<cr>\<c-x>\<c-u>\<c-p>}\<cr>\<esc>" + let result = getline(1,'$') + call assert_equal(['', '{','}',''], result) + bw! + delfunction! FooBarComplete +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_mapping.vim b/src/nvim/testdir/test_mapping.vim index 98440ccdd7..a8dd0ca286 100644 --- a/src/nvim/testdir/test_mapping.vim +++ b/src/nvim/testdir/test_mapping.vim @@ -676,4 +676,38 @@ func Test_plug_remap() %bw! endfunc +" Test for mapping <LeftDrag> in Insert mode +func Test_mouse_drag_insert_map() + CheckFunction test_setmouse + set mouse=a + func ClickExpr() + call test_setmouse(1, 1) + return "\<LeftMouse>" + endfunc + func DragExpr() + call test_setmouse(1, 2) + return "\<LeftDrag>" + endfunc + inoremap <expr> <F2> ClickExpr() + imap <expr> <F3> DragExpr() + + inoremap <LeftDrag> <LeftDrag><Cmd>let g:dragged = 1<CR> + exe "normal i\<F2>\<F3>" + call assert_equal(1, g:dragged) + call assert_equal('v', mode()) + exe "normal! \<C-\>\<C-N>" + unlet g:dragged + + inoremap <LeftDrag> <LeftDrag><C-\><C-N> + exe "normal i\<F2>\<F3>" + call assert_equal('n', mode()) + + iunmap <LeftDrag> + iunmap <F2> + iunmap <F3> + delfunc ClickExpr + delfunc DragExpr + set mouse& +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim index f45cd96733..1feffe1f8c 100644 --- a/src/nvim/testdir/test_normal.vim +++ b/src/nvim/testdir/test_normal.vim @@ -1843,6 +1843,16 @@ fun! Test_normal33_g_cmd2() bw! endfunc +func Test_normal_ex_substitute() + " This was hanging on the substitute prompt. + new + call setline(1, 'a') + exe "normal! gggQs/a/b/c\<CR>" + call assert_equal('a', getline(1)) + bwipe! +endfunc + +" Test for g CTRL-G func Test_g_ctrl_g() new diff --git a/src/nvim/testdir/test_regexp_latin.vim b/src/nvim/testdir/test_regexp_latin.vim index b733e334de..13e44b090f 100644 --- a/src/nvim/testdir/test_regexp_latin.vim +++ b/src/nvim/testdir/test_regexp_latin.vim @@ -6,7 +6,10 @@ scriptencoding latin1 source check.vim func s:equivalence_test() - let str = "AÀÁÂÃÄÅ B C D EÈÉÊË F G H IÌÍÎÏ J K L M NÑ OÒÓÔÕÖØ P Q R S T UÙÚÛÜ V W X YÝ Z aàáâãäå b c d eèéêë f g h iìíîï j k l m nñ oòóôõöø p q r s t uùúûü v w x yýÿ z" + let str = 'A B C D E F G H I J K L M N O P Q R S T U V W X Y Z ' + \ .. 'a b c d e f g h i j k l m n o p q r s t u v w x y z ' + \ .. "0 1 2 3 4 5 6 7 8 9 " + \ .. "` ~ ! ? ; : . , / \\ ' \" | < > [ ] { } ( ) @ # $ % ^ & * _ - + \b \e \f \n \r \t" let groups = split(str) for group1 in groups for c in split(group1, '\zs') @@ -337,7 +340,7 @@ func Test_regexp_single_line_pat() call add(tl, [2, '\v((ab)|c*)+', 'abcccaba', 'abcccab', '', 'ab']) call add(tl, [2, '\v(a(c*)+b)+', 'acbababaaa', 'acbabab', 'ab', '']) call add(tl, [2, '\v(a|b*)+', 'aaaa', 'aaaa', '']) - call add(tl, [2, '\p*', 'aá ', 'aá ']) + call add(tl, [2, '\p*', 'a ', 'a ']) " Test greedy-ness and lazy-ness call add(tl, [2, 'a\{-2,7}','aaaaaaaaaaaaa', 'aa']) @@ -790,12 +793,12 @@ endfunc " Check patterns matching cursor position. func s:curpos_test2() new - call setline(1, ['1', '2 foobar eins zwei drei vier fünf sechse', - \ '3 foobar eins zwei drei vier fünf sechse', - \ '4 foobar eins zwei drei vier fünf sechse', - \ '5 foobar eins zwei drei vier fünf sechse', - \ '6 foobar eins zwei drei vier fünf sechse', - \ '7 foobar eins zwei drei vier fünf sechse']) + call setline(1, ['1', '2 foobar eins zwei drei vier fnf sechse', + \ '3 foobar eins zwei drei vier fnf sechse', + \ '4 foobar eins zwei drei vier fnf sechse', + \ '5 foobar eins zwei drei vier fnf sechse', + \ '6 foobar eins zwei drei vier fnf sechse', + \ '7 foobar eins zwei drei vier fnf sechse']) call setpos('.', [0, 2, 10, 0]) s/\%.c.*//g call setpos('.', [0, 3, 15, 0]) @@ -805,10 +808,10 @@ func s:curpos_test2() call assert_equal(['1', \ '2 foobar ', \ '', - \ '4 foobar eins zwei drei vier fünf sechse', + \ '4 foobar eins zwei drei vier fnf sechse', \ '5 _', - \ '6 foobar eins zwei drei vier fünf sechse', - \ '7 foobar eins zwei drei vier fünf sechse'], + \ '6 foobar eins zwei drei vier fnf sechse', + \ '7 foobar eins zwei drei vier fnf sechse'], \ getline(1, '$')) call assert_fails('call search("\\%.1l")', 'E1204:') call assert_fails('call search("\\%.1c")', 'E1204:') @@ -819,12 +822,12 @@ endfunc " Check patterns matching before or after cursor position. func s:curpos_test3() new - call setline(1, ['1', '2 foobar eins zwei drei vier fünf sechse', - \ '3 foobar eins zwei drei vier fünf sechse', - \ '4 foobar eins zwei drei vier fünf sechse', - \ '5 foobar eins zwei drei vier fünf sechse', - \ '6 foobar eins zwei drei vier fünf sechse', - \ '7 foobar eins zwei drei vier fünf sechse']) + call setline(1, ['1', '2 foobar eins zwei drei vier fnf sechse', + \ '3 foobar eins zwei drei vier fnf sechse', + \ '4 foobar eins zwei drei vier fnf sechse', + \ '5 foobar eins zwei drei vier fnf sechse', + \ '6 foobar eins zwei drei vier fnf sechse', + \ '7 foobar eins zwei drei vier fnf sechse']) call setpos('.', [0, 2, 10, 0]) " Note: This removes all columns, except for the column directly in front of " the cursor. Bug???? @@ -838,27 +841,27 @@ func s:curpos_test3() call setpos('.', [0, 6, 4, 0]) :s/\%>.v.*$/_/ call assert_equal(['1', - \ ' eins zwei drei vier fünf sechse', + \ ' eins zwei drei vier fnf sechse', \ '3 foobar e', - \ '4 foobar eins zwei drei vier fünf sechse', - \ '_foobar eins zwei drei vier fünf sechse', + \ '4 foobar eins zwei drei vier fnf sechse', + \ '_foobar eins zwei drei vier fnf sechse', \ '6 fo_', - \ '7 foobar eins zwei drei vier fünf sechse'], + \ '7 foobar eins zwei drei vier fnf sechse'], \ getline(1, '$')) sil %d - call setline(1, ['1', '2 foobar eins zwei drei vier fünf sechse', - \ '3 foobar eins zwei drei vier fünf sechse', - \ '4 foobar eins zwei drei vier fünf sechse', - \ '5 foobar eins zwei drei vier fünf sechse', - \ '6 foobar eins zwei drei vier fünf sechse', - \ '7 foobar eins zwei drei vier fünf sechse']) + call setline(1, ['1', '2 foobar eins zwei drei vier fnf sechse', + \ '3 foobar eins zwei drei vier fnf sechse', + \ '4 foobar eins zwei drei vier fnf sechse', + \ '5 foobar eins zwei drei vier fnf sechse', + \ '6 foobar eins zwei drei vier fnf sechse', + \ '7 foobar eins zwei drei vier fnf sechse']) call setpos('.', [0, 4, 4, 0]) %s/\%<.l.*// call setpos('.', [0, 5, 4, 0]) %s/\%>.l.*// call assert_equal(['', '', '', - \ '4 foobar eins zwei drei vier fünf sechse', - \ '5 foobar eins zwei drei vier fünf sechse', + \ '4 foobar eins zwei drei vier fnf sechse', + \ '5 foobar eins zwei drei vier fnf sechse', \ '', ''], \ getline(1, '$')) bwipe! diff --git a/src/nvim/testdir/test_registers.vim b/src/nvim/testdir/test_registers.vim index f78b748d71..c623edd5a1 100644 --- a/src/nvim/testdir/test_registers.vim +++ b/src/nvim/testdir/test_registers.vim @@ -690,5 +690,23 @@ func Test_record_in_select_mode() bwipe! endfunc +func Test_end_reg_executing() + nnoremap s <Nop> + let @a = 's' + call feedkeys("@aqaq\<Esc>", 'tx') + call assert_equal('', @a) + call assert_equal('', getline(1)) + + call setline(1, 'aaa') + nnoremap s qa + let @a = 'fa' + call feedkeys("@asq\<Esc>", 'tx') + call assert_equal('', @a) + call assert_equal('aaa', getline(1)) + + nunmap s + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim index 1ecb5c8070..56ed97cdd9 100644 --- a/src/nvim/testdir/test_spell.vim +++ b/src/nvim/testdir/test_spell.vim @@ -407,7 +407,7 @@ func Test_zz_basic() \ ) call assert_equal("gebletegek", soundfold('goobledygoook')) - call assert_equal("kepereneven", 'kóopërÿnôven'->soundfold()) + call assert_equal("kepereneven", 'koprnven'->soundfold()) call assert_equal("everles gesvets etele", soundfold('oeverloos gezwets edale')) endfunc @@ -588,7 +588,7 @@ func Test_zz_sal_and_addition() mkspell! Xtest Xtest set spl=Xtest.latin1.spl spell call assert_equal('kbltykk', soundfold('goobledygoook')) - call assert_equal('kprnfn', soundfold('kóopërÿnôven')) + call assert_equal('kprnfn', soundfold('koprnven')) call assert_equal('*fls kswts tl', soundfold('oeverloos gezwets edale')) "also use an addition file @@ -681,6 +681,14 @@ func Test_spell_long_word() set nospell endfunc +func Test_spellsuggest_too_deep() + " This was incrementing "depth" over MAXWLEN. + new + norm s000G00000000000000 + sil norm ..vzG................vvzG0 v z= + bwipe! +endfunc + func LoadAffAndDic(aff_contents, dic_contents) throw 'skipped: Nvim does not support enc=latin1' set enc=latin1 diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim index 9710a7ab84..86fd0147a5 100644 --- a/src/nvim/testdir/test_substitute.vim +++ b/src/nvim/testdir/test_substitute.vim @@ -819,4 +819,22 @@ func Test_substitute_skipped_range() bwipe! endfunc +" This was using "old_sub" after it was freed. +func Test_using_old_sub() + " set compatible maxfuncdepth=10 + set maxfuncdepth=10 + new + call setline(1, 'some text.') + func Repl() + ~ + s/ + endfunc + silent! s/\%')/\=Repl() + + delfunc Repl + bwipe! + set nocompatible +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_trycatch.vim b/src/nvim/testdir/test_trycatch.vim index 7e513180a3..adc1745b39 100644 --- a/src/nvim/testdir/test_trycatch.vim +++ b/src/nvim/testdir/test_trycatch.vim @@ -1972,6 +1972,29 @@ func Test_builtin_func_error() call assert_equal('jlmnpqrtueghivyzACD', g:Xpath) endfunc -" Modelines {{{1 +func Test_reload_in_try_catch() + call writefile(['x'], 'Xreload') + set autoread + edit Xreload + tabnew + call writefile(['xx'], 'Xreload') + augroup ReLoad + au FileReadPost Xreload let x = doesnotexist + au BufReadPost Xreload let x = doesnotexist + augroup END + try + edit Xreload + catch + endtry + tabnew + + tabclose + tabclose + autocmd! ReLoad + set noautoread + bwipe! Xreload + call delete('Xreload') +endfunc + +" Modeline {{{1 " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker -"------------------------------------------------------------------------------- diff --git a/src/nvim/testing.c b/src/nvim/testing.c new file mode 100644 index 0000000000..5771ec4445 --- /dev/null +++ b/src/nvim/testing.c @@ -0,0 +1,562 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +// testing.c: Support for tests + +#include "nvim/eval/encode.h" +#include "nvim/ex_docmd.h" +#include "nvim/eval.h" +#include "nvim/os/os.h" +#include "nvim/testing.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "testing.c.generated.h" +#endif + +/// Prepare "gap" for an assert error and add the sourcing position. +static void prepare_assert_error(garray_T *gap) +{ + char buf[NUMBUFLEN]; + + ga_init(gap, 1, 100); + if (sourcing_name != NULL) { + ga_concat(gap, (char *)sourcing_name); + if (sourcing_lnum > 0) { + ga_concat(gap, " "); + } + } + if (sourcing_lnum > 0) { + vim_snprintf(buf, ARRAY_SIZE(buf), "line %" PRId64, (int64_t)sourcing_lnum); + ga_concat(gap, buf); + } + if (sourcing_name != NULL || sourcing_lnum > 0) { + ga_concat(gap, ": "); + } +} + +/// Append "p[clen]" to "gap", escaping unprintable characters. +/// Changes NL to \n, CR to \r, etc. +static void ga_concat_esc(garray_T *gap, const char_u *p, int clen) + FUNC_ATTR_NONNULL_ALL +{ + char_u buf[NUMBUFLEN]; + + if (clen > 1) { + memmove(buf, p, (size_t)clen); + buf[clen] = NUL; + ga_concat(gap, (char *)buf); + } else { + switch (*p) { + case BS: + ga_concat(gap, "\\b"); break; + case ESC: + ga_concat(gap, "\\e"); break; + case FF: + ga_concat(gap, "\\f"); break; + case NL: + ga_concat(gap, "\\n"); break; + case TAB: + ga_concat(gap, "\\t"); break; + case CAR: + ga_concat(gap, "\\r"); break; + case '\\': + ga_concat(gap, "\\\\"); break; + default: + if (*p < ' ') { + vim_snprintf((char *)buf, NUMBUFLEN, "\\x%02x", *p); + ga_concat(gap, (char *)buf); + } else { + ga_append(gap, (char)(*p)); + } + break; + } + } +} + +/// Append "str" to "gap", escaping unprintable characters. +/// Changes NL to \n, CR to \r, etc. +static void ga_concat_shorten_esc(garray_T *gap, const char_u *str) + FUNC_ATTR_NONNULL_ARG(1) +{ + char_u buf[NUMBUFLEN]; + + if (str == NULL) { + ga_concat(gap, "NULL"); + return; + } + + for (const char_u *p = str; *p != NUL; p++) { + int same_len = 1; + const char_u *s = p; + const int c = mb_ptr2char_adv(&s); + const int clen = (int)(s - p); + while (*s != NUL && c == utf_ptr2char(s)) { + same_len++; + s += clen; + } + if (same_len > 20) { + ga_concat(gap, "\\["); + ga_concat_esc(gap, p, clen); + ga_concat(gap, " occurs "); + vim_snprintf((char *)buf, NUMBUFLEN, "%d", same_len); + ga_concat(gap, (char *)buf); + ga_concat(gap, " times]"); + p = s - 1; + } else { + ga_concat_esc(gap, p, clen); + } + } +} + +/// Fill "gap" with information about an assert error. +static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char_u *exp_str, + typval_T *exp_tv, typval_T *got_tv, assert_type_T atype) +{ + char_u *tofree; + + if (opt_msg_tv->v_type != VAR_UNKNOWN) { + tofree = (char_u *)encode_tv2echo(opt_msg_tv, NULL); + ga_concat(gap, (char *)tofree); + xfree(tofree); + ga_concat(gap, ": "); + } + + if (atype == ASSERT_MATCH || atype == ASSERT_NOTMATCH) { + ga_concat(gap, "Pattern "); + } else if (atype == ASSERT_NOTEQUAL) { + ga_concat(gap, "Expected not equal to "); + } else { + ga_concat(gap, "Expected "); + } + + if (exp_str == NULL) { + tofree = (char_u *)encode_tv2string(exp_tv, NULL); + ga_concat_shorten_esc(gap, tofree); + xfree(tofree); + } else { + ga_concat_shorten_esc(gap, exp_str); + } + + if (atype != ASSERT_NOTEQUAL) { + if (atype == ASSERT_MATCH) { + ga_concat(gap, " does not match "); + } else if (atype == ASSERT_NOTMATCH) { + ga_concat(gap, " does match "); + } else { + ga_concat(gap, " but got "); + } + tofree = (char_u *)encode_tv2string(got_tv, NULL); + ga_concat_shorten_esc(gap, tofree); + xfree(tofree); + } +} + +static int assert_equal_common(typval_T *argvars, assert_type_T atype) + FUNC_ATTR_NONNULL_ALL +{ + garray_T ga; + + if (tv_equal(&argvars[0], &argvars[1], false, false) + != (atype == ASSERT_EQUAL)) { + prepare_assert_error(&ga); + fill_assert_error(&ga, &argvars[2], NULL, + &argvars[0], &argvars[1], atype); + assert_error(&ga); + ga_clear(&ga); + return 1; + } + return 0; +} + +static int assert_match_common(typval_T *argvars, assert_type_T atype) + FUNC_ATTR_NONNULL_ALL +{ + char buf1[NUMBUFLEN]; + char buf2[NUMBUFLEN]; + const char *const pat = tv_get_string_buf_chk(&argvars[0], buf1); + const char *const text = tv_get_string_buf_chk(&argvars[1], buf2); + + if (pat == NULL || text == NULL) { + emsg(_(e_invarg)); + } else if (pattern_match((char_u *)pat, (char_u *)text, false) + != (atype == ASSERT_MATCH)) { + garray_T ga; + prepare_assert_error(&ga); + fill_assert_error(&ga, &argvars[2], NULL, &argvars[0], &argvars[1], atype); + assert_error(&ga); + ga_clear(&ga); + return 1; + } + return 0; +} + +/// Common for assert_true() and assert_false(). +static int assert_bool(typval_T *argvars, bool is_true) + FUNC_ATTR_NONNULL_ALL +{ + bool error = false; + garray_T ga; + + if ((argvars[0].v_type != VAR_NUMBER + || (tv_get_number_chk(&argvars[0], &error) == 0) == is_true + || error) + && (argvars[0].v_type != VAR_BOOL + || (argvars[0].vval.v_bool + != (BoolVarValue)(is_true + ? kBoolVarTrue + : kBoolVarFalse)))) { + prepare_assert_error(&ga); + fill_assert_error(&ga, &argvars[1], + (char_u *)(is_true ? "True" : "False"), + NULL, &argvars[0], ASSERT_OTHER); + assert_error(&ga); + ga_clear(&ga); + return 1; + } + return 0; +} + +static void assert_append_cmd_or_arg(garray_T *gap, typval_T *argvars, const char *cmd) + FUNC_ATTR_NONNULL_ALL +{ + if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN) { + char *const tofree = encode_tv2echo(&argvars[2], NULL); + ga_concat(gap, tofree); + xfree(tofree); + } else { + ga_concat(gap, cmd); + } +} + +static int assert_beeps(typval_T *argvars, bool no_beep) + FUNC_ATTR_NONNULL_ALL +{ + const char *const cmd = tv_get_string_chk(&argvars[0]); + int ret = 0; + + called_vim_beep = false; + suppress_errthrow = true; + emsg_silent = false; + do_cmdline_cmd(cmd); + if (no_beep ? called_vim_beep : !called_vim_beep) { + garray_T ga; + prepare_assert_error(&ga); + if (no_beep) { + ga_concat(&ga, "command did beep: "); + } else { + ga_concat(&ga, "command did not beep: "); + } + ga_concat(&ga, cmd); + assert_error(&ga); + ga_clear(&ga); + ret = 1; + } + + suppress_errthrow = false; + emsg_on_display = false; + return ret; +} + +/// "assert_beeps(cmd [, error])" function +void f_assert_beeps(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_beeps(argvars, false); +} + +/// "assert_nobeep(cmd [, error])" function +void f_assert_nobeep(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_beeps(argvars, true); +} + +/// "assert_equal(expected, actual[, msg])" function +void f_assert_equal(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_equal_common(argvars, ASSERT_EQUAL); +} + +static int assert_equalfile(typval_T *argvars) + FUNC_ATTR_NONNULL_ALL +{ + char buf1[NUMBUFLEN]; + char buf2[NUMBUFLEN]; + const char *const fname1 = tv_get_string_buf_chk(&argvars[0], buf1); + const char *const fname2 = tv_get_string_buf_chk(&argvars[1], buf2); + garray_T ga; + + if (fname1 == NULL || fname2 == NULL) { + return 0; + } + + IObuff[0] = NUL; + FILE *const fd1 = os_fopen(fname1, READBIN); + char line1[200]; + char line2[200]; + ptrdiff_t lineidx = 0; + if (fd1 == NULL) { + snprintf((char *)IObuff, IOSIZE, (char *)e_notread, fname1); + } else { + FILE *const fd2 = os_fopen(fname2, READBIN); + if (fd2 == NULL) { + fclose(fd1); + snprintf((char *)IObuff, IOSIZE, (char *)e_notread, fname2); + } else { + int64_t linecount = 1; + for (int64_t count = 0;; count++) { + const int c1 = fgetc(fd1); + const int c2 = fgetc(fd2); + if (c1 == EOF) { + if (c2 != EOF) { + STRCPY(IObuff, "first file is shorter"); + } + break; + } else if (c2 == EOF) { + STRCPY(IObuff, "second file is shorter"); + break; + } else { + line1[lineidx] = (char)c1; + line2[lineidx] = (char)c2; + lineidx++; + if (c1 != c2) { + snprintf((char *)IObuff, IOSIZE, + "difference at byte %" PRId64 ", line %" PRId64, + count, linecount); + break; + } + } + if (c1 == NL) { + linecount++; + lineidx = 0; + } else if (lineidx + 2 == (ptrdiff_t)sizeof(line1)) { + memmove(line1, line1 + 100, (size_t)(lineidx - 100)); + memmove(line2, line2 + 100, (size_t)(lineidx - 100)); + lineidx -= 100; + } + } + fclose(fd1); + fclose(fd2); + } + } + if (IObuff[0] != NUL) { + prepare_assert_error(&ga); + if (argvars[2].v_type != VAR_UNKNOWN) { + char *const tofree = encode_tv2echo(&argvars[2], NULL); + ga_concat(&ga, tofree); + xfree(tofree); + ga_concat(&ga, ": "); + } + ga_concat(&ga, (char *)IObuff); + if (lineidx > 0) { + line1[lineidx] = NUL; + line2[lineidx] = NUL; + ga_concat(&ga, " after \""); + ga_concat(&ga, line1); + if (STRCMP(line1, line2) != 0) { + ga_concat(&ga, "\" vs \""); + ga_concat(&ga, line2); + } + ga_concat(&ga, "\""); + } + assert_error(&ga); + ga_clear(&ga); + return 1; + } + return 0; +} + +/// "assert_equalfile(fname-one, fname-two[, msg])" function +void f_assert_equalfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_equalfile(argvars); +} + +/// "assert_notequal(expected, actual[, msg])" function +void f_assert_notequal(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_equal_common(argvars, ASSERT_NOTEQUAL); +} + +/// "assert_exception(string[, msg])" function +void f_assert_exception(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + garray_T ga; + + const char *const error = tv_get_string_chk(&argvars[0]); + if (*get_vim_var_str(VV_EXCEPTION) == NUL) { + prepare_assert_error(&ga); + ga_concat(&ga, "v:exception is not set"); + assert_error(&ga); + ga_clear(&ga); + rettv->vval.v_number = 1; + } else if (error != NULL + && strstr((char *)get_vim_var_str(VV_EXCEPTION), error) == NULL) { + prepare_assert_error(&ga); + fill_assert_error(&ga, &argvars[1], NULL, &argvars[0], + get_vim_var_tv(VV_EXCEPTION), ASSERT_OTHER); + assert_error(&ga); + ga_clear(&ga); + rettv->vval.v_number = 1; + } +} + +/// "assert_fails(cmd [, error [, msg]])" function +void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *const cmd = tv_get_string_chk(&argvars[0]); + garray_T ga; + int save_trylevel = trylevel; + + // trylevel must be zero for a ":throw" command to be considered failed + trylevel = 0; + called_emsg = false; + suppress_errthrow = true; + emsg_silent = true; + + do_cmdline_cmd(cmd); + if (!called_emsg) { + prepare_assert_error(&ga); + ga_concat(&ga, "command did not fail: "); + assert_append_cmd_or_arg(&ga, argvars, cmd); + assert_error(&ga); + ga_clear(&ga); + rettv->vval.v_number = 1; + } else if (argvars[1].v_type != VAR_UNKNOWN) { + char buf[NUMBUFLEN]; + const char *const error = tv_get_string_buf_chk(&argvars[1], buf); + + if (error == NULL + || strstr((char *)get_vim_var_str(VV_ERRMSG), error) == NULL) { + prepare_assert_error(&ga); + fill_assert_error(&ga, &argvars[2], NULL, &argvars[1], + get_vim_var_tv(VV_ERRMSG), ASSERT_OTHER); + ga_concat(&ga, ": "); + assert_append_cmd_or_arg(&ga, argvars, cmd); + assert_error(&ga); + ga_clear(&ga); + rettv->vval.v_number = 1; + } + } + + trylevel = save_trylevel; + called_emsg = false; + suppress_errthrow = false; + emsg_silent = false; + emsg_on_display = false; + set_vim_var_string(VV_ERRMSG, NULL, 0); +} + +// "assert_false(actual[, msg])" function +void f_assert_false(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_bool(argvars, false); +} + +static int assert_inrange(typval_T *argvars) + FUNC_ATTR_NONNULL_ALL +{ + bool error = false; + + if (argvars[0].v_type == VAR_FLOAT + || argvars[1].v_type == VAR_FLOAT + || argvars[2].v_type == VAR_FLOAT) { + const float_T flower = tv_get_float(&argvars[0]); + const float_T fupper = tv_get_float(&argvars[1]); + const float_T factual = tv_get_float(&argvars[2]); + + if (factual < flower || factual > fupper) { + garray_T ga; + prepare_assert_error(&ga); + if (argvars[3].v_type != VAR_UNKNOWN) { + char_u *const tofree = (char_u *)encode_tv2string(&argvars[3], NULL); + ga_concat(&ga, (char *)tofree); + xfree(tofree); + } else { + char msg[80]; + vim_snprintf(msg, sizeof(msg), "Expected range %g - %g, but got %g", + flower, fupper, factual); + ga_concat(&ga, msg); + } + assert_error(&ga); + ga_clear(&ga); + return 1; + } + } else { + const varnumber_T lower = tv_get_number_chk(&argvars[0], &error); + const varnumber_T upper = tv_get_number_chk(&argvars[1], &error); + const varnumber_T actual = tv_get_number_chk(&argvars[2], &error); + + if (error) { + return 0; + } + if (actual < lower || actual > upper) { + garray_T ga; + prepare_assert_error(&ga); + + char msg[55]; + vim_snprintf(msg, sizeof(msg), + "range %" PRIdVARNUMBER " - %" PRIdVARNUMBER ",", + lower, upper); // -V576 + fill_assert_error(&ga, &argvars[3], (char_u *)msg, NULL, &argvars[2], + ASSERT_INRANGE); + assert_error(&ga); + ga_clear(&ga); + return 1; + } + } + return 0; +} + +/// "assert_inrange(lower, upper[, msg])" function +void f_assert_inrange(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_inrange(argvars); +} + +/// "assert_match(pattern, actual[, msg])" function +void f_assert_match(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_match_common(argvars, ASSERT_MATCH); +} + +/// "assert_notmatch(pattern, actual[, msg])" function +void f_assert_notmatch(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_match_common(argvars, ASSERT_NOTMATCH); +} + +/// "assert_report(msg)" function +void f_assert_report(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + garray_T ga; + + prepare_assert_error(&ga); + ga_concat(&ga, tv_get_string(&argvars[0])); + assert_error(&ga); + ga_clear(&ga); + rettv->vval.v_number = 1; +} + +/// "assert_true(actual[, msg])" function +void f_assert_true(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_bool(argvars, true); +} + +/// "test_garbagecollect_now()" function +void f_test_garbagecollect_now(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + // This is dangerous, any Lists and Dicts used internally may be freed + // while still in use. + garbage_collect(true); +} + +/// "test_write_list_log()" function +void f_test_write_list_log(typval_T *const argvars, typval_T *const rettv, FunPtr fptr) +{ + const char *const fname = tv_get_string_chk(&argvars[0]); + if (fname == NULL) { + return; + } + list_write_log(fname); +} + diff --git a/src/nvim/testing.h b/src/nvim/testing.h new file mode 100644 index 0000000000..34ee1d10df --- /dev/null +++ b/src/nvim/testing.h @@ -0,0 +1,10 @@ +#ifndef NVIM_TESTING_H +#define NVIM_TESTING_H + +#include "nvim/eval/typval.h" +#include "nvim/eval/funcs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "testing.h.generated.h" +#endif +#endif // NVIM_TESTING_H diff --git a/src/nvim/vim.h b/src/nvim/vim.h index 5d632b1b25..f65dd4f0d4 100644 --- a/src/nvim/vim.h +++ b/src/nvim/vim.h @@ -264,7 +264,7 @@ enum { FOLD_TEXT_LEN = 51, }; //!< buffer size for get_foldtext() // Prefer using semsg(), because perror() may send the output to the wrong // destination and mess up the screen. -#define PERROR(msg) (void)semsg("%s: %s", msg, strerror(errno)) +#define PERROR(msg) (void)semsg("%s: %s", (msg), strerror(errno)) #define SHOWCMD_COLS 10 // columns needed by shown command @@ -319,7 +319,7 @@ enum { FOLD_TEXT_LEN = 51, }; //!< buffer size for get_foldtext() #endif // Replacement for nchar used by nv_replace(). -#define REPLACE_CR_NCHAR -1 -#define REPLACE_NL_NCHAR -2 +#define REPLACE_CR_NCHAR (-1) +#define REPLACE_NL_NCHAR (-2) #endif // NVIM_VIM_H diff --git a/src/nvim/window.c b/src/nvim/window.c index 632e9b3f44..9aa2f947cb 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -59,7 +59,7 @@ #endif -#define NOWIN (win_T *)-1 // non-existing window +#define NOWIN ((win_T *)-1) // non-existing window #define ROWS_AVAIL (Rows - p_ch - tabline_height() - global_stl_height()) @@ -2541,6 +2541,41 @@ static bool close_last_window_tabpage(win_T *win, bool free_buf, tabpage_T *prev return true; } +/// Close the buffer of "win" and unload it if "free_buf" is true. +/// "abort_if_last" is passed to close_buffer(): abort closing if all other +/// windows are closed. +static void win_close_buffer(win_T *win, bool free_buf, bool abort_if_last) +{ + // Free independent synblock before the buffer is freed. + if (win->w_buffer != NULL) { + reset_synblock(win); + } + + // When a quickfix/location list window is closed and the buffer is + // displayed in only one window, then unlist the buffer. + if (win->w_buffer != NULL && bt_quickfix(win->w_buffer) + && win->w_buffer->b_nwindows == 1) { + win->w_buffer->b_p_bl = false; + } + + // Close the link to the buffer. + if (win->w_buffer != NULL) { + bufref_T bufref; + set_bufref(&bufref, curbuf); + win->w_closing = true; + close_buffer(win, win->w_buffer, free_buf ? DOBUF_UNLOAD : 0, abort_if_last, true); + if (win_valid_any_tab(win)) { + win->w_closing = false; + } + + // Make sure curbuf is valid. It can become invalid if 'bufhidden' is + // "wipe". + if (!bufref_valid(&bufref)) { + curbuf = firstbuf; + } + } +} + // Close window "win". Only works for the current tab page. // If "free_buf" is true related buffer may be unloaded. // @@ -2679,36 +2714,7 @@ int win_close(win_T *win, bool free_buf, bool force) return OK; } - // Free independent synblock before the buffer is freed. - if (win->w_buffer != NULL) { - reset_synblock(win); - } - - // When a quickfix/location list window is closed and the buffer is - // displayed in only one window, then unlist the buffer. - if (win->w_buffer != NULL && bt_quickfix(win->w_buffer) - && win->w_buffer->b_nwindows == 1) { - win->w_buffer->b_p_bl = false; - } - - /* - * Close the link to the buffer. - */ - if (win->w_buffer != NULL) { - bufref_T bufref; - set_bufref(&bufref, curbuf); - win->w_closing = true; - close_buffer(win, win->w_buffer, free_buf ? DOBUF_UNLOAD : 0, true); - if (win_valid_any_tab(win)) { - win->w_closing = false; - } - - // Make sure curbuf is valid. It can become invalid if 'bufhidden' is - // "wipe". - if (!bufref_valid(&bufref)) { - curbuf = firstbuf; - } - } + win_close_buffer(win, free_buf, true); if (only_one_window() && win_valid(win) && win->w_buffer == NULL && (last_window(win) || curtab != prev_curtab @@ -2879,7 +2885,7 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) if (win->w_buffer != NULL) { // Close the link to the buffer. - close_buffer(win, win->w_buffer, free_buf ? DOBUF_UNLOAD : 0, false); + close_buffer(win, win->w_buffer, free_buf ? DOBUF_UNLOAD : 0, false, true); } // Careful: Autocommands may have closed the tab page or made it the @@ -5412,11 +5418,17 @@ void win_setheight_win(int height, win_T *win) // line, clear it. if (full_screen && msg_scrolled == 0 && row < cmdline_row) { grid_fill(&default_grid, row, cmdline_row, 0, Columns, ' ', ' ', 0); + if (msg_grid.chars) { + clear_cmdline = true; + } } cmdline_row = row; + p_ch = MAX(Rows - cmdline_row, 1); + curtab->tp_ch_used = p_ch; msg_row = row; msg_col = 0; redraw_all_later(NOT_VALID); + showmode(); } } @@ -5452,7 +5464,9 @@ static void frame_setheight(frame_T *curfrp, int height) if (curfrp->fr_parent == NULL) { // topframe: can only change the command line if (height > ROWS_AVAIL) { - height = ROWS_AVAIL; + // If height is greater than the available space, try to create space for the frame by + // reducing 'cmdheight' if possible, while making sure `cmdheight` doesn't go below 1. + height = MIN(ROWS_AVAIL + (p_ch - 1), height); } if (height > 0) { frame_new_height(curfrp, height, false, false); |