aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/nvim/autocmd.c31
-rw-r--r--src/nvim/autocmd.h14
-rw-r--r--src/nvim/buffer.c90
-rw-r--r--src/nvim/buffer_defs.h8
-rw-r--r--src/nvim/edit.c4
-rw-r--r--src/nvim/eval/funcs.c2
-rw-r--r--src/nvim/eval/userfunc.c1
-rw-r--r--src/nvim/ex_cmds.c49
-rw-r--r--src/nvim/ex_eval.c35
-rw-r--r--src/nvim/ex_getln.c12
-rw-r--r--src/nvim/fileio.c4
-rw-r--r--src/nvim/fold.c3
-rw-r--r--src/nvim/main.c23
-rw-r--r--src/nvim/msgpack_rpc/channel.c6
-rw-r--r--src/nvim/normal.c89
-rw-r--r--src/nvim/ops.c9
-rw-r--r--src/nvim/option.c2
-rw-r--r--src/nvim/regexp.c26
-rw-r--r--src/nvim/regexp_nfa.c35
-rw-r--r--src/nvim/sign.c70
-rw-r--r--src/nvim/sign_defs.h16
-rw-r--r--src/nvim/testdir/check.vim8
-rw-r--r--src/nvim/testdir/test_digraph.vim22
-rw-r--r--src/nvim/testdir/test_fileformat.vim23
-rw-r--r--src/nvim/testdir/test_filetype.vim1
-rw-r--r--src/nvim/testdir/test_fold.vim7
-rw-r--r--src/nvim/testdir/test_global.vim11
-rw-r--r--src/nvim/testdir/test_help.vim46
-rw-r--r--src/nvim/testdir/test_normal.vim15
-rw-r--r--src/nvim/testdir/test_regexp_utf8.vim11
-rw-r--r--src/nvim/testdir/test_registers.vim11
-rw-r--r--src/nvim/testdir/test_signals.vim140
-rw-r--r--src/nvim/testdir/test_signs.vim8
-rw-r--r--src/nvim/testdir/test_startup.vim50
-rw-r--r--src/nvim/testdir/test_tabpage.vim107
-rw-r--r--src/nvim/testdir/test_textformat.vim222
-rw-r--r--src/nvim/vim.h1
-rw-r--r--src/nvim/window.c20
38 files changed, 1018 insertions, 214 deletions
diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c
index 53b11c250e..42224d0a4f 100644
--- a/src/nvim/autocmd.c
+++ b/src/nvim/autocmd.c
@@ -1106,9 +1106,9 @@ void aucmd_prepbuf(aco_save_T *aco, buf_T *buf)
win = curwin;
}
- aco->save_curwin = curwin;
- aco->save_prevwin = prevwin;
+ aco->save_curwin_handle = curwin->handle;
aco->save_curbuf = curbuf;
+ aco->save_prevwin_handle = prevwin == NULL ? 0 : prevwin->handle;
if (win != NULL) {
// There is a window for "buf" in the current tab page, make it the
// curwin. This is preferred, it has the least side effects (esp. if
@@ -1148,7 +1148,7 @@ void aucmd_prepbuf(aco_save_T *aco, buf_T *buf)
curwin = aucmd_win;
}
curbuf = buf;
- aco->new_curwin = curwin;
+ aco->new_curwin_handle = curwin->handle;
set_bufref(&aco->new_curbuf, curbuf);
}
@@ -1194,14 +1194,14 @@ void aucmd_restbuf(aco_save_T *aco)
unblock_autocmds();
- if (win_valid(aco->save_curwin)) {
- curwin = aco->save_curwin;
+ win_T *const save_curwin = win_find_by_handle(aco->save_curwin_handle);
+ if (save_curwin != NULL) {
+ curwin = save_curwin;
} else {
// Hmm, original window disappeared. Just use the first one.
curwin = firstwin;
}
- prevwin = win_valid(aco->save_prevwin) ? aco->save_prevwin
- : firstwin; // window disappeared?
+ prevwin = win_find_by_handle(aco->save_prevwin_handle);
vars_clear(&aucmd_win->w_vars->dv_hashtab); // free all w: variables
hash_init(&aucmd_win->w_vars->dv_hashtab); // re-use the hashtab
curbuf = curwin->w_buffer;
@@ -1216,11 +1216,14 @@ void aucmd_restbuf(aco_save_T *aco)
curwin->w_topfill = 0;
}
} else {
- // restore curwin
- if (win_valid(aco->save_curwin)) {
+ // Restore curwin. Use the window ID, a window may have been closed
+ // and the memory re-used for another one.
+ win_T *const save_curwin = win_find_by_handle(aco->save_curwin_handle);
+ if (save_curwin != NULL) {
// Restore the buffer which was previously edited by curwin, if it was
// changed, we are still the same window and the buffer is valid.
- if (curwin == aco->new_curwin && curbuf != aco->new_curbuf.br_buf
+ if (curwin->handle == aco->new_curwin_handle
+ && curbuf != aco->new_curbuf.br_buf
&& bufref_valid(&aco->new_curbuf)
&& aco->new_curbuf.br_buf->b_ml.ml_mfp != NULL) {
if (curwin->w_s == &curbuf->b_s) {
@@ -1232,10 +1235,9 @@ void aucmd_restbuf(aco_save_T *aco)
curbuf->b_nwindows++;
}
- curwin = aco->save_curwin;
- prevwin = win_valid(aco->save_prevwin) ? aco->save_prevwin
- : firstwin; // window disappeared?
+ curwin = save_curwin;
curbuf = curwin->w_buffer;
+ prevwin = win_find_by_handle(aco->save_prevwin_handle);
// In case the autocommand moves the cursor to a position that does not
// exist in curbuf
check_cursor();
@@ -1717,7 +1719,8 @@ void unblock_autocmds(void)
}
}
-static inline bool is_autocmd_blocked(void)
+bool is_autocmd_blocked(void)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
return autocmd_blocked != 0;
}
diff --git a/src/nvim/autocmd.h b/src/nvim/autocmd.h
index af1eeb0fc4..1c0f88f08f 100644
--- a/src/nvim/autocmd.h
+++ b/src/nvim/autocmd.h
@@ -7,13 +7,13 @@
// Struct to save values in before executing autocommands for a buffer that is
// not the current buffer.
typedef struct {
- buf_T *save_curbuf; ///< saved curbuf
- int use_aucmd_win; ///< using aucmd_win
- win_T *save_curwin; ///< saved curwin
- win_T *save_prevwin; ///< saved prevwin
- win_T *new_curwin; ///< new curwin
- bufref_T new_curbuf; ///< new curbuf
- char_u *globaldir; ///< saved value of globaldir
+ buf_T *save_curbuf; ///< saved curbuf
+ bool use_aucmd_win; ///< using aucmd_win
+ handle_T save_curwin_handle; ///< ID of saved curwin
+ handle_T new_curwin_handle; ///< ID of new curwin
+ handle_T save_prevwin_handle; ///< ID of saved prevwin
+ bufref_T new_curbuf; ///< new curbuf
+ char_u *globaldir; ///< saved value of globaldir
} aco_save_T;
typedef struct AutoCmd {
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index 839d61cd2e..a8cb90ff0e 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -602,8 +602,12 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last)
* Remove the buffer from the list.
*/
if (wipe_buf) {
- xfree(buf->b_ffname);
- xfree(buf->b_sfname);
+ if (buf->b_sfname != buf->b_ffname) {
+ XFREE_CLEAR(buf->b_sfname);
+ } else {
+ buf->b_sfname = NULL;
+ }
+ XFREE_CLEAR(buf->b_ffname);
if (buf->b_prev == NULL) {
firstbuf = buf->b_next;
} else {
@@ -1693,15 +1697,18 @@ static inline void buf_init_changedtick(buf_T *const buf)
/// if the buffer already exists.
/// This is the ONLY way to create a new buffer.
///
-/// @param ffname full path of fname or relative
-/// @param sfname short fname or NULL
+/// @param ffname_arg full path of fname or relative
+/// @param sfname_arg short fname or NULL
/// @param lnum preferred cursor line
/// @param flags BLN_ defines
/// @param bufnr
///
/// @return pointer to the buffer
-buf_T * buflist_new(char_u *ffname, char_u *sfname, linenr_T lnum, int flags)
+buf_T *buflist_new(char_u *ffname_arg, char_u *sfname_arg, linenr_T lnum,
+ int flags)
{
+ char_u *ffname = ffname_arg;
+ char_u *sfname = sfname_arg;
buf_T *buf;
fname_expand(curbuf, &ffname, &sfname); // will allocate ffname
@@ -1787,8 +1794,12 @@ buf_T * buflist_new(char_u *ffname, char_u *sfname, linenr_T lnum, int flags)
buf->b_wininfo = xcalloc(1, sizeof(wininfo_T));
if (ffname != NULL && (buf->b_ffname == NULL || buf->b_sfname == NULL)) {
+ if (buf->b_sfname != buf->b_ffname) {
+ XFREE_CLEAR(buf->b_sfname);
+ } else {
+ buf->b_sfname = NULL;
+ }
XFREE_CLEAR(buf->b_ffname);
- XFREE_CLEAR(buf->b_sfname);
if (buf != curbuf) {
free_buffer(buf);
}
@@ -2778,28 +2789,32 @@ int buflist_name_nr(int fnum, char_u **fname, linenr_T *lnum)
return OK;
}
-/*
- * Set the file name for "buf"' to 'ffname', short file name to 'sfname'.
- * The file name with the full path is also remembered, for when :cd is used.
- * Returns FAIL for failure (file name already in use by other buffer)
- * OK otherwise.
- */
-int
-setfname(
+// Set the file name for "buf" to "ffname_arg", short file name to
+// "sfname_arg".
+// The file name with the full path is also remembered, for when :cd is used.
+// Returns FAIL for failure (file name already in use by other buffer)
+// OK otherwise.
+int setfname(
buf_T *buf,
- char_u *ffname,
- char_u *sfname,
+ char_u *ffname_arg,
+ char_u *sfname_arg,
bool message // give message when buffer already exists
)
{
+ char_u *ffname = ffname_arg;
+ char_u *sfname = sfname_arg;
buf_T *obuf = NULL;
FileID file_id;
bool file_id_valid = false;
if (ffname == NULL || *ffname == NUL) {
// Removing the name.
+ if (buf->b_sfname != buf->b_ffname) {
+ XFREE_CLEAR(buf->b_sfname);
+ } else {
+ buf->b_sfname = NULL;
+ }
XFREE_CLEAR(buf->b_ffname);
- XFREE_CLEAR(buf->b_sfname);
} else {
fname_expand(buf, &ffname, &sfname); // will allocate ffname
if (ffname == NULL) { // out of memory
@@ -2830,8 +2845,10 @@ setfname(
#ifdef USE_FNAME_CASE
path_fix_case(sfname); // set correct case for short file name
#endif
+ if (buf->b_sfname != buf->b_ffname) {
+ xfree(buf->b_sfname);
+ }
xfree(buf->b_ffname);
- xfree(buf->b_sfname);
buf->b_ffname = ffname;
buf->b_sfname = sfname;
}
@@ -2857,7 +2874,9 @@ void buf_set_name(int fnum, char_u *name)
buf = buflist_findnr(fnum);
if (buf != NULL) {
- xfree(buf->b_sfname);
+ if (buf->b_sfname != buf->b_ffname) {
+ xfree(buf->b_sfname);
+ }
xfree(buf->b_ffname);
buf->b_ffname = vim_strsave(name);
buf->b_sfname = NULL;
@@ -4633,16 +4652,18 @@ static bool append_arg_number(win_T *wp, char_u *buf, int buflen, bool add_file)
return true;
}
-/*
- * Make "ffname" a full file name, set "sfname" to "ffname" if not NULL.
- * "ffname" becomes a pointer to allocated memory (or NULL).
- */
+// Make "*ffname" a full file name, set "*sfname" to "*ffname" if not NULL.
+// "*ffname" becomes a pointer to allocated memory (or NULL).
+// When resolving a link both "*sfname" and "*ffname" will point to the same
+// allocated memory.
+// The "*ffname" and "*sfname" pointer values on call will not be freed.
+// Note that the resulting "*ffname" pointer should be considered not allocaed.
void fname_expand(buf_T *buf, char_u **ffname, char_u **sfname)
{
- if (*ffname == NULL) { // if no file name given, nothing to do
+ if (*ffname == NULL) { // no file name given, nothing to do
return;
}
- if (*sfname == NULL) { // if no short file name given, use ffname
+ if (*sfname == NULL) { // no short file name given, use ffname
*sfname = *ffname;
}
*ffname = (char_u *)fix_fname((char *)(*ffname)); // expand to full path
@@ -4685,7 +4706,6 @@ do_arg_all(
int keep_tabs // keep current tabs, for ":tab drop file"
)
{
- int i;
char_u *opened; // Array of weight for which args are open:
// 0: not opened
// 1: opened in other tab
@@ -4694,6 +4714,7 @@ do_arg_all(
int opened_len; // length of opened[]
int use_firstwin = false; // use first window for arglist
+ bool tab_drop_empty_window = false;
int split_ret = OK;
bool p_ea_save;
alist_T *alist; // argument list to be used
@@ -4741,6 +4762,7 @@ do_arg_all(
win_T *wpnext = NULL;
tpnext = curtab->tp_next;
for (win_T *wp = firstwin; wp != NULL; wp = wpnext) {
+ int i;
wpnext = wp->w_next;
buf = wp->w_buffer;
if (buf->b_ffname == NULL
@@ -4846,14 +4868,15 @@ do_arg_all(
last_curwin = curwin;
last_curtab = curtab;
win_enter(lastwin, false);
- // ":drop all" should re-use an empty window to avoid "--remote-tab"
+ // ":tab drop file" should re-use an empty window to avoid "--remote-tab"
// leaving an empty tab page when executed locally.
if (keep_tabs && BUFEMPTY() && curbuf->b_nwindows == 1
&& curbuf->b_ffname == NULL && !curbuf->b_changed) {
use_firstwin = true;
+ tab_drop_empty_window = true;
}
- for (i = 0; i < count && i < opened_len && !got_int; i++) {
+ for (int i = 0; i < count && !got_int; i++) {
if (alist == &global_alist && i == global_alist.al_ga.ga_len - 1) {
arg_had_last = true;
}
@@ -4873,6 +4896,10 @@ do_arg_all(
}
}
} else if (split_ret == OK) {
+ // trigger events for tab drop
+ if (tab_drop_empty_window && i == count - 1) {
+ autocmd_no_enter--;
+ }
if (!use_firstwin) { // split current window
p_ea_save = p_ea;
p_ea = true; // use space from all windows
@@ -4898,6 +4925,9 @@ do_arg_all(
|| bufIsChanged(curwin->w_buffer))
? ECMD_HIDE : 0) + ECMD_OLDBUF,
curwin);
+ if (tab_drop_empty_window && i == count - 1) {
+ autocmd_no_enter++;
+ }
if (use_firstwin) {
autocmd_no_leave++;
}
@@ -5449,7 +5479,9 @@ int buf_signcols(buf_T *buf)
curline = sign->lnum;
linesum = 0;
}
- linesum++;
+ if (sign->has_text_or_icon) {
+ linesum++;
+ }
}
if (linesum > buf->b_signcols_max) {
buf->b_signcols_max = linesum;
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index 44a9b39939..cc09b7e989 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -533,9 +533,11 @@ struct file_buffer {
// b_fname is the same as b_sfname, unless ":cd" has been done,
// then it is the same as b_ffname (NULL for no name).
//
- char_u *b_ffname; // full path file name
- char_u *b_sfname; // short file name
- char_u *b_fname; // current file name
+ char_u *b_ffname; // full path file name, allocated
+ char_u *b_sfname; // short file name, allocated, may be equal to
+ // b_ffname
+ char_u *b_fname; // current file name, points to b_ffname or
+ // b_sfname
bool file_id_valid;
FileID file_id;
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index a07e694817..ec424c12da 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -3716,7 +3716,7 @@ static bool ins_compl_prep(int c)
retval = true;
}
- auto_format(FALSE, TRUE);
+ auto_format(false, true);
// Trigger the CompleteDonePre event to give scripts a chance to
// act upon the completion before clearing the info, and restore
@@ -6504,7 +6504,7 @@ stop_insert (
curwin->w_cursor = tpos;
}
- auto_format(TRUE, FALSE);
+ auto_format(true, false);
if (ascii_iswhite(cc)) {
if (gchar_cursor() != NUL)
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 89d9a85775..ed01e8a8b0 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -4206,6 +4206,8 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr)
n = true;
} else if (STRICMP(name, "syntax_items") == 0) {
n = syntax_present(curwin);
+ } else if (STRICMP(name, "clipboard_working") == 0) {
+ n = eval_has_provider("clipboard");
#ifdef UNIX
} else if (STRICMP(name, "unnamedplus") == 0) {
n = eval_has_provider("clipboard");
diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c
index 8daef00985..4e8b98d723 100644
--- a/src/nvim/eval/userfunc.c
+++ b/src/nvim/eval/userfunc.c
@@ -2484,6 +2484,7 @@ errret_2:
ga_clear_strings(&newlines);
ret_free:
xfree(skip_until);
+ xfree(heredoc_trimmed);
xfree(line_to_free);
xfree(fudi.fd_newkey);
xfree(name);
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 05ff52fe9b..2a96db6a8c 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -2066,19 +2066,20 @@ static int check_readonly(int *forceit, buf_T *buf)
return FALSE;
}
-/*
- * Try to abandon current file and edit a new or existing file.
- * "fnum" is the number of the file, if zero use ffname/sfname.
- * "lnum" is the line number for the cursor in the new file (if non-zero).
- *
- * Return:
- * GETFILE_ERROR for "normal" error,
- * GETFILE_NOT_WRITTEN for "not written" error,
- * GETFILE_SAME_FILE for success
- * GETFILE_OPEN_OTHER for successfully opening another file.
- */
-int getfile(int fnum, char_u *ffname, char_u *sfname, int setpm, linenr_T lnum, int forceit)
+// Try to abandon the current file and edit a new or existing file.
+// "fnum" is the number of the file, if zero use "ffname_arg"/"sfname_arg".
+// "lnum" is the line number for the cursor in the new file (if non-zero).
+//
+// Return:
+// GETFILE_ERROR for "normal" error,
+// GETFILE_NOT_WRITTEN for "not written" error,
+// GETFILE_SAME_FILE for success
+// GETFILE_OPEN_OTHER for successfully opening another file.
+int getfile(int fnum, char_u *ffname_arg, char_u *sfname_arg, int setpm,
+ linenr_T lnum, int forceit)
{
+ char_u *ffname = ffname_arg;
+ char_u *sfname = sfname_arg;
int other;
int retval;
char_u *free_me = NULL;
@@ -5248,8 +5249,10 @@ void ex_viusage(exarg_T *eap)
/// @param tagname Name of the tags file ("tags" for English, "tags-fr" for
/// French)
/// @param add_help_tags Whether to add the "help-tags" tag
-static void helptags_one(char_u *const dir, const char_u *const ext,
- const char_u *const tagfname, const bool add_help_tags)
+/// @param ignore_writeerr ignore write error
+static void helptags_one(char_u *dir, const char_u *ext, const char_u *tagfname,
+ bool add_help_tags, bool ignore_writeerr)
+ FUNC_ATTR_NONNULL_ALL
{
garray_T ga;
int filecount;
@@ -5293,7 +5296,9 @@ static void helptags_one(char_u *const dir, const char_u *const ext,
FILE *const fd_tags = os_fopen((char *)NameBuff, "w");
if (fd_tags == NULL) {
- EMSG2(_("E152: Cannot open %s for writing"), NameBuff);
+ if (!ignore_writeerr) {
+ EMSG2(_("E152: Cannot open %s for writing"), NameBuff);
+ }
FreeWild(filecount, files);
return;
}
@@ -5441,7 +5446,9 @@ static void helptags_one(char_u *const dir, const char_u *const ext,
}
/// Generate tags in one help directory, taking care of translations.
-static void do_helptags(char_u *dirname, bool add_help_tags)
+static void do_helptags(char_u *dirname, bool add_help_tags,
+ bool ignore_writeerr)
+ FUNC_ATTR_NONNULL_ALL
{
int len;
garray_T ga;
@@ -5523,17 +5530,17 @@ static void do_helptags(char_u *dirname, bool add_help_tags)
ext[1] = fname[5];
ext[2] = fname[6];
}
- helptags_one(dirname, ext, fname, add_help_tags);
+ helptags_one(dirname, ext, fname, add_help_tags, ignore_writeerr);
}
ga_clear(&ga);
FreeWild(filecount, files);
}
- static void
-helptags_cb(char_u *fname, void *cookie)
+static void helptags_cb(char_u *fname, void *cookie)
+ FUNC_ATTR_NONNULL_ALL
{
- do_helptags(fname, *(bool *)cookie);
+ do_helptags(fname, *(bool *)cookie, true);
}
/*
@@ -5562,7 +5569,7 @@ void ex_helptags(exarg_T *eap)
if (dirname == NULL || !os_isdir(dirname)) {
EMSG2(_("E150: Not a directory: %s"), eap->arg);
} else {
- do_helptags(dirname, add_help_tags);
+ do_helptags(dirname, add_help_tags, false);
}
xfree(dirname);
}
diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c
index 0c7562980a..0917c6dd02 100644
--- a/src/nvim/ex_eval.c
+++ b/src/nvim/ex_eval.c
@@ -518,10 +518,13 @@ fail:
* Discard an exception. "was_finished" is set when the exception has been
* caught and the catch clause has been ended normally.
*/
-static void discard_exception(except_T *excp, int was_finished)
+static void discard_exception(except_T *excp, bool was_finished)
{
char_u *saved_IObuff;
+ if (current_exception == excp) {
+ current_exception = NULL;
+ }
if (excp == NULL) {
internal_error("discard_exception()");
return;
@@ -569,7 +572,6 @@ void discard_current_exception(void)
{
if (current_exception != NULL) {
discard_exception(current_exception, false);
- current_exception = NULL;
}
// Note: all globals manipulated here should be saved/restored in
// try_enter/try_leave.
@@ -652,8 +654,8 @@ static void finish_exception(except_T *excp)
set_vim_var_string(VV_THROWPOINT, NULL, -1);
}
- /* Discard the exception, but use the finish message for 'verbose'. */
- discard_exception(excp, TRUE);
+ // Discard the exception, but use the finish message for 'verbose'.
+ discard_exception(excp, true);
}
/*
@@ -1812,11 +1814,12 @@ void leave_cleanup(cleanup_T *csp)
* made pending by it. Report this to the user if required by the
* 'verbose' option or when debugging. */
if (aborting() || need_rethrow) {
- if (pending & CSTP_THROW)
- /* Cancel the pending exception (includes report). */
- discard_exception(csp->exception, FALSE);
- else
+ if (pending & CSTP_THROW) {
+ // Cancel the pending exception (includes report).
+ discard_exception(csp->exception, false);
+ } else {
report_discard_pending(pending, NULL);
+ }
/* If an error was about to be converted to an exception when
* enter_cleanup() was called, free the message list. */
@@ -1916,15 +1919,13 @@ int cleanup_conditionals(cstack_T *cstack, int searched_cond, int inclusive)
default:
if (cstack->cs_flags[idx] & CSF_FINALLY) {
if (cstack->cs_pending[idx] & CSTP_THROW) {
- /* Cancel the pending exception. This is in the
- * finally clause, so that the stack of the
- * caught exceptions is not involved. */
- discard_exception((except_T *)
- cstack->cs_exception[idx],
- FALSE);
- } else
- report_discard_pending(cstack->cs_pending[idx],
- NULL);
+ // Cancel the pending exception. This is in the
+ // finally clause, so that the stack of the
+ // caught exceptions is not involved.
+ discard_exception((except_T *)cstack->cs_exception[idx], false);
+ } else {
+ report_discard_pending(cstack->cs_pending[idx], NULL);
+ }
cstack->cs_pending[idx] = CSTP_NONE;
}
break;
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 1aa97c2966..24fc60e60a 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -4229,17 +4229,11 @@ ExpandOne (
* Prepare an expand structure for use.
*/
void ExpandInit(expand_T *xp)
+ FUNC_ATTR_NONNULL_ALL
{
- xp->xp_pattern = NULL;
- xp->xp_pattern_len = 0;
+ CLEAR_POINTER(xp);
xp->xp_backslash = XP_BS_NONE;
-#ifndef BACKSLASH_IN_FILENAME
- xp->xp_shell = FALSE;
-#endif
xp->xp_numfiles = -1;
- xp->xp_files = NULL;
- xp->xp_arg = NULL;
- xp->xp_line = NULL;
}
/*
@@ -5399,7 +5393,7 @@ static void expand_shellcmd(char_u *filepat, int *num_file, char_u ***file,
}
/// Call "user_expand_func()" to invoke a user defined Vim script function and
-/// return the result (either a string or a List).
+/// return the result (either a string, a List or NULL).
static void * call_user_expand_func(user_expand_func_T user_expand_func,
expand_T *xp, int *num_file, char_u ***file)
FUNC_ATTR_NONNULL_ALL
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index 1b879add63..936a9d1397 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -4217,7 +4217,9 @@ void shorten_buf_fname(buf_T *buf, char_u *dirname, int force)
&& (force
|| buf->b_sfname == NULL
|| path_is_absolute(buf->b_sfname))) {
- XFREE_CLEAR(buf->b_sfname);
+ if (buf->b_sfname != buf->b_ffname) {
+ XFREE_CLEAR(buf->b_sfname);
+ }
p = path_shorten_fname(buf->b_ffname, dirname);
if (p != NULL) {
buf->b_sfname = vim_strsave(p);
diff --git a/src/nvim/fold.c b/src/nvim/fold.c
index 654aa6d5ba..0593c16999 100644
--- a/src/nvim/fold.c
+++ b/src/nvim/fold.c
@@ -902,8 +902,9 @@ int foldMoveTo(
bool last = false;
for (;; ) {
if (!foldFind(gap, curwin->w_cursor.lnum - lnum_off, &fp)) {
- if (!updown)
+ if (!updown || gap->ga_len == 0) {
break;
+ }
/* When moving up, consider a fold above the cursor; when
* moving down consider a fold below the cursor. */
diff --git a/src/nvim/main.c b/src/nvim/main.c
index eb3360d069..8bf745966e 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -653,7 +653,18 @@ void getout(int exitval)
}
}
}
- apply_autocmds(EVENT_VIMLEAVEPRE, NULL, NULL, FALSE, curbuf);
+
+ int unblock = 0;
+ // deathtrap() blocks autocommands, but we do want to trigger
+ // VimLeavePre.
+ if (is_autocmd_blocked()) {
+ unblock_autocmds();
+ unblock++;
+ }
+ apply_autocmds(EVENT_VIMLEAVEPRE, NULL, NULL, false, curbuf);
+ if (unblock) {
+ block_autocmds();
+ }
}
if (p_shada && *p_shada != NUL) {
@@ -662,7 +673,17 @@ void getout(int exitval)
}
if (v_dying <= 1) {
+ int unblock = 0;
+
+ // deathtrap() blocks autocommands, but we do want to trigger VimLeave.
+ if (is_autocmd_blocked()) {
+ unblock_autocmds();
+ unblock++;
+ }
apply_autocmds(EVENT_VIMLEAVE, NULL, NULL, false, curbuf);
+ if (unblock) {
+ block_autocmds();
+ }
}
profile_dump();
diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c
index 68ef4cd41e..a0b439ac45 100644
--- a/src/nvim/msgpack_rpc/channel.c
+++ b/src/nvim/msgpack_rpc/channel.c
@@ -377,6 +377,10 @@ static void request_event(void **argv)
Channel *channel = e->channel;
MsgpackRpcRequestHandler handler = e->handler;
Error error = ERROR_INIT;
+ if (channel->rpc.closed) {
+ // channel was closed, abort any pending requests
+ goto free_ret;
+ }
Object result = handler.fn(channel->id, e->args, &error);
if (e->type == kMessageTypeRequest || ERROR_SET(&error)) {
// Send the response.
@@ -391,6 +395,8 @@ static void request_event(void **argv)
} else {
api_free_object(result);
}
+
+free_ret:
api_free_array(e->args);
channel_decref(channel);
xfree(e);
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 4fb4045e1b..a2060b91f7 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -3052,57 +3052,57 @@ static bool find_is_eval_item(
return false;
}
-/*
- * Find the identifier under or to the right of the cursor.
- * "find_type" can have one of three values:
- * FIND_IDENT: find an identifier (keyword)
- * FIND_STRING: find any non-white string
- * FIND_IDENT + FIND_STRING: find any non-white string, identifier preferred.
- * FIND_EVAL: find text useful for C program debugging
- *
- * There are three steps:
- * 1. Search forward for the start of an identifier/string. Doesn't move if
- * already on one.
- * 2. Search backward for the start of this identifier/string.
- * This doesn't match the real Vi but I like it a little better and it
- * shouldn't bother anyone.
- * 3. Search forward to the end of this identifier/string.
- * When FIND_IDENT isn't defined, we backup until a blank.
- *
- * Returns the length of the string, or zero if no string is found.
- * If a string is found, a pointer to the string is put in "*string". This
- * string is not always NUL terminated.
- */
-size_t find_ident_under_cursor(char_u **string, int find_type)
+// Find the identifier under or to the right of the cursor.
+// "find_type" can have one of three values:
+// FIND_IDENT: find an identifier (keyword)
+// FIND_STRING: find any non-white text
+// FIND_IDENT + FIND_STRING: find any non-white text, identifier preferred.
+// FIND_EVAL: find text useful for C program debugging
+//
+// There are three steps:
+// 1. Search forward for the start of an identifier/text. Doesn't move if
+// already on one.
+// 2. Search backward for the start of this identifier/text.
+// This doesn't match the real Vi but I like it a little better and it
+// shouldn't bother anyone.
+// 3. Search forward to the end of this identifier/text.
+// When FIND_IDENT isn't defined, we backup until a blank.
+//
+// Returns the length of the text, or zero if no text is found.
+// If text is found, a pointer to the text is put in "*text". This
+// points into the current buffer line and is not always NUL terminated.
+size_t find_ident_under_cursor(char_u **text, int find_type)
+ FUNC_ATTR_NONNULL_ARG(1)
{
return find_ident_at_pos(curwin, curwin->w_cursor.lnum,
- curwin->w_cursor.col, string, find_type);
+ curwin->w_cursor.col, text, NULL, find_type);
}
/*
* Like find_ident_under_cursor(), but for any window and any position.
* However: Uses 'iskeyword' from the current window!.
*/
-size_t find_ident_at_pos(win_T *wp, linenr_T lnum, colnr_T startcol,
- char_u **string, int find_type)
-{
- char_u *ptr;
- int col = 0; /* init to shut up GCC */
+size_t find_ident_at_pos(
+ win_T *wp,
+ linenr_T lnum,
+ colnr_T startcol,
+ char_u **text,
+ int *textcol, // column where "text" starts, can be NULL
+ int find_type)
+ FUNC_ATTR_NONNULL_ARG(1, 4)
+{
+ int col = 0; // init to shut up GCC
int i;
int this_class = 0;
int prev_class;
int prevcol;
int bn = 0; // bracket nesting
- /*
- * if i == 0: try to find an identifier
- * if i == 1: try to find any non-white string
- */
- ptr = ml_get_buf(wp->w_buffer, lnum, false);
- for (i = (find_type & FIND_IDENT) ? 0 : 1; i < 2; ++i) {
- /*
- * 1. skip to start of identifier/string
- */
+ // if i == 0: try to find an identifier
+ // if i == 1: try to find any non-white text
+ char_u *ptr = ml_get_buf(wp->w_buffer, lnum, false);
+ for (i = (find_type & FIND_IDENT) ? 0 : 1; i < 2; i++) {
+ // 1. skip to start of identifier/text
col = startcol;
while (ptr[col] != NUL) {
// Stop at a ']' to evaluate "a[x]".
@@ -3120,7 +3120,7 @@ size_t find_ident_at_pos(win_T *wp, linenr_T lnum, colnr_T startcol,
bn = ptr[col] == ']';
//
- // 2. Back up to start of identifier/string.
+ // 2. Back up to start of identifier/text.
//
// Remember class of character under cursor.
if ((find_type & FIND_EVAL) && ptr[col] == ']') {
@@ -3143,7 +3143,7 @@ size_t find_ident_at_pos(win_T *wp, linenr_T lnum, colnr_T startcol,
col = prevcol;
}
- // If we don't want just any old string, or we've found an
+ // If we don't want just any old text, or we've found an
// identifier, stop searching.
if (this_class > 2) {
this_class = 2;
@@ -3154,7 +3154,7 @@ size_t find_ident_at_pos(win_T *wp, linenr_T lnum, colnr_T startcol,
}
if (ptr[col] == NUL || (i == 0 && this_class != 2)) {
- // Didn't find an identifier or string.
+ // Didn't find an identifier or text.
if (find_type & FIND_STRING) {
EMSG(_("E348: No string under cursor"));
} else {
@@ -3163,11 +3163,12 @@ size_t find_ident_at_pos(win_T *wp, linenr_T lnum, colnr_T startcol,
return 0;
}
ptr += col;
- *string = ptr;
+ *text = ptr;
+ if (textcol != NULL) {
+ *textcol = col;
+ }
- /*
- * 3. Find the end if the identifier/string.
- */
+ // 3. Find the end if the identifier/text.
bn = 0;
startcol -= col;
col = 0;
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index 40dd5f0b5c..8fddb1b561 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -1197,7 +1197,13 @@ int insert_reg(
retval = FAIL;
} else {
for (size_t i = 0; i < reg->y_size; i++) {
- stuffescaped((const char *)reg->y_array[i], literally);
+ if (regname == '-') {
+ AppendCharToRedobuff(Ctrl_R);
+ AppendCharToRedobuff(regname);
+ do_put(regname, NULL, BACKWARD, 1L, PUT_CURSEND);
+ } else {
+ stuffescaped((const char *)reg->y_array[i], literally);
+ }
// Insert a newline between lines and after last line if
// y_type is kMTLineWise.
if (reg->y_type == kMTLineWise || i < reg->y_size - 1) {
@@ -1683,6 +1689,7 @@ int op_delete(oparg_T *oap)
(int)oap->line_count-1, n, deleted_bytes,
0, 0, 0, kExtmarkUndo);
}
+ auto_format(false, true);
}
msgmore(curbuf->b_ml.ml_line_count - old_lcount);
diff --git a/src/nvim/option.c b/src/nvim/option.c
index dc2591510c..f41c0761cc 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -320,7 +320,7 @@ static char *(p_scl_values[]) = { "yes", "no", "auto", "auto:1", "auto:2",
"auto:3", "auto:4", "auto:5", "auto:6", "auto:7", "auto:8", "auto:9",
"yes:1", "yes:2", "yes:3", "yes:4", "yes:5", "yes:6", "yes:7", "yes:8",
"yes:9", "number", NULL };
-static char *(p_fdc_values[]) = { "auto:1", "auto:2",
+static char *(p_fdc_values[]) = { "auto", "auto:1", "auto:2",
"auto:3", "auto:4", "auto:5", "auto:6", "auto:7", "auto:8", "auto:9",
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", NULL };
diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c
index 2878e73573..fb08cd2727 100644
--- a/src/nvim/regexp.c
+++ b/src/nvim/regexp.c
@@ -3605,19 +3605,21 @@ theend:
if (backpos.ga_maxlen > BACKPOS_INITIAL)
ga_clear(&backpos);
- // Make sure the end is never before the start. Can happen when \zs and
- // \ze are used.
- if (REG_MULTI) {
- const lpos_T *const start = &rex.reg_mmatch->startpos[0];
- const lpos_T *const end = &rex.reg_mmatch->endpos[0];
+ if (retval > 0) {
+ // Make sure the end is never before the start. Can happen when \zs
+ // and \ze are used.
+ if (REG_MULTI) {
+ const lpos_T *const start = &rex.reg_mmatch->startpos[0];
+ const lpos_T *const end = &rex.reg_mmatch->endpos[0];
- if (end->lnum < start->lnum
- || (end->lnum == start->lnum && end->col < start->col)) {
- rex.reg_mmatch->endpos[0] = rex.reg_mmatch->startpos[0];
- }
- } else {
- if (rex.reg_match->endp[0] < rex.reg_match->startp[0]) {
- rex.reg_match->endp[0] = rex.reg_match->startp[0];
+ if (end->lnum < start->lnum
+ || (end->lnum == start->lnum && end->col < start->col)) {
+ rex.reg_mmatch->endpos[0] = rex.reg_mmatch->startpos[0];
+ }
+ } else {
+ if (rex.reg_match->endp[0] < rex.reg_match->startp[0]) {
+ rex.reg_match->endp[0] = rex.reg_match->startp[0];
+ }
}
}
diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c
index 137b2304c0..d38719f396 100644
--- a/src/nvim/regexp_nfa.c
+++ b/src/nvim/regexp_nfa.c
@@ -5243,9 +5243,12 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
switch (t->state->c) {
case NFA_MATCH:
{
- // If the match ends before a composing characters and
- // rex.reg_icombine is not set, that is not really a match.
- if (!rex.reg_icombine && utf_iscomposing(curc)) {
+ // If the match is not at the start of the line, ends before a
+ // composing characters and rex.reg_icombine is not set, that
+ // is not really a match.
+ if (!rex.reg_icombine
+ && rex.input != rex.line
+ && utf_iscomposing(curc)) {
break;
}
nfa_match = true;
@@ -6591,19 +6594,21 @@ static long nfa_regexec_both(char_u *line, colnr_T startcol,
#endif
theend:
- // Make sure the end is never before the start. Can happen when \zs and
- // \ze are used.
- if (REG_MULTI) {
- const lpos_T *const start = &rex.reg_mmatch->startpos[0];
- const lpos_T *const end = &rex.reg_mmatch->endpos[0];
+ if (retval > 0) {
+ // Make sure the end is never before the start. Can happen when \zs and
+ // \ze are used.
+ if (REG_MULTI) {
+ const lpos_T *const start = &rex.reg_mmatch->startpos[0];
+ const lpos_T *const end = &rex.reg_mmatch->endpos[0];
- if (end->lnum < start->lnum
- || (end->lnum == start->lnum && end->col < start->col)) {
- rex.reg_mmatch->endpos[0] = rex.reg_mmatch->startpos[0];
- }
- } else {
- if (rex.reg_match->endp[0] < rex.reg_match->startp[0]) {
- rex.reg_match->endp[0] = rex.reg_match->startp[0];
+ if (end->lnum < start->lnum
+ || (end->lnum == start->lnum && end->col < start->col)) {
+ rex.reg_mmatch->endpos[0] = rex.reg_mmatch->startpos[0];
+ }
+ } else {
+ if (rex.reg_match->endp[0] < rex.reg_match->startp[0]) {
+ rex.reg_match->endp[0] = rex.reg_match->startp[0];
+ }
}
}
diff --git a/src/nvim/sign.c b/src/nvim/sign.c
index ffe51287c5..fc9f53c192 100644
--- a/src/nvim/sign.c
+++ b/src/nvim/sign.c
@@ -173,13 +173,15 @@ static void insert_sign(
const char_u *group, // sign group; NULL for global group
int prio, // sign priority
linenr_T lnum, // line number which gets the mark
- int typenr // typenr of sign we are adding
+ int typenr, // typenr of sign we are adding
+ bool has_text_or_icon // sign has text or icon
)
{
signlist_T *newsign = xmalloc(sizeof(signlist_T));
newsign->id = id;
newsign->lnum = lnum;
newsign->typenr = typenr;
+ newsign->has_text_or_icon = has_text_or_icon;
if (group != NULL) {
newsign->group = sign_group_ref(group);
} else {
@@ -210,13 +212,14 @@ static void insert_sign(
/// Insert a new sign sorted by line number and sign priority.
static void insert_sign_by_lnum_prio(
- buf_T *buf, // buffer to store sign in
- signlist_T *prev, // previous sign entry
- int id, // sign ID
- const char_u *group, // sign group; NULL for global group
- int prio, // sign priority
- linenr_T lnum, // line number which gets the mark
- int typenr // typenr of sign we are adding
+ buf_T *buf, // buffer to store sign in
+ signlist_T *prev, // previous sign entry
+ int id, // sign ID
+ const char_u *group, // sign group; NULL for global group
+ int prio, // sign priority
+ linenr_T lnum, // line number which gets the mark
+ int typenr, // typenr of sign we are adding
+ bool has_text_or_icon // sign has text or icon
)
{
signlist_T *sign;
@@ -234,7 +237,7 @@ static void insert_sign_by_lnum_prio(
sign = prev->next;
}
- insert_sign(buf, prev, sign, id, group, prio, lnum, typenr);
+ insert_sign(buf, prev, sign, id, group, prio, lnum, typenr, has_text_or_icon);
}
/// Get the name of a sign by its typenr.
@@ -342,12 +345,13 @@ static void sign_sort_by_prio_on_line(buf_T *buf, signlist_T *sign)
/// Add the sign into the signlist. Find the right spot to do it though.
void buf_addsign(
- buf_T *buf, // buffer to store sign in
- int id, // sign ID
+ buf_T *buf, // buffer to store sign in
+ int id, // sign ID
const char_u *groupname, // sign group
- int prio, // sign priority
- linenr_T lnum, // line number which gets the mark
- int typenr // typenr of sign we are adding
+ int prio, // sign priority
+ linenr_T lnum, // line number which gets the mark
+ int typenr, // typenr of sign we are adding
+ bool has_text_or_icon // sign has text or icon
)
{
signlist_T *sign; // a sign in the signlist
@@ -363,13 +367,29 @@ void buf_addsign(
sign_sort_by_prio_on_line(buf, sign);
return;
} else if (lnum < sign->lnum) {
- insert_sign_by_lnum_prio(buf, prev, id, groupname, prio, lnum, typenr);
+ insert_sign_by_lnum_prio(
+ buf,
+ prev,
+ id,
+ groupname,
+ prio,
+ lnum,
+ typenr,
+ has_text_or_icon);
return;
}
prev = sign;
}
- insert_sign_by_lnum_prio(buf, prev, id, groupname, prio, lnum, typenr);
+ insert_sign_by_lnum_prio(
+ buf,
+ prev,
+ id,
+ groupname,
+ prio,
+ lnum,
+ typenr,
+ has_text_or_icon);
}
// For an existing, placed sign "markId" change the type to "typenr".
@@ -786,11 +806,15 @@ static int sign_define_init_text(sign_T *sp, char_u *text)
}
cells += utf_ptr2cells(s);
}
- // Currently must be one or two display cells
- if (s != endp || cells < 1 || cells > 2) {
+ // Currently must be empty, one or two display cells
+ if (s != endp || cells > 2) {
EMSG2(_("E239: Invalid sign text: %s"), text);
return FAIL;
}
+ if (cells < 1) {
+ sp->sn_text = NULL;
+ return OK;
+ }
xfree(sp->sn_text);
// Allocate one byte more if we need to pad up
@@ -939,7 +963,15 @@ int sign_place(
if (lnum > 0) {
// ":sign place {id} line={lnum} name={name} file={fname}":
// place a sign
- buf_addsign(buf, *sign_id, sign_group, prio, lnum, sp->sn_typenr);
+ bool has_text_or_icon = sp->sn_text != NULL || sp->sn_icon != NULL;
+ buf_addsign(
+ buf,
+ *sign_id,
+ sign_group,
+ prio,
+ lnum,
+ sp->sn_typenr,
+ has_text_or_icon);
} else {
// ":sign place {id} file={fname}": change sign type
lnum = buf_change_sign_type(buf, *sign_id, sign_group, sp->sn_typenr);
diff --git a/src/nvim/sign_defs.h b/src/nvim/sign_defs.h
index 687c15bbd6..c898dba890 100644
--- a/src/nvim/sign_defs.h
+++ b/src/nvim/sign_defs.h
@@ -1,6 +1,7 @@
#ifndef NVIM_SIGN_DEFS_H
#define NVIM_SIGN_DEFS_H
+#include <stdbool.h>
#include "nvim/pos.h"
#include "nvim/types.h"
@@ -22,13 +23,14 @@ typedef struct signlist signlist_T;
struct signlist
{
- int id; // unique identifier for each placed sign
- linenr_T lnum; // line number which has this sign
- int typenr; // typenr of sign
- signgroup_T *group; // sign group
- int priority; // priority for highlighting
- signlist_T *next; // next signlist entry
- signlist_T *prev; // previous entry -- for easy reordering
+ int id; // unique identifier for each placed sign
+ linenr_T lnum; // line number which has this sign
+ int typenr; // typenr of sign
+ bool has_text_or_icon; // has text or icon
+ signgroup_T *group; // sign group
+ int priority; // priority for highlighting
+ signlist_T *next; // next signlist entry
+ signlist_T *prev; // previous entry -- for easy reordering
};
// Default sign priority for highlighting
diff --git a/src/nvim/testdir/check.vim b/src/nvim/testdir/check.vim
index 7f6b7dcfec..467ff5a1e9 100644
--- a/src/nvim/testdir/check.vim
+++ b/src/nvim/testdir/check.vim
@@ -74,6 +74,14 @@ func CheckCanRunGui()
endif
endfunc
+" Command to check that we are using the GUI
+command CheckGui call CheckGui()
+func CheckGui()
+ if !has('gui_running')
+ throw 'Skipped: only works in the GUI'
+ endif
+endfunc
+
" Command to check that not currently using the GUI
command CheckNotGui call CheckNotGui()
func CheckNotGui()
diff --git a/src/nvim/testdir/test_digraph.vim b/src/nvim/testdir/test_digraph.vim
index 9eea27740d..b6d9687560 100644
--- a/src/nvim/testdir/test_digraph.vim
+++ b/src/nvim/testdir/test_digraph.vim
@@ -1,8 +1,8 @@
" Tests for digraphs
-if !has("digraphs")
- finish
-endif
+source check.vim
+CheckFeature digraphs
+source term_util.vim
func Put_Dig(chars)
exe "norm! o\<c-k>".a:chars
@@ -487,4 +487,20 @@ func Test_show_digraph_cp1251()
bwipe!
endfunc
+" Test for the characters displayed on the screen when entering a digraph
+func Test_entering_digraph()
+ CheckRunVimInTerminal
+ let buf = RunVimInTerminal('', {'rows': 6})
+ call term_sendkeys(buf, "i\<C-K>")
+ call term_wait(buf)
+ call assert_equal('?', term_getline(buf, 1))
+ call term_sendkeys(buf, "1")
+ call term_wait(buf)
+ call assert_equal('1', term_getline(buf, 1))
+ call term_sendkeys(buf, "2")
+ call term_wait(buf)
+ call assert_equal('½', term_getline(buf, 1))
+ call StopVimInTerminal(buf)
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_fileformat.vim b/src/nvim/testdir/test_fileformat.vim
index 8dc25f62b1..465613f1cf 100644
--- a/src/nvim/testdir/test_fileformat.vim
+++ b/src/nvim/testdir/test_fileformat.vim
@@ -31,3 +31,26 @@ func Test_fileformat_autocommand()
au! BufReadPre Xfile
bw!
endfunc
+
+" Test for changing the fileformat using ++read
+func Test_fileformat_plusplus_read()
+ new
+ call setline(1, ['one', 'two', 'three'])
+ w ++ff=dos Xfile1
+ enew!
+ set ff=unix
+ " A :read doesn't change the fileformat, but does apply to the read lines.
+ r ++fileformat=unix Xfile1
+ call assert_equal('unix', &fileformat)
+ call assert_equal("three\r", getline('$'))
+ 3r ++edit Xfile1
+ call assert_equal('dos', &fileformat)
+ close!
+ call delete('Xfile1')
+ set fileformat&
+ call assert_fails('e ++fileformat Xfile1', 'E474:')
+ call assert_fails('e ++ff=abc Xfile1', 'E474:')
+ call assert_fails('e ++abc1 Xfile1', 'E474:')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim
index 356a7ce135..2123780f11 100644
--- a/src/nvim/testdir/test_filetype.vim
+++ b/src/nvim/testdir/test_filetype.vim
@@ -181,6 +181,7 @@ let s:filename_checks = {
\ 'gdb': ['.gdbinit'],
\ 'gdmo': ['file.mo', 'file.gdmo'],
\ 'gedcom': ['file.ged', 'lltxxxxx.txt'],
+ \ 'gift': ['file.gift'],
\ 'gitcommit': ['COMMIT_EDITMSG', 'MERGE_MSG', 'TAG_EDITMSG'],
\ 'gitconfig': ['file.git/config', '.gitconfig', '.gitmodules', 'file.git/modules//config', '/.config/git/config', '/etc/gitconfig'],
\ 'gitolite': ['gitolite.conf'],
diff --git a/src/nvim/testdir/test_fold.vim b/src/nvim/testdir/test_fold.vim
index 3c90c45952..88ce64b9eb 100644
--- a/src/nvim/testdir/test_fold.vim
+++ b/src/nvim/testdir/test_fold.vim
@@ -815,4 +815,11 @@ func Test_fold_create_delete_create()
bwipe!
endfunc
+" this was crashing
+func Test_fold_create_delete()
+ new
+ norm zFzFzdzj
+ bwipe!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_global.vim b/src/nvim/testdir/test_global.vim
index bdeaf8e2cf..7ccf2812ff 100644
--- a/src/nvim/testdir/test_global.vim
+++ b/src/nvim/testdir/test_global.vim
@@ -1,3 +1,4 @@
+source check.vim
func Test_yank_put_clipboard()
new
@@ -10,6 +11,16 @@ func Test_yank_put_clipboard()
bwipe!
endfunc
+func Test_global_set_clipboard()
+ CheckFeature clipboard_working
+ new
+ set clipboard=unnamedplus
+ let @+='clipboard' | g/^/set cb= | let @" = 'unnamed' | put
+ call assert_equal(['','unnamed'], getline(1, '$'))
+ set clipboard&
+ bwipe!
+endfunc
+
func Test_nested_global()
new
call setline(1, ['nothing', 'found', 'found bad', 'bad'])
diff --git a/src/nvim/testdir/test_help.vim b/src/nvim/testdir/test_help.vim
index 01fb9917e9..8e59efd22d 100644
--- a/src/nvim/testdir/test_help.vim
+++ b/src/nvim/testdir/test_help.vim
@@ -56,3 +56,49 @@ func Test_help_local_additions()
call delete('Xruntime', 'rf')
let &rtp = rtp_save
endfunc
+
+" Test for the :helptags command
+func Test_helptag_cmd()
+ call mkdir('Xdir/a/doc', 'p')
+
+ " No help file to process in the directory
+ call assert_fails('helptags Xdir', 'E151:')
+
+ call writefile([], 'Xdir/a/doc/sample.txt')
+
+ " Test for ++t argument
+ helptags ++t Xdir
+ call assert_equal(["help-tags\ttags\t1"], readfile('Xdir/tags'))
+ call delete('Xdir/tags')
+
+ " The following tests fail on FreeBSD for some reason
+ if has('unix') && !has('bsd')
+ " Read-only tags file
+ call mkdir('Xdir/doc', 'p')
+ call writefile([''], 'Xdir/doc/tags')
+ call writefile([], 'Xdir/doc/sample.txt')
+ call setfperm('Xdir/doc/tags', 'r-xr--r--')
+ call assert_fails('helptags Xdir/doc', 'E152:', getfperm('Xdir/doc/tags'))
+
+ let rtp = &rtp
+ let &rtp = 'Xdir'
+ helptags ALL
+ let &rtp = rtp
+
+ call delete('Xdir/doc/tags')
+
+ " No permission to read the help file
+ call setfperm('Xdir/a/doc/sample.txt', '-w-------')
+ call assert_fails('helptags Xdir', 'E153:', getfperm('Xdir/a/doc/sample.txt'))
+ call delete('Xdir/a/doc/sample.txt')
+ call delete('Xdir/tags')
+ endif
+
+ " Duplicate tags in the help file
+ call writefile(['*tag1*', '*tag1*', '*tag2*'], 'Xdir/a/doc/sample.txt')
+ call assert_fails('helptags Xdir', 'E154:')
+
+ call delete('Xdir', 'rf')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim
index ad6d325510..7a846e5ea0 100644
--- a/src/nvim/testdir/test_normal.vim
+++ b/src/nvim/testdir/test_normal.vim
@@ -222,6 +222,21 @@ func Test_normal05_formatexpr_setopt()
set formatexpr=
endfunc
+" When 'formatexpr' returns non-zero, internal formatting is used.
+func Test_normal_formatexpr_returns_nonzero()
+ new
+ call setline(1, ['one', 'two'])
+ func! Format()
+ return 1
+ endfunc
+ setlocal formatexpr=Format()
+ normal VGgq
+ call assert_equal(['one two'], getline(1, '$'))
+ setlocal formatexpr=
+ delfunc Format
+ close!
+endfunc
+
func Test_normal06_formatprg()
" basic test for formatprg
" only test on non windows platform
diff --git a/src/nvim/testdir/test_regexp_utf8.vim b/src/nvim/testdir/test_regexp_utf8.vim
index 4466ad436a..513780938e 100644
--- a/src/nvim/testdir/test_regexp_utf8.vim
+++ b/src/nvim/testdir/test_regexp_utf8.vim
@@ -533,4 +533,15 @@ func Test_search_with_end_offset()
close!
endfunc
+" Check that "^" matches even when the line starts with a combining char
+func Test_match_start_of_line_combining()
+ new
+ call setline(1, ['', "\u05ae", ''])
+ exe "normal gg/^\<CR>"
+ call assert_equal(2, getcurpos()[1])
+ bwipe!
+endfunc
+
+
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_registers.vim b/src/nvim/testdir/test_registers.vim
index 19a7c6c9d0..2f72b6a4c0 100644
--- a/src/nvim/testdir/test_registers.vim
+++ b/src/nvim/testdir/test_registers.vim
@@ -254,4 +254,15 @@ func Test_ve_blockpaste()
bwipe!
endfunc
+func Test_insert_small_delete()
+ new
+ call setline(1, ['foo foobar bar'])
+ call cursor(1,1)
+ exe ":norm! ciw'\<C-R>-'"
+ call assert_equal("'foo' foobar bar", getline(1))
+ exe ":norm! w.w."
+ call assert_equal("'foo' 'foobar' 'bar'", getline(1))
+ bwipe!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_signals.vim b/src/nvim/testdir/test_signals.vim
new file mode 100644
index 0000000000..338c0d79ff
--- /dev/null
+++ b/src/nvim/testdir/test_signals.vim
@@ -0,0 +1,140 @@
+" Test signal handling.
+
+source check.vim
+source term_util.vim
+
+CheckUnix
+
+source shared.vim
+
+" Check whether a signal is available on this system.
+func HasSignal(signal)
+ let signals = system('kill -l')
+ return signals =~# '\<' .. a:signal .. '\>'
+endfunc
+
+" Test signal WINCH (window resize signal)
+func Test_signal_WINCH()
+ throw 'skipped: Nvim cannot avoid terminal resize'
+ if has('gui_running') || !HasSignal('WINCH')
+ return
+ endif
+
+ " We do not actually want to change the size of the terminal.
+ let old_WS = ''
+ if exists('&t_WS')
+ let old_WS = &t_WS
+ let &t_WS = ''
+ endif
+
+ let old_lines = &lines
+ let old_columns = &columns
+ let new_lines = &lines - 2
+ let new_columns = &columns - 2
+
+ exe 'set lines=' .. new_lines
+ exe 'set columns=' .. new_columns
+ call assert_equal(new_lines, &lines)
+ call assert_equal(new_columns, &columns)
+
+ " Send signal and wait for signal to be processed.
+ " 'lines' and 'columns' should have been restored
+ " after handing signal WINCH.
+ exe 'silent !kill -s WINCH ' .. getpid()
+ call WaitForAssert({-> assert_equal(old_lines, &lines)})
+ call assert_equal(old_columns, &columns)
+
+ if old_WS != ''
+ let &t_WS = old_WS
+ endif
+endfunc
+
+" Test signal PWR, which should update the swap file.
+func Test_signal_PWR()
+ if !HasSignal('PWR')
+ return
+ endif
+
+ " Set a very large 'updatetime' and 'updatecount', so that we can be sure
+ " that swap file is updated as a result of sending PWR signal, and not
+ " because of exceeding 'updatetime' or 'updatecount' when changing buffer.
+ set updatetime=100000 updatecount=100000
+ new Xtest_signal_PWR
+ let swap_name = swapname('%')
+ call setline(1, '123')
+ preserve
+ let swap_content = readfile(swap_name, 'b')
+
+ " Update the buffer and check that the swap file is not yet updated,
+ " since we set 'updatetime' and 'updatecount' to large values.
+ call setline(1, 'abc')
+ call assert_equal(swap_content, readfile(swap_name, 'b'))
+
+ " Sending PWR signal should update the swap file.
+ exe 'silent !kill -s PWR ' .. getpid()
+ call WaitForAssert({-> assert_notequal(swap_content, readfile(swap_name, 'b'))})
+
+ bwipe!
+ set updatetime& updatecount&
+endfunc
+
+" Test a deadly signal.
+"
+" There are several deadly signals: SISEGV, SIBUS, SIGTERM...
+" Test uses signal SIGTERM as it does not create a core
+" dump file unlike SIGSEGV, SIGBUS, etc. See "man 7 signals.
+"
+" Vim should exit with a deadly signal and unsaved changes
+" should be recoverable from the swap file preserved as a
+" result of the deadly signal handler.
+func Test_deadly_signal_TERM()
+ if !HasSignal('TERM')
+ throw 'Skipped: TERM signal not supported'
+ endif
+ if !CanRunVimInTerminal()
+ throw 'Skipped: cannot run vim in terminal'
+ endif
+ let cmd = GetVimCommand()
+ if cmd =~ 'valgrind'
+ throw 'Skipped: cannot test signal TERM with valgrind'
+ endif
+
+ " If test fails once, it can leave temporary files and trying to rerun
+ " the test would then fail again if they are not deleted first.
+ call delete('.Xsig_TERM.swp')
+ call delete('XsetupAucmd')
+ call delete('XautoOut')
+ let lines =<< trim END
+ au VimLeave * call writefile(["VimLeave triggered"], "XautoOut", "as")
+ au VimLeavePre * call writefile(["VimLeavePre triggered"], "XautoOut", "as")
+ END
+ call writefile(lines, 'XsetupAucmd')
+
+ let buf = RunVimInTerminal('-S XsetupAucmd Xsig_TERM', {'rows': 6})
+ let pid_vim = term_getjob(buf)->job_info().process
+
+ call term_sendkeys(buf, ":call setline(1, 'foo')\n")
+ call WaitForAssert({-> assert_equal('foo', term_getline(buf, 1))})
+
+ call assert_false(filereadable('Xsig_TERM'))
+ exe 'silent !kill -s TERM ' .. pid_vim
+ call WaitForAssert({-> assert_true(filereadable('.Xsig_TERM.swp'))})
+
+ " Don't call StopVimInTerminal() as it expects job to be still running.
+ call WaitForAssert({-> assert_equal("finished", term_getstatus(buf))})
+
+ new
+ silent recover .Xsig_TERM.swp
+ call assert_equal(['foo'], getline(1, '$'))
+
+ let result = readfile('XautoOut')
+ call assert_match('VimLeavePre triggered', result[0])
+ call assert_match('VimLeave triggered', result[1])
+
+ %bwipe!
+ call delete('.Xsig_TERM.swp')
+ call delete('XsetupAucmd')
+ call delete('XautoOut')
+endfunc
+
+" vim: ts=8 sw=2 sts=2 tw=80 fdm=marker
diff --git a/src/nvim/testdir/test_signs.vim b/src/nvim/testdir/test_signs.vim
index 1e6c311374..4bbd722fdb 100644
--- a/src/nvim/testdir/test_signs.vim
+++ b/src/nvim/testdir/test_signs.vim
@@ -122,9 +122,9 @@ func Test_sign()
call assert_fails("sign define Sign4 text=a\e linehl=Comment", 'E239:')
call assert_fails("sign define Sign4 text=\ea linehl=Comment", 'E239:')
- " Only 1 or 2 character text is allowed
+ " Only 0, 1 or 2 character text is allowed
call assert_fails("sign define Sign4 text=abc linehl=Comment", 'E239:')
- call assert_fails("sign define Sign4 text= linehl=Comment", 'E239:')
+ " call assert_fails("sign define Sign4 text= linehl=Comment", 'E239:')
call assert_fails("sign define Sign4 text=\\ ab linehl=Comment", 'E239:')
" define sign with whitespace
@@ -306,7 +306,7 @@ func Test_sign_invalid_commands()
call assert_fails('sign jump 1 name=', 'E474:')
call assert_fails('sign jump 1 name=Sign1', 'E474:')
call assert_fails('sign jump 1 line=100', '474:')
- call assert_fails('sign define Sign2 text=', 'E239:')
+ " call assert_fails('sign define Sign2 text=', 'E239:')
" Non-numeric identifier for :sign place
call assert_fails("sign place abc line=3 name=Sign1 buffer=" . bufnr(''),
\ 'E474:')
@@ -415,7 +415,7 @@ func Test_sign_funcs()
" Tests for invalid arguments to sign_define()
call assert_fails('call sign_define("sign4", {"text" : "===>"})', 'E239:')
- call assert_fails('call sign_define("sign5", {"text" : ""})', 'E239:')
+ " call assert_fails('call sign_define("sign5", {"text" : ""})', 'E239:')
call assert_fails('call sign_define([])', 'E730:')
call assert_fails('call sign_define("sign6", [])', 'E715:')
diff --git a/src/nvim/testdir/test_startup.vim b/src/nvim/testdir/test_startup.vim
index 6214975ef5..e7f332bc4c 100644
--- a/src/nvim/testdir/test_startup.vim
+++ b/src/nvim/testdir/test_startup.vim
@@ -686,3 +686,53 @@ func Test_v_argv()
call assert_true(idx > 2)
call assert_equal(['arg1', '--cmd', 'echo v:argv', '--cmd', 'q'']'], list[idx:])
endfunc
+
+" Test the '-T' argument which sets the 'term' option.
+func Test_T_arg()
+ throw 'skipped: Nvim does not support "-T" argument'
+ CheckNotGui
+ let after =<< trim [CODE]
+ call writefile([&term], "Xtest_T_arg")
+ qall
+ [CODE]
+
+ for t in ['builtin_dumb', 'builtin_ansi']
+ if RunVim([], after, '-T ' .. t)
+ let lines = readfile('Xtest_T_arg')
+ call assert_equal([t], lines)
+ endif
+ endfor
+
+ call delete('Xtest_T_arg')
+endfunc
+
+" Test the '-x' argument to read/write encrypted files.
+func Test_x_arg()
+ CheckRunVimInTerminal
+ CheckFeature cryptv
+
+ " Create an encrypted file Xtest_x_arg.
+ let buf = RunVimInTerminal('-n -x Xtest_x_arg', #{rows: 10, wait_for_ruler: 0})
+ call WaitForAssert({-> assert_match('^Enter encryption key: ', term_getline(buf, 10))})
+ call term_sendkeys(buf, "foo\n")
+ call WaitForAssert({-> assert_match('^Enter same key again: ', term_getline(buf, 10))})
+ call term_sendkeys(buf, "foo\n")
+ call WaitForAssert({-> assert_match(' All$', term_getline(buf, 10))})
+ call term_sendkeys(buf, "itest\<Esc>:w\<Enter>")
+ call WaitForAssert({-> assert_match('"Xtest_x_arg" \[New\]\[blowfish2\] 1L, 5B written',
+ \ term_getline(buf, 10))})
+ call StopVimInTerminal(buf)
+
+ " Read the encrypted file and check that it contains the expected content "test"
+ let buf = RunVimInTerminal('-n -x Xtest_x_arg', #{rows: 10, wait_for_ruler: 0})
+ call WaitForAssert({-> assert_match('^Enter encryption key: ', term_getline(buf, 10))})
+ call term_sendkeys(buf, "foo\n")
+ call WaitForAssert({-> assert_match('^Enter same key again: ', term_getline(buf, 10))})
+ call term_sendkeys(buf, "foo\n")
+ call WaitForAssert({-> assert_match('^test', term_getline(buf, 1))})
+ call StopVimInTerminal(buf)
+
+ call delete('Xtest_x_arg')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_tabpage.vim b/src/nvim/testdir/test_tabpage.vim
index bc7c69d920..2b6a89647e 100644
--- a/src/nvim/testdir/test_tabpage.vim
+++ b/src/nvim/testdir/test_tabpage.vim
@@ -1,6 +1,7 @@
" Tests for tabpage
source screendump.vim
+source check.vim
function Test_tabpage()
bw!
@@ -222,6 +223,34 @@ function Test_tabpage_with_autocmd()
1tabonly!
endfunction
+" Test autocommands on tab drop
+function Test_tabpage_with_autocmd_tab_drop()
+ augroup TestTabpageGroup
+ au!
+ autocmd TabEnter * call add(s:li, 'TabEnter')
+ autocmd WinEnter * call add(s:li, 'WinEnter')
+ autocmd BufEnter * call add(s:li, 'BufEnter')
+ autocmd TabLeave * call add(s:li, 'TabLeave')
+ autocmd WinLeave * call add(s:li, 'WinLeave')
+ autocmd BufLeave * call add(s:li, 'BufLeave')
+ augroup END
+
+ let s:li = []
+ tab drop test1
+ call assert_equal(['BufLeave', 'BufEnter'], s:li)
+
+ let s:li = []
+ tab drop test2 test3
+ call assert_equal([
+ \ 'TabLeave', 'TabEnter', 'TabLeave', 'TabEnter',
+ \ 'TabLeave', 'WinEnter', 'TabEnter', 'BufEnter',
+ \ 'TabLeave', 'WinEnter', 'TabEnter', 'BufEnter'], s:li)
+
+ autocmd! TestTabpageGroup
+ augroup! TestTabpageGroup
+ 1tabonly!
+endfunction
+
function Test_tabpage_with_tab_modifier()
for n in range(4)
tabedit
@@ -579,4 +608,82 @@ func Test_tabpage_cmdheight()
call delete('XTest_tabpage_cmdheight')
endfunc
+" Return the terminal key code for selecting a tab page from the tabline. This
+" sequence contains the following codes: a CSI (0x9b), KS_TABLINE (0xf0),
+" KS_FILLER (0x58) and then the tab page number.
+func TabLineSelectPageCode(tabnr)
+ return "\x9b\xf0\x58" .. nr2char(a:tabnr)
+endfunc
+
+" Return the terminal key code for opening a new tabpage from the tabpage
+" menu. This sequence consists of the following codes: a CSI (0x9b),
+" KS_TABMENU (0xef), KS_FILLER (0x58), the tab page number and
+" TABLINE_MENU_NEW (2).
+func TabMenuNewItemCode(tabnr)
+ return "\x9b\xef\x58" .. nr2char(a:tabnr) .. nr2char(2)
+endfunc
+
+" Return the terminal key code for closing a tabpage from the tabpage menu.
+" This sequence consists of the following codes: a CSI (0x9b), KS_TABMENU
+" (0xef), KS_FILLER (0x58), the tab page number and TABLINE_MENU_CLOSE (1).
+func TabMenuCloseItemCode(tabnr)
+ return "\x9b\xef\x58" .. nr2char(a:tabnr) .. nr2char(1)
+endfunc
+
+" Test for using the tabpage menu from the insert and normal modes
+func Test_tabline_tabmenu()
+ " only works in GUI
+ CheckGui
+
+ %bw!
+ tabnew
+ tabnew
+ call assert_equal(3, tabpagenr())
+
+ " go to tab page 2 in normal mode
+ call feedkeys(TabLineSelectPageCode(2), "Lx!")
+ call assert_equal(2, tabpagenr())
+
+ " close tab page 3 in normal mode
+ call feedkeys(TabMenuCloseItemCode(3), "Lx!")
+ call assert_equal(2, tabpagenr('$'))
+ call assert_equal(2, tabpagenr())
+
+ " open new tab page before tab page 1 in normal mode
+ call feedkeys(TabMenuNewItemCode(1), "Lx!")
+ call assert_equal(1, tabpagenr())
+ call assert_equal(3, tabpagenr('$'))
+
+ " go to tab page 2 in operator-pending mode (should beep)
+ call assert_beeps('call feedkeys("f" .. TabLineSelectPageCode(2), "Lx!")')
+
+ " open new tab page before tab page 1 in operator-pending mode (should beep)
+ call assert_beeps('call feedkeys("f" .. TabMenuNewItemCode(1), "Lx!")')
+
+ " open new tab page after tab page 3 in normal mode
+ call feedkeys(TabMenuNewItemCode(4), "Lx!")
+ call assert_equal(4, tabpagenr())
+ call assert_equal(4, tabpagenr('$'))
+
+ " go to tab page 2 in insert mode
+ call feedkeys("i" .. TabLineSelectPageCode(2) .. "\<C-C>", "Lx!")
+ call assert_equal(2, tabpagenr())
+
+ " close tab page 2 in insert mode
+ call feedkeys("i" .. TabMenuCloseItemCode(2) .. "\<C-C>", "Lx!")
+ call assert_equal(3, tabpagenr('$'))
+
+ " open new tab page before tab page 3 in insert mode
+ call feedkeys("i" .. TabMenuNewItemCode(3) .. "\<C-C>", "Lx!")
+ call assert_equal(3, tabpagenr())
+ call assert_equal(4, tabpagenr('$'))
+
+ " open new tab page after tab page 4 in insert mode
+ call feedkeys("i" .. TabMenuNewItemCode(5) .. "\<C-C>", "Lx!")
+ call assert_equal(5, tabpagenr())
+ call assert_equal(5, tabpagenr('$'))
+
+ %bw!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_textformat.vim b/src/nvim/testdir/test_textformat.vim
index 2223be952c..4af52b536c 100644
--- a/src/nvim/testdir/test_textformat.vim
+++ b/src/nvim/testdir/test_textformat.vim
@@ -424,6 +424,15 @@ func Test_format_align()
\ ], getline(1, '$'))
enew!
+ " align text with 'wrapmargin'
+ 50vnew
+ call setline(1, ['Vim'])
+ setl textwidth=0
+ setl wrapmargin=30
+ right
+ call assert_equal("\t\t Vim", getline(1))
+ q!
+
set tw&
endfunc
@@ -931,4 +940,217 @@ func Test_substitute()
call assert_equal('a1a2a3a', substitute('123', '\zs', 'a', 'g'))
endfunc
+" Test for 'a' and 'w' flags in 'formatoptions'
+func Test_fo_a_w()
+ new
+ setlocal fo+=aw tw=10
+ call feedkeys("iabc abc a abc\<Esc>k0weade", 'xt')
+ call assert_equal(['abc abcde ', 'a abc'], getline(1, '$'))
+
+ " Test for 'a', 'w' and '1' options.
+ setlocal textwidth=0
+ setlocal fo=1aw
+ %d
+ call setline(1, '. foo')
+ normal 72ig
+ call feedkeys('a uu uu uu', 'xt')
+ call assert_equal('g uu uu ', getline(1)[-8:])
+ call assert_equal(['uu. foo'], getline(2, '$'))
+
+ " using backspace or "x" triggers reformat
+ call setline(1, ['1 2 3 4 5 ', '6 7 8 9'])
+ set tw=10
+ set fo=taw
+ set bs=indent,eol,start
+ exe "normal 1G4la\<BS>\<BS>\<Esc>"
+ call assert_equal(['1 2 4 5 6 ', '7 8 9'], getline(1, 2))
+ exe "normal f4xx"
+ call assert_equal(['1 2 5 6 7 ', '8 9'], getline(1, 2))
+
+ set tw=0
+ set fo&
+ %bw!
+endfunc
+
+" Test for formatting lines using gq in visual mode
+func Test_visual_gq_format()
+ new
+ call setline(1, ['one two three four', 'five six', 'one two'])
+ setl textwidth=10
+ call feedkeys('ggv$jj', 'xt')
+ redraw!
+ normal gq
+ %d
+ call setline(1, ['one two three four', 'five six', 'one two'])
+ normal G$
+ call feedkeys('v0kk', 'xt')
+ redraw!
+ normal gq
+ setl textwidth&
+ close!
+endfunc
+
+" Test for 'n' flag in 'formatoptions' to format numbered lists
+func Test_fo_n()
+ new
+ setlocal autoindent
+ setlocal textwidth=12
+ setlocal fo=n
+ call setline(1, [' 1) one two three four', ' 2) two'])
+ normal gggqG
+ call assert_equal([' 1) one two', ' three', ' four', ' 2) two'],
+ \ getline(1, '$'))
+ close!
+endfunc
+
+" Test for 'formatlistpat' option
+func Test_formatlistpat()
+ new
+ setlocal autoindent
+ setlocal textwidth=10
+ setlocal fo=n
+ setlocal formatlistpat=^\\s*-\\s*
+ call setline(1, [' - one two three', ' - two'])
+ normal gggqG
+ call assert_equal([' - one', ' two', ' three', ' - two'],
+ \ getline(1, '$'))
+ close!
+endfunc
+
+" Test for the 'b' and 'v' flags in 'formatoptions'
+" Text should wrap only if a space character is inserted at or before
+" 'textwidth'
+func Test_fo_b()
+ new
+ setlocal textwidth=20
+
+ setlocal formatoptions=t
+ call setline(1, 'one two three four')
+ call feedkeys('Amore', 'xt')
+ call assert_equal(['one two three', 'fourmore'], getline(1, '$'))
+
+ setlocal formatoptions=bt
+ %d
+ call setline(1, 'one two three four')
+ call feedkeys('Amore five', 'xt')
+ call assert_equal(['one two three fourmore five'], getline(1, '$'))
+
+ setlocal formatoptions=bt
+ %d
+ call setline(1, 'one two three four')
+ call feedkeys('A five', 'xt')
+ call assert_equal(['one two three four', 'five'], getline(1, '$'))
+
+ setlocal formatoptions=vt
+ %d
+ call setline(1, 'one two three four')
+ call feedkeys('Amore five', 'xt')
+ call assert_equal(['one two three fourmore', 'five'], getline(1, '$'))
+
+ close!
+endfunc
+
+" Test for the '1' flag in 'formatoptions'. Don't wrap text after a one letter
+" word.
+func Test_fo_1()
+ new
+ setlocal textwidth=20
+
+ setlocal formatoptions=t
+ call setline(1, 'one two three four')
+ call feedkeys('A a bird', 'xt')
+ call assert_equal(['one two three four a', 'bird'], getline(1, '$'))
+
+ %d
+ setlocal formatoptions=t1
+ call setline(1, 'one two three four')
+ call feedkeys('A a bird', 'xt')
+ call assert_equal(['one two three four', 'a bird'], getline(1, '$'))
+
+ close!
+endfunc
+
+" Test for 'l' flag in 'formatoptions'. When starting insert mode, if a line
+" is longer than 'textwidth', then it is not broken.
+func Test_fo_l()
+ new
+ setlocal textwidth=20
+
+ setlocal formatoptions=t
+ call setline(1, 'one two three four five')
+ call feedkeys('A six', 'xt')
+ call assert_equal(['one two three four', 'five six'], getline(1, '$'))
+
+ %d
+ setlocal formatoptions=tl
+ call setline(1, 'one two three four five')
+ call feedkeys('A six', 'xt')
+ call assert_equal(['one two three four five six'], getline(1, '$'))
+
+ close!
+endfunc
+
+" Test for the '2' flag in 'formatoptions'
+func Test_fo_2()
+ new
+ setlocal autoindent
+ setlocal formatoptions=t2
+ setlocal textwidth=30
+ call setline(1, ["\tfirst line of a paragraph.",
+ \ "second line of the same paragraph.",
+ \ "third line."])
+ normal gggqG
+ call assert_equal(["\tfirst line of a",
+ \ "paragraph. second line of the",
+ \ "same paragraph. third line."], getline(1, '$'))
+ close!
+endfunc
+
+" Test for formatting lines where only the first line has a comment.
+func Test_fo_gq_with_firstline_comment()
+ new
+ setlocal formatoptions=tcq
+ call setline(1, ['- one two', 'three'])
+ normal gggqG
+ call assert_equal(['- one two three'], getline(1, '$'))
+
+ %d
+ call setline(1, ['- one', '- two'])
+ normal gggqG
+ call assert_equal(['- one', '- two'], getline(1, '$'))
+ close!
+endfunc
+
+" Test for trying to join a comment line with a non-comment line
+func Test_join_comments()
+ new
+ call setline(1, ['one', '/* two */', 'three'])
+ normal gggqG
+ call assert_equal(['one', '/* two */', 'three'], getline(1, '$'))
+ close!
+endfunc
+
+" Test for using 'a' in 'formatoptions' with comments
+func Test_autoformat_comments()
+ new
+ setlocal formatoptions+=a
+ call feedkeys("a- one\n- two\n", 'xt')
+ call assert_equal(['- one', '- two', ''], getline(1, '$'))
+
+ %d
+ call feedkeys("a\none\n", 'xt')
+ call assert_equal(['', 'one', ''], getline(1, '$'))
+
+ setlocal formatoptions+=aw
+ %d
+ call feedkeys("aone \ntwo\n", 'xt')
+ call assert_equal(['one two', ''], getline(1, '$'))
+
+ %d
+ call feedkeys("aone\ntwo\n", 'xt')
+ call assert_equal(['one', 'two', ''], getline(1, '$'))
+
+ close!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/vim.h b/src/nvim/vim.h
index 900f2acd81..01f20cf29a 100644
--- a/src/nvim/vim.h
+++ b/src/nvim/vim.h
@@ -208,6 +208,7 @@ enum { FOLD_TEXT_LEN = 51 }; //!< buffer size for get_foldtext()
// Size in bytes of the hash used in the undo file.
#define UNDO_HASH_SIZE 32
+#define CLEAR_POINTER(ptr) memset((ptr), 0, sizeof(*(ptr)))
// defines to avoid typecasts from (char_u *) to (char *) and back
// (vim_strchr() is now in strings.c)
diff --git a/src/nvim/window.c b/src/nvim/window.c
index ed9d3ce3d8..6ecfd9ab64 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -641,7 +641,7 @@ void win_set_minimal_style(win_T *wp)
wp->w_p_scl = (char_u *)xstrdup("auto");
}
- // foldcolumn: use 'auto'
+ // foldcolumn: use '0'
if (wp->w_p_fdc[0] != '0') {
xfree(wp->w_p_fdc);
wp->w_p_fdc = (char_u *)xstrdup("0");
@@ -700,9 +700,10 @@ int win_fdccol_count(win_T *wp)
const char *fdc = (const char *)wp->w_p_fdc;
// auto:<NUM>
- if (strncmp(fdc, "auto:", 5) == 0) {
+ if (strncmp(fdc, "auto", 4) == 0) {
+ const int fdccol = fdc[4] == ':' ? fdc[5] - '0' : 1;
int needed_fdccols = getDeepestNesting(wp);
- return MIN(fdc[5] - '0', needed_fdccols);
+ return MIN(fdccol, needed_fdccols);
} else {
return fdc[0] - '0';
}
@@ -1636,6 +1637,19 @@ bool win_valid(const win_T *win) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
return false;
}
+// Find window "handle" in the current tab page.
+// Return NULL if not found.
+win_T *win_find_by_handle(handle_T handle)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (wp->handle == handle) {
+ return wp;
+ }
+ }
+ return NULL;
+}
+
/// Check if "win" is a pointer to an existing window in any tabpage.
///
/// @param win window to check