aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/nvim/api/autocmd.c16
-rw-r--r--src/nvim/autocmd.c160
-rw-r--r--src/nvim/buffer.c39
-rw-r--r--src/nvim/buffer.h9
-rw-r--r--src/nvim/buffer_defs.h4
-rw-r--r--src/nvim/change.c6
-rw-r--r--src/nvim/charset.c4
-rw-r--r--src/nvim/diff.c14
-rw-r--r--src/nvim/edit.c42
-rw-r--r--src/nvim/eval.c470
-rw-r--r--src/nvim/eval/funcs.c167
-rw-r--r--src/nvim/ex_cmds.c68
-rw-r--r--src/nvim/ex_cmds.h2
-rw-r--r--src/nvim/ex_cmds2.c6
-rw-r--r--src/nvim/ex_cmds_defs.h4
-rw-r--r--src/nvim/ex_docmd.c4
-rw-r--r--src/nvim/ex_docmd.h1
-rw-r--r--src/nvim/ex_getln.c2
-rw-r--r--src/nvim/fileio.c117
-rw-r--r--src/nvim/fileio.h4
-rw-r--r--src/nvim/fold.c2
-rw-r--r--src/nvim/getchar.c39
-rw-r--r--src/nvim/getchar.h4
-rw-r--r--src/nvim/globals.h10
-rw-r--r--src/nvim/macros.h4
-rw-r--r--src/nvim/memline.c161
-rw-r--r--src/nvim/memory.c2
-rw-r--r--src/nvim/mouse.h4
-rw-r--r--src/nvim/normal.c11
-rw-r--r--src/nvim/option.c66
-rw-r--r--src/nvim/option_defs.h4
-rw-r--r--src/nvim/os/win_defs.h2
-rw-r--r--src/nvim/pos.h30
-rw-r--r--src/nvim/quickfix.c24
-rw-r--r--src/nvim/regexp.c33
-rw-r--r--src/nvim/regexp_defs.h2
-rw-r--r--src/nvim/screen.c14
-rw-r--r--src/nvim/screen.h4
-rw-r--r--src/nvim/search.c10
-rw-r--r--src/nvim/sha256.c15
-rw-r--r--src/nvim/shada.c2
-rw-r--r--src/nvim/spell.c23
-rw-r--r--src/nvim/spell_defs.h6
-rw-r--r--src/nvim/spellfile.c2
-rw-r--r--src/nvim/state.c5
-rw-r--r--src/nvim/syntax.c85
-rw-r--r--src/nvim/syntax_defs.h2
-rw-r--r--src/nvim/testdir/test_autocmd.vim34
-rw-r--r--src/nvim/testdir/test_cdo.vim45
-rw-r--r--src/nvim/testdir/test_diffmode.vim37
-rw-r--r--src/nvim/testdir/test_edit.vim23
-rw-r--r--src/nvim/testdir/test_filetype.vim51
-rw-r--r--src/nvim/testdir/test_functions.vim28
-rw-r--r--src/nvim/testdir/test_highlight.vim8
-rw-r--r--src/nvim/testdir/test_ins_complete.vim19
-rw-r--r--src/nvim/testdir/test_mapping.vim34
-rw-r--r--src/nvim/testdir/test_normal.vim10
-rw-r--r--src/nvim/testdir/test_regexp_latin.vim61
-rw-r--r--src/nvim/testdir/test_registers.vim18
-rw-r--r--src/nvim/testdir/test_spell.vim12
-rw-r--r--src/nvim/testdir/test_substitute.vim18
-rw-r--r--src/nvim/testdir/test_trycatch.vim27
-rw-r--r--src/nvim/testing.c562
-rw-r--r--src/nvim/testing.h10
-rw-r--r--src/nvim/vim.h6
-rw-r--r--src/nvim/window.c80
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(&current_state)) {
+ break;
+ }
+ if (had_extend && keepend_level >= 0) {
+ syn_update_ends(false);
if (GA_EMPTY(&current_state)) {
break;
}
+ }
- if (had_extend && keepend_level >= 0) {
- syn_update_ends(false);
- if (GA_EMPTY(&current_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);