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