diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 08:50:31 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 08:50:31 +0000 |
commit | aed8ce9da277f5ecffe968b324f242c41c3b752a (patch) | |
tree | d2e538394cb7a8a7c42a4aac6ccf1a8e3256999b /src/buffer.c | |
parent | Initial commit. (diff) | |
download | vim-aed8ce9da277f5ecffe968b324f242c41c3b752a.tar.xz vim-aed8ce9da277f5ecffe968b324f242c41c3b752a.zip |
Adding upstream version 2:9.0.1378.upstream/2%9.0.1378upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/buffer.c')
-rw-r--r-- | src/buffer.c | 6058 |
1 files changed, 6058 insertions, 0 deletions
diff --git a/src/buffer.c b/src/buffer.c new file mode 100644 index 0000000..ff35729 --- /dev/null +++ b/src/buffer.c @@ -0,0 +1,6058 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +/* + * buffer.c: functions for dealing with the buffer structure + */ + +/* + * The buffer list is a double linked list of all buffers. + * Each buffer can be in one of these states: + * never loaded: BF_NEVERLOADED is set, only the file name is valid + * not loaded: b_ml.ml_mfp == NULL, no memfile allocated + * hidden: b_nwindows == 0, loaded but not displayed in a window + * normal: loaded and displayed in a window + * + * Instead of storing file names all over the place, each file name is + * stored in the buffer list. It can be referenced by a number. + * + * The current implementation remembers all file names ever used. + */ + +#include "vim.h" + + +#ifdef FEAT_EVAL +// Determines how deeply nested %{} blocks will be evaluated in statusline. +# define MAX_STL_EVAL_DEPTH 100 +#endif + +static void enter_buffer(buf_T *buf); +static void buflist_getfpos(void); +static char_u *buflist_match(regmatch_T *rmp, buf_T *buf, int ignore_case); +static char_u *fname_match(regmatch_T *rmp, char_u *name, int ignore_case); +#ifdef UNIX +static buf_T *buflist_findname_stat(char_u *ffname, stat_T *st); +static int otherfile_buf(buf_T *buf, char_u *ffname, stat_T *stp); +static int buf_same_ino(buf_T *buf, stat_T *stp); +#else +static int otherfile_buf(buf_T *buf, char_u *ffname); +#endif +static int value_changed(char_u *str, char_u **last); +static int append_arg_number(win_T *wp, char_u *buf, int buflen, int add_file); +static void free_buffer(buf_T *); +static void free_buffer_stuff(buf_T *buf, int free_options); +static int bt_nofileread(buf_T *buf); +static void no_write_message_buf(buf_T *buf); + +#ifdef UNIX +# define dev_T dev_t +#else +# define dev_T unsigned +#endif + +#define FOR_ALL_BUFS_FROM_LAST(buf) \ + for ((buf) = lastbuf; (buf) != NULL; (buf) = (buf)->b_prev) + +#if defined(FEAT_QUICKFIX) +static char *msg_loclist = N_("[Location List]"); +static char *msg_qflist = N_("[Quickfix List]"); +#endif + +// Number of times free_buffer() was called. +static int buf_free_count = 0; + +static int top_file_num = 1; // highest file number +static garray_T buf_reuse = GA_EMPTY; // file numbers to recycle + +/* + * Return the highest possible buffer number. + */ + int +get_highest_fnum(void) +{ + return top_file_num - 1; +} + +/* + * Read data from buffer for retrying. + */ + static int +read_buffer( + int read_stdin, // read file from stdin, otherwise fifo + exarg_T *eap, // for forced 'ff' and 'fenc' or NULL + int flags) // extra flags for readfile() +{ + int retval = OK; + linenr_T line_count; + + // Read from the buffer which the text is already filled in and append at + // the end. This makes it possible to retry when 'fileformat' or + // 'fileencoding' was guessed wrong. + line_count = curbuf->b_ml.ml_line_count; + retval = readfile( + read_stdin ? NULL : curbuf->b_ffname, + read_stdin ? NULL : curbuf->b_fname, + line_count, (linenr_T)0, (linenr_T)MAXLNUM, eap, + flags | READ_BUFFER); + if (retval == OK) + { + // Delete the binary lines. + while (--line_count >= 0) + ml_delete((linenr_T)1); + } + else + { + // Delete the converted lines. + while (curbuf->b_ml.ml_line_count > line_count) + ml_delete(line_count); + } + // Put the cursor on the first line. + curwin->w_cursor.lnum = 1; + curwin->w_cursor.col = 0; + + if (read_stdin) + { + // Set or reset 'modified' before executing autocommands, so that + // it can be changed there. + if (!readonlymode && !BUFEMPTY()) + changed(); + else if (retval == OK) + unchanged(curbuf, FALSE, TRUE); + + if (retval == OK) + { +#ifdef FEAT_EVAL + apply_autocmds_retval(EVENT_STDINREADPOST, NULL, NULL, FALSE, + curbuf, &retval); +#else + apply_autocmds(EVENT_STDINREADPOST, NULL, NULL, FALSE, curbuf); +#endif + } + } + return retval; +} + +#if defined(FEAT_EVAL) || defined(PROTO) +/* + * Ensure buffer "buf" is loaded. Does not trigger the swap-exists action. + */ + void +buffer_ensure_loaded(buf_T *buf) +{ + if (buf->b_ml.ml_mfp != NULL) + return; + + aco_save_T aco; + + // Make sure the buffer is in a window. If not then skip it. + aucmd_prepbuf(&aco, buf); + if (curbuf == buf) + { + if (swap_exists_action != SEA_READONLY) + swap_exists_action = SEA_NONE; + open_buffer(FALSE, NULL, 0); + aucmd_restbuf(&aco); + } +} +#endif + +/* + * Open current buffer, that is: open the memfile and read the file into + * memory. + * Return FAIL for failure, OK otherwise. + */ + int +open_buffer( + int read_stdin, // read file from stdin + exarg_T *eap, // for forced 'ff' and 'fenc' or NULL + int flags_arg) // extra flags for readfile() +{ + int flags = flags_arg; + int retval = OK; + bufref_T old_curbuf; +#ifdef FEAT_SYN_HL + long old_tw = curbuf->b_p_tw; +#endif + int read_fifo = FALSE; + + // The 'readonly' flag is only set when BF_NEVERLOADED is being reset. + // When re-entering the same buffer, it should not change, because the + // user may have reset the flag by hand. + if (readonlymode && curbuf->b_ffname != NULL + && (curbuf->b_flags & BF_NEVERLOADED)) + curbuf->b_p_ro = TRUE; + + if (ml_open(curbuf) == FAIL) + { + // There MUST be a memfile, otherwise we can't do anything + // If we can't create one for the current buffer, take another buffer + close_buffer(NULL, curbuf, 0, FALSE, FALSE); + FOR_ALL_BUFFERS(curbuf) + if (curbuf->b_ml.ml_mfp != NULL) + break; + // If there is no memfile at all, exit. + // This is OK, since there are no changes to lose. + if (curbuf == NULL) + { + emsg(_(e_cannot_allocate_any_buffer_exiting)); + + // Don't try to do any saving, with "curbuf" NULL almost nothing + // will work. + v_dying = 2; + getout(2); + } + + emsg(_(e_cannot_allocate_buffer_using_other_one)); + enter_buffer(curbuf); +#ifdef FEAT_SYN_HL + if (old_tw != curbuf->b_p_tw) + check_colorcolumn(curwin); +#endif + return FAIL; + } + + // The autocommands in readfile() may change the buffer, but only AFTER + // reading the file. + set_bufref(&old_curbuf, curbuf); + modified_was_set = FALSE; + + // mark cursor position as being invalid + curwin->w_valid = 0; + + // A buffer without an actual file should not use the buffer name to read a + // file. + if (bt_nofileread(curbuf)) + flags |= READ_NOFILE; + + // Read the file if there is one. + if (curbuf->b_ffname != NULL +#ifdef FEAT_NETBEANS_INTG + && netbeansReadFile +#endif + ) + { + int old_msg_silent = msg_silent; +#ifdef UNIX + int save_bin = curbuf->b_p_bin; + int perm; +#endif +#ifdef FEAT_NETBEANS_INTG + int oldFire = netbeansFireChanges; + + netbeansFireChanges = 0; +#endif +#ifdef UNIX + perm = mch_getperm(curbuf->b_ffname); + if (perm >= 0 && (S_ISFIFO(perm) + || S_ISSOCK(perm) +# ifdef OPEN_CHR_FILES + || (S_ISCHR(perm) && is_dev_fd_file(curbuf->b_ffname)) +# endif + )) + read_fifo = TRUE; + if (read_fifo) + curbuf->b_p_bin = TRUE; +#endif + if (shortmess(SHM_FILEINFO)) + msg_silent = 1; + retval = readfile(curbuf->b_ffname, curbuf->b_fname, + (linenr_T)0, (linenr_T)0, (linenr_T)MAXLNUM, eap, + flags | READ_NEW | (read_fifo ? READ_FIFO : 0)); +#ifdef UNIX + if (read_fifo) + { + curbuf->b_p_bin = save_bin; + if (retval == OK) + retval = read_buffer(FALSE, eap, flags); + } +#endif + msg_silent = old_msg_silent; +#ifdef FEAT_NETBEANS_INTG + netbeansFireChanges = oldFire; +#endif + // Help buffer is filtered. + if (bt_help(curbuf)) + fix_help_buffer(); + } + else if (read_stdin) + { + int save_bin = curbuf->b_p_bin; + + // First read the text in binary mode into the buffer. + // Then read from that same buffer and append at the end. This makes + // it possible to retry when 'fileformat' or 'fileencoding' was + // guessed wrong. + curbuf->b_p_bin = TRUE; + retval = readfile(NULL, NULL, (linenr_T)0, + (linenr_T)0, (linenr_T)MAXLNUM, NULL, + flags | (READ_NEW + READ_STDIN)); + curbuf->b_p_bin = save_bin; + if (retval == OK) + retval = read_buffer(TRUE, eap, flags); + } + + // if first time loading this buffer, init b_chartab[] + if (curbuf->b_flags & BF_NEVERLOADED) + { + (void)buf_init_chartab(curbuf, FALSE); + parse_cino(curbuf); + } + + // Set/reset the Changed flag first, autocmds may change the buffer. + // Apply the automatic commands, before processing the modelines. + // So the modelines have priority over autocommands. + // + // When reading stdin, the buffer contents always needs writing, so set + // the changed flag. Unless in readonly mode: "ls | gview -". + // When interrupted and 'cpoptions' contains 'i' set changed flag. + if ((got_int && vim_strchr(p_cpo, CPO_INTMOD) != NULL) + || modified_was_set // ":set modified" used in autocmd +#ifdef FEAT_EVAL + || (aborting() && vim_strchr(p_cpo, CPO_INTMOD) != NULL) +#endif + ) + changed(); + else if (retval == OK && !read_stdin && !read_fifo) + unchanged(curbuf, FALSE, TRUE); + save_file_ff(curbuf); // keep this fileformat + + // Set last_changedtick to avoid triggering a TextChanged autocommand right + // after it was added. + curbuf->b_last_changedtick = CHANGEDTICK(curbuf); + curbuf->b_last_changedtick_i = CHANGEDTICK(curbuf); + curbuf->b_last_changedtick_pum = CHANGEDTICK(curbuf); + + // require "!" to overwrite the file, because it wasn't read completely +#ifdef FEAT_EVAL + if (aborting()) +#else + if (got_int) +#endif + curbuf->b_flags |= BF_READERR; + +#ifdef FEAT_FOLDING + // Need to update automatic folding. Do this before the autocommands, + // they may use the fold info. + foldUpdateAll(curwin); +#endif + + // need to set w_topline, unless some autocommand already did that. + if (!(curwin->w_valid & VALID_TOPLINE)) + { + curwin->w_topline = 1; +#ifdef FEAT_DIFF + curwin->w_topfill = 0; +#endif + } +#ifdef FEAT_EVAL + apply_autocmds_retval(EVENT_BUFENTER, NULL, NULL, FALSE, curbuf, &retval); +#else + apply_autocmds(EVENT_BUFENTER, NULL, NULL, FALSE, curbuf); +#endif + + if (retval != OK) + return retval; + + // The autocommands may have changed the current buffer. Apply the + // modelines to the correct buffer, if it still exists and is loaded. + if (bufref_valid(&old_curbuf) && old_curbuf.br_buf->b_ml.ml_mfp != NULL) + { + aco_save_T aco; + + // Go to the buffer that was opened, make sure it is in a window. + // If not then skip it. + aucmd_prepbuf(&aco, old_curbuf.br_buf); + if (curbuf == old_curbuf.br_buf) + { + do_modelines(0); + curbuf->b_flags &= ~(BF_CHECK_RO | BF_NEVERLOADED); + + if ((flags & READ_NOWINENTER) == 0) +#ifdef FEAT_EVAL + apply_autocmds_retval(EVENT_BUFWINENTER, NULL, NULL, + FALSE, curbuf, &retval); +#else + apply_autocmds(EVENT_BUFWINENTER, NULL, NULL, + FALSE, curbuf); +#endif + + // restore curwin/curbuf and a few other things + aucmd_restbuf(&aco); + } + } + + return retval; +} + +/* + * Store "buf" in "bufref" and set the free count. + */ + void +set_bufref(bufref_T *bufref, buf_T *buf) +{ + bufref->br_buf = buf; + bufref->br_fnum = buf == NULL ? 0 : buf->b_fnum; + bufref->br_buf_free_count = buf_free_count; +} + +/* + * Return TRUE if "bufref->br_buf" points to the same buffer as when + * set_bufref() was called and it is a valid buffer. + * Only goes through the buffer list if buf_free_count changed. + * Also checks if b_fnum is still the same, a :bwipe followed by :new might get + * the same allocated memory, but it's a different buffer. + */ + int +bufref_valid(bufref_T *bufref) +{ + return bufref->br_buf_free_count == buf_free_count + ? TRUE : buf_valid(bufref->br_buf) + && bufref->br_fnum == bufref->br_buf->b_fnum; +} + +/* + * Return TRUE if "buf" points to a valid buffer (in the buffer list). + * This can be slow if there are many buffers, prefer using bufref_valid(). + */ + int +buf_valid(buf_T *buf) +{ + buf_T *bp; + + // Assume that we more often have a recent buffer, start with the last + // one. + FOR_ALL_BUFS_FROM_LAST(bp) + if (bp == buf) + return TRUE; + return FALSE; +} + +/* + * A hash table used to quickly lookup a buffer by its number. + */ +static hashtab_T buf_hashtab; + + static void +buf_hashtab_add(buf_T *buf) +{ + sprintf((char *)buf->b_key, "%x", buf->b_fnum); + if (hash_add(&buf_hashtab, buf->b_key, "create buffer") == FAIL) + emsg(_(e_buffer_cannot_be_registered)); +} + + static void +buf_hashtab_remove(buf_T *buf) +{ + hashitem_T *hi = hash_find(&buf_hashtab, buf->b_key); + + if (!HASHITEM_EMPTY(hi)) + hash_remove(&buf_hashtab, hi, "close buffer"); +} + +/* + * Return TRUE when buffer "buf" can be unloaded. + * Give an error message and return FALSE when the buffer is locked or the + * screen is being redrawn and the buffer is in a window. + */ + static int +can_unload_buffer(buf_T *buf) +{ + int can_unload = !buf->b_locked; + + if (can_unload && updating_screen) + { + win_T *wp; + + FOR_ALL_WINDOWS(wp) + if (wp->w_buffer == buf) + { + can_unload = FALSE; + break; + } + } + if (!can_unload) + { + char_u *fname = buf->b_fname != NULL ? buf->b_fname : buf->b_ffname; + + semsg(_(e_attempt_to_delete_buffer_that_is_in_use_str), + fname != NULL ? fname : (char_u *)"[No Name]"); + } + return can_unload; +} + +/* + * Close the link to a buffer. + * "action" is used when there is no longer a window for the buffer. + * It can be: + * 0 buffer becomes hidden + * DOBUF_UNLOAD buffer is unloaded + * DOBUF_DELETE buffer is unloaded and removed from buffer list + * DOBUF_WIPE buffer is unloaded and really deleted + * DOBUF_WIPE_REUSE idem, and add to buf_reuse list + * When doing all but the first one on the current buffer, the caller should + * get a new buffer very soon! + * + * The 'bufhidden' option can force freeing and deleting. + * + * When "abort_if_last" is TRUE then do not close the buffer if autocommands + * cause there to be only one window with this buffer. e.g. when ":quit" is + * supposed to close the window but autocommands close all other windows. + * + * When "ignore_abort" is TRUE don't abort even when aborting() returns TRUE. + * + * Return TRUE when we got to the end and b_nwindows was decremented. + */ + int +close_buffer( + win_T *win, // if not NULL, set b_last_cursor + buf_T *buf, + int action, + int abort_if_last, + int ignore_abort) +{ + int is_curbuf; + int nwindows; + bufref_T bufref; + int is_curwin = (curwin != NULL && curwin->w_buffer == buf); + win_T *the_curwin = curwin; + tabpage_T *the_curtab = curtab; + int unload_buf = (action != 0); + int wipe_buf = (action == DOBUF_WIPE || action == DOBUF_WIPE_REUSE); + int del_buf = (action == DOBUF_DEL || wipe_buf); + + CHECK_CURBUF; + + // Force unloading or deleting when 'bufhidden' says so. + // The caller must take care of NOT deleting/freeing when 'bufhidden' is + // "hide" (otherwise we could never free or delete a buffer). + if (buf->b_p_bh[0] == 'd') // 'bufhidden' == "delete" + { + del_buf = TRUE; + unload_buf = TRUE; + } + else if (buf->b_p_bh[0] == 'w') // 'bufhidden' == "wipe" + { + del_buf = TRUE; + unload_buf = TRUE; + wipe_buf = TRUE; + } + else if (buf->b_p_bh[0] == 'u') // 'bufhidden' == "unload" + unload_buf = TRUE; + +#ifdef FEAT_TERMINAL + // depending on how we get here b_nwindows may already be zero + if (bt_terminal(buf) && (buf->b_nwindows <= 1 || del_buf)) + { + CHECK_CURBUF; + if (term_job_running(buf->b_term)) + { + if (wipe_buf || unload_buf) + { + if (!can_unload_buffer(buf)) + return FALSE; + + // Wiping out or unloading a terminal buffer kills the job. + free_terminal(buf); + + // A terminal buffer is wiped out when job has finished. + del_buf = TRUE; + unload_buf = TRUE; + wipe_buf = TRUE; + } + else + { + // The job keeps running, hide the buffer. + del_buf = FALSE; + unload_buf = FALSE; + } + } + else if (buf->b_p_bh[0] == 'h' && !del_buf) + { + // Hide a terminal buffer. + unload_buf = FALSE; + } + else + { + if (del_buf || unload_buf) + { + // A terminal buffer is wiped out if the job has finished. + // We only do this when there's an intention to unload the + // buffer. This way, :hide and other similar commands won't + // wipe the buffer. + del_buf = TRUE; + unload_buf = TRUE; + wipe_buf = TRUE; + } + } + CHECK_CURBUF; + } +#endif + + // Disallow deleting the buffer when it is locked (already being closed or + // halfway a command that relies on it). Unloading is allowed. + if ((del_buf || wipe_buf) && !can_unload_buffer(buf)) + return FALSE; + + // check no autocommands closed the window + if (win != NULL && win_valid_any_tab(win)) + { + // Set b_last_cursor when closing the last window for the buffer. + // Remember the last cursor position and window options of the buffer. + // This used to be only for the current window, but then options like + // 'foldmethod' may be lost with a ":only" command. + if (buf->b_nwindows == 1) + set_last_cursor(win); + buflist_setfpos(buf, win, + win->w_cursor.lnum == 1 ? 0 : win->w_cursor.lnum, + win->w_cursor.col, TRUE); + } + + set_bufref(&bufref, buf); + + // When the buffer is no longer in a window, trigger BufWinLeave + if (buf->b_nwindows == 1) + { + ++buf->b_locked; + ++buf->b_locked_split; + if (apply_autocmds(EVENT_BUFWINLEAVE, buf->b_fname, buf->b_fname, + FALSE, buf) + && !bufref_valid(&bufref)) + { + // Autocommands deleted the buffer. +aucmd_abort: + emsg(_(e_autocommands_caused_command_to_abort)); + return FALSE; + } + --buf->b_locked; + --buf->b_locked_split; + if (abort_if_last && one_window()) + // Autocommands made this the only window. + goto aucmd_abort; + + // When the buffer becomes hidden, but is not unloaded, trigger + // BufHidden + if (!unload_buf) + { + ++buf->b_locked; + ++buf->b_locked_split; + if (apply_autocmds(EVENT_BUFHIDDEN, buf->b_fname, buf->b_fname, + FALSE, buf) + && !bufref_valid(&bufref)) + // Autocommands deleted the buffer. + goto aucmd_abort; + --buf->b_locked; + --buf->b_locked_split; + if (abort_if_last && one_window()) + // Autocommands made this the only window. + goto aucmd_abort; + } +#ifdef FEAT_EVAL + // autocmds may abort script processing + if (!ignore_abort && aborting()) + return FALSE; +#endif + } + + // If the buffer was in curwin and the window has changed, go back to that + // window, if it still exists. This avoids that ":edit x" triggering a + // "tabnext" BufUnload autocmd leaves a window behind without a buffer. + if (is_curwin && curwin != the_curwin && win_valid_any_tab(the_curwin)) + { + block_autocmds(); + goto_tabpage_win(the_curtab, the_curwin); + unblock_autocmds(); + } + + nwindows = buf->b_nwindows; + + // decrease the link count from windows (unless not in any window) + if (buf->b_nwindows > 0) + --buf->b_nwindows; + +#ifdef FEAT_DIFF + if (diffopt_hiddenoff() && !unload_buf && buf->b_nwindows == 0) + diff_buf_delete(buf); // Clear 'diff' for hidden buffer. +#endif + + // Return when a window is displaying the buffer or when it's not + // unloaded. + if (buf->b_nwindows > 0 || !unload_buf) + return FALSE; + + // Always remove the buffer when there is no file name. + if (buf->b_ffname == NULL) + del_buf = TRUE; + + // When closing the current buffer stop Visual mode before freeing + // anything. + if (buf == curbuf && VIsual_active +#if defined(EXITFREE) + && !entered_free_all_mem +#endif + ) + end_visual_mode(); + + // Free all things allocated for this buffer. + // Also calls the "BufDelete" autocommands when del_buf is TRUE. + // + // Remember if we are closing the current buffer. Restore the number of + // windows, so that autocommands in buf_freeall() don't get confused. + is_curbuf = (buf == curbuf); + buf->b_nwindows = nwindows; + + buf_freeall(buf, (del_buf ? BFA_DEL : 0) + + (wipe_buf ? BFA_WIPE : 0) + + (ignore_abort ? BFA_IGNORE_ABORT : 0)); + + // Autocommands may have deleted the buffer. + if (!bufref_valid(&bufref)) + return FALSE; +#ifdef FEAT_EVAL + // autocmds may abort script processing + if (!ignore_abort && aborting()) + return FALSE; +#endif + + // It's possible that autocommands change curbuf to the one being deleted. + // This might cause the previous curbuf to be deleted unexpectedly. But + // in some cases it's OK to delete the curbuf, because a new one is + // obtained anyway. Therefore only return if curbuf changed to the + // deleted buffer. + if (buf == curbuf && !is_curbuf) + return FALSE; + + if (win_valid_any_tab(win) && win->w_buffer == buf) + win->w_buffer = NULL; // make sure we don't use the buffer now + + // Autocommands may have opened or closed windows for this buffer. + // Decrement the count for the close we do here. + if (buf->b_nwindows > 0) + --buf->b_nwindows; + + /* + * Remove the buffer from the list. + */ + if (wipe_buf) + { + // Do not wipe out the buffer if it is used in a window. + if (buf->b_nwindows > 0) + return FALSE; + + if (action == DOBUF_WIPE_REUSE) + { + // we can re-use this buffer number, store it + if (buf_reuse.ga_itemsize == 0) + ga_init2(&buf_reuse, sizeof(int), 50); + if (ga_grow(&buf_reuse, 1) == OK) + ((int *)buf_reuse.ga_data)[buf_reuse.ga_len++] = buf->b_fnum; + } + if (buf->b_sfname != buf->b_ffname) + VIM_CLEAR(buf->b_sfname); + else + buf->b_sfname = NULL; + VIM_CLEAR(buf->b_ffname); + if (buf->b_prev == NULL) + firstbuf = buf->b_next; + else + buf->b_prev->b_next = buf->b_next; + if (buf->b_next == NULL) + lastbuf = buf->b_prev; + else + buf->b_next->b_prev = buf->b_prev; + free_buffer(buf); + } + else + { + if (del_buf) + { + // Free all internal variables and reset option values, to make + // ":bdel" compatible with Vim 5.7. + free_buffer_stuff(buf, TRUE); + + // Make it look like a new buffer. + buf->b_flags = BF_CHECK_RO | BF_NEVERLOADED; + + // Init the options when loaded again. + buf->b_p_initialized = FALSE; + } + buf_clear_file(buf); + if (del_buf) + buf->b_p_bl = FALSE; + } + // NOTE: at this point "curbuf" may be invalid! + return TRUE; +} + +/* + * Make buffer not contain a file. + */ + void +buf_clear_file(buf_T *buf) +{ + buf->b_ml.ml_line_count = 1; + unchanged(buf, TRUE, TRUE); + buf->b_shortname = FALSE; + buf->b_p_eof = FALSE; + buf->b_start_eof = FALSE; + buf->b_p_eol = TRUE; + buf->b_start_eol = TRUE; + buf->b_p_bomb = FALSE; + buf->b_start_bomb = FALSE; + buf->b_ml.ml_mfp = NULL; + buf->b_ml.ml_flags = ML_EMPTY; // empty buffer +#ifdef FEAT_NETBEANS_INTG + netbeans_deleted_all_lines(buf); +#endif +} + +/* + * buf_freeall() - free all things allocated for a buffer that are related to + * the file. Careful: get here with "curwin" NULL when exiting. + * flags: + * BFA_DEL buffer is going to be deleted + * BFA_WIPE buffer is going to be wiped out + * BFA_KEEP_UNDO do not free undo information + * BFA_IGNORE_ABORT don't abort even when aborting() returns TRUE + */ + void +buf_freeall(buf_T *buf, int flags) +{ + int is_curbuf = (buf == curbuf); + bufref_T bufref; + int is_curwin = (curwin != NULL && curwin->w_buffer == buf); + win_T *the_curwin = curwin; + tabpage_T *the_curtab = curtab; + + // Make sure the buffer isn't closed by autocommands. + ++buf->b_locked; + ++buf->b_locked_split; + set_bufref(&bufref, buf); + if (buf->b_ml.ml_mfp != NULL) + { + if (apply_autocmds(EVENT_BUFUNLOAD, buf->b_fname, buf->b_fname, + FALSE, buf) + && !bufref_valid(&bufref)) + // autocommands deleted the buffer + return; + } + if ((flags & BFA_DEL) && buf->b_p_bl) + { + if (apply_autocmds(EVENT_BUFDELETE, buf->b_fname, buf->b_fname, + FALSE, buf) + && !bufref_valid(&bufref)) + // autocommands deleted the buffer + return; + } + if (flags & BFA_WIPE) + { + if (apply_autocmds(EVENT_BUFWIPEOUT, buf->b_fname, buf->b_fname, + FALSE, buf) + && !bufref_valid(&bufref)) + // autocommands deleted the buffer + return; + } + --buf->b_locked; + --buf->b_locked_split; + + // If the buffer was in curwin and the window has changed, go back to that + // window, if it still exists. This avoids that ":edit x" triggering a + // "tabnext" BufUnload autocmd leaves a window behind without a buffer. + if (is_curwin && curwin != the_curwin && win_valid_any_tab(the_curwin)) + { + block_autocmds(); + goto_tabpage_win(the_curtab, the_curwin); + unblock_autocmds(); + } + +#ifdef FEAT_EVAL + // autocmds may abort script processing + if ((flags & BFA_IGNORE_ABORT) == 0 && aborting()) + return; +#endif + + // It's possible that autocommands change curbuf to the one being deleted. + // This might cause curbuf to be deleted unexpectedly. But in some cases + // it's OK to delete the curbuf, because a new one is obtained anyway. + // Therefore only return if curbuf changed to the deleted buffer. + if (buf == curbuf && !is_curbuf) + return; +#ifdef FEAT_DIFF + diff_buf_delete(buf); // Can't use 'diff' for unloaded buffer. +#endif +#ifdef FEAT_SYN_HL + // Remove any ownsyntax, unless exiting. + if (curwin != NULL && curwin->w_buffer == buf) + reset_synblock(curwin); +#endif + +#ifdef FEAT_FOLDING + // No folds in an empty buffer. + { + win_T *win; + tabpage_T *tp; + + FOR_ALL_TAB_WINDOWS(tp, win) + if (win->w_buffer == buf) + clearFolding(win); + } +#endif + +#ifdef FEAT_TCL + tcl_buffer_free(buf); +#endif + ml_close(buf, TRUE); // close and delete the memline/memfile + buf->b_ml.ml_line_count = 0; // no lines in buffer + if ((flags & BFA_KEEP_UNDO) == 0) + { + u_blockfree(buf); // free the memory allocated for undo + u_clearall(buf); // reset all undo information + } +#ifdef FEAT_SYN_HL + syntax_clear(&buf->b_s); // reset syntax info +#endif +#ifdef FEAT_PROP_POPUP + clear_buf_prop_types(buf); +#endif + buf->b_flags &= ~BF_READERR; // a read error is no longer relevant +} + +/* + * Free a buffer structure and the things it contains related to the buffer + * itself (not the file, that must have been done already). + */ + static void +free_buffer(buf_T *buf) +{ + ++buf_free_count; + free_buffer_stuff(buf, TRUE); +#ifdef FEAT_EVAL + // b:changedtick uses an item in buf_T, remove it now + dictitem_remove(buf->b_vars, (dictitem_T *)&buf->b_ct_di, "free buffer"); + unref_var_dict(buf->b_vars); + remove_listeners(buf); +#endif +#ifdef FEAT_LUA + lua_buffer_free(buf); +#endif +#ifdef FEAT_MZSCHEME + mzscheme_buffer_free(buf); +#endif +#ifdef FEAT_PERL + perl_buf_free(buf); +#endif +#ifdef FEAT_PYTHON + python_buffer_free(buf); +#endif +#ifdef FEAT_PYTHON3 + python3_buffer_free(buf); +#endif +#ifdef FEAT_RUBY + ruby_buffer_free(buf); +#endif +#ifdef FEAT_JOB_CHANNEL + channel_buffer_free(buf); +#endif +#ifdef FEAT_TERMINAL + free_terminal(buf); +#endif +#ifdef FEAT_JOB_CHANNEL + vim_free(buf->b_prompt_text); + free_callback(&buf->b_prompt_callback); + free_callback(&buf->b_prompt_interrupt); +#endif + + buf_hashtab_remove(buf); + + aubuflocal_remove(buf); + + if (autocmd_busy) + { + // Do not free the buffer structure while autocommands are executing, + // it's still needed. Free it when autocmd_busy is reset. + buf->b_next = au_pending_free_buf; + au_pending_free_buf = buf; + } + else + { + vim_free(buf); + if (curbuf == buf) + curbuf = NULL; // make clear it's not to be used + } +} + +/* + * Initializes b:changedtick. + */ + static void +init_changedtick(buf_T *buf) +{ + dictitem_T *di = (dictitem_T *)&buf->b_ct_di; + + di->di_flags = DI_FLAGS_FIX | DI_FLAGS_RO; + di->di_tv.v_type = VAR_NUMBER; + di->di_tv.v_lock = VAR_FIXED; + di->di_tv.vval.v_number = 0; + +#ifdef FEAT_EVAL + STRCPY(buf->b_ct_di.di_key, "changedtick"); + (void)dict_add(buf->b_vars, di); +#endif +} + +/* + * Free the b_wininfo list for buffer "buf". + */ + static void +clear_wininfo(buf_T *buf) +{ + wininfo_T *wip; + + while (buf->b_wininfo != NULL) + { + wip = buf->b_wininfo; + buf->b_wininfo = wip->wi_next; + free_wininfo(wip); + } +} + +/* + * Free stuff in the buffer for ":bdel" and when wiping out the buffer. + */ + static void +free_buffer_stuff( + buf_T *buf, + int free_options) // free options as well +{ + if (free_options) + { + clear_wininfo(buf); // including window-local options + free_buf_options(buf, TRUE); +#ifdef FEAT_SPELL + ga_clear(&buf->b_s.b_langp); +#endif + } +#ifdef FEAT_EVAL + { + varnumber_T tick = CHANGEDTICK(buf); + + vars_clear(&buf->b_vars->dv_hashtab); // free all buffer variables + hash_init(&buf->b_vars->dv_hashtab); + init_changedtick(buf); + CHANGEDTICK(buf) = tick; + remove_listeners(buf); + } +#endif + uc_clear(&buf->b_ucmds); // clear local user commands +#ifdef FEAT_SIGNS + buf_delete_signs(buf, (char_u *)"*"); // delete any signs +#endif +#ifdef FEAT_NETBEANS_INTG + netbeans_file_killed(buf); +#endif +#ifdef FEAT_PROP_POPUP + ga_clear_strings(&buf->b_textprop_text); +#endif + map_clear_mode(buf, MAP_ALL_MODES, TRUE, FALSE); // clear local mappings + map_clear_mode(buf, MAP_ALL_MODES, TRUE, TRUE); // clear local abbrevs + VIM_CLEAR(buf->b_start_fenc); +} + +/* + * Free one wininfo_T. + */ + void +free_wininfo(wininfo_T *wip) +{ + if (wip->wi_optset) + { + clear_winopt(&wip->wi_opt); +#ifdef FEAT_FOLDING + deleteFoldRecurse(&wip->wi_folds); +#endif + } + vim_free(wip); +} + +/* + * Go to another buffer. Handles the result of the ATTENTION dialog. + */ + void +goto_buffer( + exarg_T *eap, + int start, + int dir, + int count) +{ + bufref_T old_curbuf; + int save_sea = swap_exists_action; + + set_bufref(&old_curbuf, curbuf); + + if (swap_exists_action == SEA_NONE) + swap_exists_action = SEA_DIALOG; + (void)do_buffer(*eap->cmd == 's' ? DOBUF_SPLIT : DOBUF_GOTO, + start, dir, count, eap->forceit); + if (swap_exists_action == SEA_QUIT && *eap->cmd == 's') + { +#if defined(FEAT_EVAL) + cleanup_T cs; + + // Reset the error/interrupt/exception state here so that + // aborting() returns FALSE when closing a window. + enter_cleanup(&cs); +#endif + + // Quitting means closing the split window, nothing else. + win_close(curwin, TRUE); + swap_exists_action = save_sea; + swap_exists_did_quit = TRUE; + +#if defined(FEAT_EVAL) + // Restore the error/interrupt/exception state if not discarded by a + // new aborting error, interrupt, or uncaught exception. + leave_cleanup(&cs); +#endif + } + else + handle_swap_exists(&old_curbuf); +} + +/* + * Handle the situation of swap_exists_action being set. + * It is allowed for "old_curbuf" to be NULL or invalid. + */ + void +handle_swap_exists(bufref_T *old_curbuf) +{ +#if defined(FEAT_EVAL) + cleanup_T cs; +#endif +#ifdef FEAT_SYN_HL + long old_tw = curbuf->b_p_tw; +#endif + buf_T *buf; + + if (swap_exists_action == SEA_QUIT) + { +#if defined(FEAT_EVAL) + // Reset the error/interrupt/exception state here so that + // aborting() returns FALSE when closing a buffer. + enter_cleanup(&cs); +#endif + + // User selected Quit at ATTENTION prompt. Go back to previous + // buffer. If that buffer is gone or the same as the current one, + // open a new, empty buffer. + swap_exists_action = SEA_NONE; // don't want it again + swap_exists_did_quit = TRUE; + close_buffer(curwin, curbuf, DOBUF_UNLOAD, FALSE, FALSE); + if (old_curbuf == NULL || !bufref_valid(old_curbuf) + || old_curbuf->br_buf == curbuf) + { + // Block autocommands here because curwin->w_buffer is NULL. + block_autocmds(); + buf = buflist_new(NULL, NULL, 1L, BLN_CURBUF | BLN_LISTED); + unblock_autocmds(); + } + else + buf = old_curbuf->br_buf; + if (buf != NULL) + { + int old_msg_silent = msg_silent; + + if (shortmess(SHM_FILEINFO)) + msg_silent = 1; // prevent fileinfo message + enter_buffer(buf); + // restore msg_silent, so that the command line will be shown + msg_silent = old_msg_silent; + +#ifdef FEAT_SYN_HL + if (old_tw != curbuf->b_p_tw) + check_colorcolumn(curwin); +#endif + } + // If "old_curbuf" is NULL we are in big trouble here... + +#if defined(FEAT_EVAL) + // Restore the error/interrupt/exception state if not discarded by a + // new aborting error, interrupt, or uncaught exception. + leave_cleanup(&cs); +#endif + } + else if (swap_exists_action == SEA_RECOVER) + { +#if defined(FEAT_EVAL) + // Reset the error/interrupt/exception state here so that + // aborting() returns FALSE when closing a buffer. + enter_cleanup(&cs); +#endif + + // User selected Recover at ATTENTION prompt. + msg_scroll = TRUE; + ml_recover(FALSE); + msg_puts("\n"); // don't overwrite the last message + cmdline_row = msg_row; + do_modelines(0); + +#if defined(FEAT_EVAL) + // Restore the error/interrupt/exception state if not discarded by a + // new aborting error, interrupt, or uncaught exception. + leave_cleanup(&cs); +#endif + } + swap_exists_action = SEA_NONE; +} + +/* + * Make the current buffer empty. + * Used when it is wiped out and it's the last buffer. + */ + static int +empty_curbuf( + int close_others, + int forceit, + int action) +{ + int retval; + buf_T *buf = curbuf; + bufref_T bufref; + + if (action == DOBUF_UNLOAD) + { + emsg(_(e_cannot_unload_last_buffer)); + return FAIL; + } + + set_bufref(&bufref, buf); + if (close_others) + // Close any other windows on this buffer, then make it empty. + close_windows(buf, TRUE); + + setpcmark(); + retval = do_ecmd(0, NULL, NULL, NULL, ECMD_ONE, + forceit ? ECMD_FORCEIT : 0, curwin); + + // do_ecmd() may create a new buffer, then we have to delete + // the old one. But do_ecmd() may have done that already, check + // if the buffer still exists. + if (buf != curbuf && bufref_valid(&bufref) && buf->b_nwindows == 0) + close_buffer(NULL, buf, action, FALSE, FALSE); + if (!close_others) + need_fileinfo = FALSE; + return retval; +} + +/* + * Implementation of the commands for the buffer list. + * + * action == DOBUF_GOTO go to specified buffer + * action == DOBUF_SPLIT split window and go to specified buffer + * action == DOBUF_UNLOAD unload specified buffer(s) + * action == DOBUF_DEL delete specified buffer(s) from buffer list + * action == DOBUF_WIPE delete specified buffer(s) really + * action == DOBUF_WIPE_REUSE idem, and add number to "buf_reuse" + * + * start == DOBUF_CURRENT go to "count" buffer from current buffer + * start == DOBUF_FIRST go to "count" buffer from first buffer + * start == DOBUF_LAST go to "count" buffer from last buffer + * start == DOBUF_MOD go to "count" modified buffer from current buffer + * + * Return FAIL or OK. + */ + static int +do_buffer_ext( + int action, + int start, + int dir, // FORWARD or BACKWARD + int count, // buffer number or number of buffers + int flags) // DOBUF_FORCEIT etc. +{ + buf_T *buf; + buf_T *bp; + int unload = (action == DOBUF_UNLOAD || action == DOBUF_DEL + || action == DOBUF_WIPE || action == DOBUF_WIPE_REUSE); + + switch (start) + { + case DOBUF_FIRST: buf = firstbuf; break; + case DOBUF_LAST: buf = lastbuf; break; + default: buf = curbuf; break; + } + if (start == DOBUF_MOD) // find next modified buffer + { + while (count-- > 0) + { + do + { + buf = buf->b_next; + if (buf == NULL) + buf = firstbuf; + } + while (buf != curbuf && !bufIsChanged(buf)); + } + if (!bufIsChanged(buf)) + { + emsg(_(e_no_modified_buffer_found)); + return FAIL; + } + } + else if (start == DOBUF_FIRST && count) // find specified buffer number + { + while (buf != NULL && buf->b_fnum != count) + buf = buf->b_next; + } + else + { + bp = NULL; + while (count > 0 || (!unload && !buf->b_p_bl && bp != buf)) + { + // remember the buffer where we start, we come back there when all + // buffers are unlisted. + if (bp == NULL) + bp = buf; + if (dir == FORWARD) + { + buf = buf->b_next; + if (buf == NULL) + buf = firstbuf; + } + else + { + buf = buf->b_prev; + if (buf == NULL) + buf = lastbuf; + } + // don't count unlisted buffers + if (unload || buf->b_p_bl) + { + --count; + bp = NULL; // use this buffer as new starting point + } + if (bp == buf) + { + // back where we started, didn't find anything. + emsg(_(e_there_is_no_listed_buffer)); + return FAIL; + } + } + } + + if (buf == NULL) // could not find it + { + if (start == DOBUF_FIRST) + { + // don't warn when deleting + if (!unload) + semsg(_(e_buffer_nr_does_not_exist), count); + } + else if (dir == FORWARD) + emsg(_(e_cannot_go_beyond_last_buffer)); + else + emsg(_(e_cannot_go_before_first_buffer)); + return FAIL; + } +#ifdef FEAT_PROP_POPUP + if ((flags & DOBUF_NOPOPUP) && bt_popup(buf) && !bt_terminal(buf)) + return OK; +#endif + if ((action == DOBUF_GOTO || action == DOBUF_SPLIT) + && (buf->b_flags & BF_DUMMY)) + { + // disallow navigating to the dummy buffer + semsg(_(e_buffer_nr_does_not_exist), count); + return FAIL; + } + +#ifdef FEAT_GUI + need_mouse_correct = TRUE; +#endif + + /* + * delete buffer "buf" from memory and/or the list + */ + if (unload) + { + int forward; + bufref_T bufref; + + if (!can_unload_buffer(buf)) + return FAIL; + + set_bufref(&bufref, buf); + + // When unloading or deleting a buffer that's already unloaded and + // unlisted: fail silently. + if (action != DOBUF_WIPE && action != DOBUF_WIPE_REUSE + && buf->b_ml.ml_mfp == NULL && !buf->b_p_bl) + return FAIL; + + if ((flags & DOBUF_FORCEIT) == 0 && bufIsChanged(buf)) + { +#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG) + if ((p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) && p_write) + { +# ifdef FEAT_TERMINAL + if (term_job_running(buf->b_term)) + { + if (term_confirm_stop(buf) == FAIL) + return FAIL; + } + else +# endif + { + dialog_changed(buf, FALSE); + if (!bufref_valid(&bufref)) + // Autocommand deleted buffer, oops! It's not changed + // now. + return FAIL; + // If it's still changed fail silently, the dialog already + // mentioned why it fails. + if (bufIsChanged(buf)) + return FAIL; + } + } + else +#endif + { + no_write_message_buf(buf); + return FAIL; + } + } + + // When closing the current buffer stop Visual mode. + if (buf == curbuf && VIsual_active) + end_visual_mode(); + + // If deleting the last (listed) buffer, make it empty. + // The last (listed) buffer cannot be unloaded. + FOR_ALL_BUFFERS(bp) + if (bp->b_p_bl && bp != buf) + break; + if (bp == NULL && buf == curbuf) + return empty_curbuf(TRUE, (flags & DOBUF_FORCEIT), action); + + // If the deleted buffer is the current one, close the current window + // (unless it's the only window). Repeat this so long as we end up in + // a window with this buffer. + while (buf == curbuf + && !(curwin->w_closing || curwin->w_buffer->b_locked > 0) + && (!ONE_WINDOW || first_tabpage->tp_next != NULL)) + { + if (win_close(curwin, FALSE) == FAIL) + break; + } + + // If the buffer to be deleted is not the current one, delete it here. + if (buf != curbuf) + { + close_windows(buf, FALSE); + if (buf != curbuf && bufref_valid(&bufref) && buf->b_nwindows <= 0) + close_buffer(NULL, buf, action, FALSE, FALSE); + return OK; + } + + /* + * Deleting the current buffer: Need to find another buffer to go to. + * There should be another, otherwise it would have been handled + * above. However, autocommands may have deleted all buffers. + * First use au_new_curbuf.br_buf, if it is valid. + * Then prefer the buffer we most recently visited. + * Else try to find one that is loaded, after the current buffer, + * then before the current buffer. + * Finally use any buffer. + */ + buf = NULL; // selected buffer + bp = NULL; // used when no loaded buffer found + if (au_new_curbuf.br_buf != NULL && bufref_valid(&au_new_curbuf)) + buf = au_new_curbuf.br_buf; + else if (curwin->w_jumplistlen > 0) + { + int jumpidx; + + jumpidx = curwin->w_jumplistidx - 1; + if (jumpidx < 0) + jumpidx = curwin->w_jumplistlen - 1; + + forward = jumpidx; + while (jumpidx != curwin->w_jumplistidx) + { + buf = buflist_findnr(curwin->w_jumplist[jumpidx].fmark.fnum); + if (buf != NULL) + { + // Skip current and unlisted bufs. Also skip a quickfix + // buffer, it might be deleted soon. + if (buf == curbuf || !buf->b_p_bl || bt_quickfix(buf)) + buf = NULL; + else if (buf->b_ml.ml_mfp == NULL) + { + // skip unloaded buf, but may keep it for later + if (bp == NULL) + bp = buf; + buf = NULL; + } + } + if (buf != NULL) // found a valid buffer: stop searching + break; + // advance to older entry in jump list + if (!jumpidx && curwin->w_jumplistidx == curwin->w_jumplistlen) + break; + if (--jumpidx < 0) + jumpidx = curwin->w_jumplistlen - 1; + if (jumpidx == forward) // List exhausted for sure + break; + } + } + + if (buf == NULL) // No previous buffer, Try 2'nd approach + { + forward = TRUE; + buf = curbuf->b_next; + for (;;) + { + if (buf == NULL) + { + if (!forward) // tried both directions + break; + buf = curbuf->b_prev; + forward = FALSE; + continue; + } + // in non-help buffer, try to skip help buffers, and vv + if (buf->b_help == curbuf->b_help && buf->b_p_bl + && !bt_quickfix(buf)) + { + if (buf->b_ml.ml_mfp != NULL) // found loaded buffer + break; + if (bp == NULL) // remember unloaded buf for later + bp = buf; + } + if (forward) + buf = buf->b_next; + else + buf = buf->b_prev; + } + } + if (buf == NULL) // No loaded buffer, use unloaded one + buf = bp; + if (buf == NULL) // No loaded buffer, find listed one + { + FOR_ALL_BUFFERS(buf) + if (buf->b_p_bl && buf != curbuf && !bt_quickfix(buf)) + break; + } + if (buf == NULL) // Still no buffer, just take one + { + if (curbuf->b_next != NULL) + buf = curbuf->b_next; + else + buf = curbuf->b_prev; + if (bt_quickfix(buf)) + buf = NULL; + } + } + + if (buf == NULL) + { + // Autocommands must have wiped out all other buffers. Only option + // now is to make the current buffer empty. + return empty_curbuf(FALSE, (flags & DOBUF_FORCEIT), action); + } + + /* + * make "buf" the current buffer + */ + if (action == DOBUF_SPLIT) // split window first + { + // If 'switchbuf' contains "useopen": jump to first window containing + // "buf" if one exists + if ((swb_flags & SWB_USEOPEN) && buf_jump_open_win(buf)) + return OK; + // If 'switchbuf' contains "usetab": jump to first window in any tab + // page containing "buf" if one exists + if ((swb_flags & SWB_USETAB) && buf_jump_open_tab(buf)) + return OK; + if (win_split(0, 0) == FAIL) + return FAIL; + } + + // go to current buffer - nothing to do + if (buf == curbuf) + return OK; + + // Check if the current buffer may be abandoned. + if (action == DOBUF_GOTO && !can_abandon(curbuf, (flags & DOBUF_FORCEIT))) + { +#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG) + if ((p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) && p_write) + { +# ifdef FEAT_TERMINAL + if (term_job_running(curbuf->b_term)) + { + if (term_confirm_stop(curbuf) == FAIL) + return FAIL; + // Manually kill the terminal here because this command will + // hide it otherwise. + free_terminal(curbuf); + } + else +# endif + { + bufref_T bufref; + + set_bufref(&bufref, buf); + dialog_changed(curbuf, FALSE); + if (!bufref_valid(&bufref)) + // Autocommand deleted buffer, oops! + return FAIL; + + if (bufIsChanged(curbuf)) + { + no_write_message(); + return FAIL; + } + } + } + else +#endif + { + no_write_message(); + return FAIL; + } + } + + // Go to the other buffer. + set_curbuf(buf, action); + + if (action == DOBUF_SPLIT) + RESET_BINDING(curwin); // reset 'scrollbind' and 'cursorbind' + +#if defined(FEAT_EVAL) + if (aborting()) // autocmds may abort script processing + return FAIL; +#endif + + return OK; +} + + int +do_buffer( + int action, + int start, + int dir, // FORWARD or BACKWARD + int count, // buffer number or number of buffers + int forceit) // TRUE when using ! +{ + return do_buffer_ext(action, start, dir, count, + forceit ? DOBUF_FORCEIT : 0); +} + +/* + * do_bufdel() - delete or unload buffer(s) + * + * addr_count == 0: ":bdel" - delete current buffer + * addr_count == 1: ":N bdel" or ":bdel N [N ..]" - first delete + * buffer "end_bnr", then any other arguments. + * addr_count == 2: ":N,N bdel" - delete buffers in range + * + * command can be DOBUF_UNLOAD (":bunload"), DOBUF_WIPE (":bwipeout") or + * DOBUF_DEL (":bdel") + * + * Returns error message or NULL + */ + char * +do_bufdel( + int command, + char_u *arg, // pointer to extra arguments + int addr_count, + int start_bnr, // first buffer number in a range + int end_bnr, // buffer nr or last buffer nr in a range + int forceit) +{ + int do_current = 0; // delete current buffer? + int deleted = 0; // number of buffers deleted + char *errormsg = NULL; // return value + int bnr; // buffer number + char_u *p; + + if (addr_count == 0) + { + (void)do_buffer(command, DOBUF_CURRENT, FORWARD, 0, forceit); + } + else + { + if (addr_count == 2) + { + if (*arg) // both range and argument is not allowed + return ex_errmsg(e_trailing_characters_str, arg); + bnr = start_bnr; + } + else // addr_count == 1 + bnr = end_bnr; + + for ( ;!got_int; ui_breakcheck()) + { + // Delete the current buffer last, otherwise when the + // current buffer is deleted, the next buffer becomes + // the current one and will be loaded, which may then + // also be deleted, etc. + if (bnr == curbuf->b_fnum) + do_current = bnr; + else if (do_buffer_ext(command, DOBUF_FIRST, FORWARD, bnr, + DOBUF_NOPOPUP | (forceit ? DOBUF_FORCEIT : 0)) == OK) + ++deleted; + + // find next buffer number to delete/unload + if (addr_count == 2) + { + if (++bnr > end_bnr) + break; + } + else // addr_count == 1 + { + arg = skipwhite(arg); + if (*arg == NUL) + break; + if (!VIM_ISDIGIT(*arg)) + { + p = skiptowhite_esc(arg); + bnr = buflist_findpat(arg, p, + command == DOBUF_WIPE || command == DOBUF_WIPE_REUSE, + FALSE, FALSE); + if (bnr < 0) // failed + break; + arg = p; + } + else + bnr = getdigits(&arg); + } + } + if (!got_int && do_current && do_buffer(command, DOBUF_FIRST, + FORWARD, do_current, forceit) == OK) + ++deleted; + + if (deleted == 0) + { + if (command == DOBUF_UNLOAD) + STRCPY(IObuff, _(e_no_buffers_were_unloaded)); + else if (command == DOBUF_DEL) + STRCPY(IObuff, _(e_no_buffers_were_deleted)); + else + STRCPY(IObuff, _(e_no_buffers_were_wiped_out)); + errormsg = (char *)IObuff; + } + else if (deleted >= p_report) + { + if (command == DOBUF_UNLOAD) + smsg(NGETTEXT("%d buffer unloaded", + "%d buffers unloaded", deleted), deleted); + else if (command == DOBUF_DEL) + smsg(NGETTEXT("%d buffer deleted", + "%d buffers deleted", deleted), deleted); + else + smsg(NGETTEXT("%d buffer wiped out", + "%d buffers wiped out", deleted), deleted); + } + } + + return errormsg; +} + +/* + * Set current buffer to "buf". Executes autocommands and closes current + * buffer. "action" tells how to close the current buffer: + * DOBUF_GOTO free or hide it + * DOBUF_SPLIT nothing + * DOBUF_UNLOAD unload it + * DOBUF_DEL delete it + * DOBUF_WIPE wipe it out + * DOBUF_WIPE_REUSE wipe it out and add to "buf_reuse" + */ + void +set_curbuf(buf_T *buf, int action) +{ + buf_T *prevbuf; + int unload = (action == DOBUF_UNLOAD || action == DOBUF_DEL + || action == DOBUF_WIPE || action == DOBUF_WIPE_REUSE); +#ifdef FEAT_SYN_HL + long old_tw = curbuf->b_p_tw; +#endif + bufref_T newbufref; + bufref_T prevbufref; + int valid; + + setpcmark(); + if ((cmdmod.cmod_flags & CMOD_KEEPALT) == 0) + curwin->w_alt_fnum = curbuf->b_fnum; // remember alternate file + buflist_altfpos(curwin); // remember curpos + + // Don't restart Select mode after switching to another buffer. + VIsual_reselect = FALSE; + + // close_windows() or apply_autocmds() may change curbuf and wipe out "buf" + prevbuf = curbuf; + set_bufref(&prevbufref, prevbuf); + set_bufref(&newbufref, buf); + + // Autocommands may delete the current buffer and/or the buffer we want to + // go to. In those cases don't close the buffer. + if (!apply_autocmds(EVENT_BUFLEAVE, NULL, NULL, FALSE, curbuf) + || (bufref_valid(&prevbufref) + && bufref_valid(&newbufref) +#ifdef FEAT_EVAL + && !aborting() +#endif + )) + { +#ifdef FEAT_SYN_HL + if (prevbuf == curwin->w_buffer) + reset_synblock(curwin); +#endif + if (unload) + close_windows(prevbuf, FALSE); +#if defined(FEAT_EVAL) + if (bufref_valid(&prevbufref) && !aborting()) +#else + if (bufref_valid(&prevbufref)) +#endif + { + win_T *previouswin = curwin; + + // Do not sync when in Insert mode and the buffer is open in + // another window, might be a timer doing something in another + // window. + if (prevbuf == curbuf + && ((State & MODE_INSERT) == 0 || curbuf->b_nwindows <= 1)) + u_sync(FALSE); + close_buffer(prevbuf == curwin->w_buffer ? curwin : NULL, prevbuf, + unload ? action : (action == DOBUF_GOTO + && !buf_hide(prevbuf) + && !bufIsChanged(prevbuf)) ? DOBUF_UNLOAD : 0, + FALSE, FALSE); + if (curwin != previouswin && win_valid(previouswin)) + // autocommands changed curwin, Grr! + curwin = previouswin; + } + } + // An autocommand may have deleted "buf", already entered it (e.g., when + // it did ":bunload") or aborted the script processing. + // If curwin->w_buffer is null, enter_buffer() will make it valid again + valid = buf_valid(buf); + if ((valid && buf != curbuf +#ifdef FEAT_EVAL + && !aborting() +#endif + ) || curwin->w_buffer == NULL) + { + // If the buffer is not valid but curwin->w_buffer is NULL we must + // enter some buffer. Using the last one is hopefully OK. + if (!valid) + enter_buffer(lastbuf); + else + enter_buffer(buf); +#ifdef FEAT_SYN_HL + if (old_tw != curbuf->b_p_tw) + check_colorcolumn(curwin); +#endif + } +} + +/* + * Enter a new current buffer. + * Old curbuf must have been abandoned already! This also means "curbuf" may + * be pointing to freed memory. + */ + static void +enter_buffer(buf_T *buf) +{ + // when closing the current buffer stop Visual mode + if (VIsual_active +#if defined(EXITFREE) + && !entered_free_all_mem +#endif + ) + end_visual_mode(); + + // Get the buffer in the current window. + curwin->w_buffer = buf; + curbuf = buf; + ++curbuf->b_nwindows; + + // Copy buffer and window local option values. Not for a help buffer. + buf_copy_options(buf, BCO_ENTER | BCO_NOHELP); + if (!buf->b_help) + get_winopts(buf); +#ifdef FEAT_FOLDING + else + // Remove all folds in the window. + clearFolding(curwin); + foldUpdateAll(curwin); // update folds (later). +#endif + +#ifdef FEAT_DIFF + if (curwin->w_p_diff) + diff_buf_add(curbuf); +#endif + +#ifdef FEAT_SYN_HL + curwin->w_s = &(curbuf->b_s); +#endif + + // Cursor on first line by default. + curwin->w_cursor.lnum = 1; + curwin->w_cursor.col = 0; + curwin->w_cursor.coladd = 0; + curwin->w_set_curswant = TRUE; + curwin->w_topline_was_set = FALSE; + + // mark cursor position as being invalid + curwin->w_valid = 0; + + buflist_setfpos(curbuf, curwin, curbuf->b_last_cursor.lnum, + curbuf->b_last_cursor.col, TRUE); + + // Make sure the buffer is loaded. + if (curbuf->b_ml.ml_mfp == NULL) // need to load the file + { + // If there is no filetype, allow for detecting one. Esp. useful for + // ":ball" used in an autocommand. If there already is a filetype we + // might prefer to keep it. + if (*curbuf->b_p_ft == NUL) + did_filetype = FALSE; + + open_buffer(FALSE, NULL, 0); + } + else + { + if (!msg_silent && !shortmess(SHM_FILEINFO)) + need_fileinfo = TRUE; // display file info after redraw + + // check if file changed + (void)buf_check_timestamp(curbuf, FALSE); + + curwin->w_topline = 1; +#ifdef FEAT_DIFF + curwin->w_topfill = 0; +#endif + apply_autocmds(EVENT_BUFENTER, NULL, NULL, FALSE, curbuf); + apply_autocmds(EVENT_BUFWINENTER, NULL, NULL, FALSE, curbuf); + } + + // If autocommands did not change the cursor position, restore cursor lnum + // and possibly cursor col. + if (curwin->w_cursor.lnum == 1 && inindent(0)) + buflist_getfpos(); + + check_arg_idx(curwin); // check for valid arg_idx + maketitle(); + // when autocmds didn't change it + if (curwin->w_topline == 1 && !curwin->w_topline_was_set) + scroll_cursor_halfway(FALSE, FALSE); // redisplay at correct position + +#ifdef FEAT_NETBEANS_INTG + // Send fileOpened event because we've changed buffers. + netbeans_file_activated(curbuf); +#endif + + // Change directories when the 'acd' option is set. + DO_AUTOCHDIR; + +#ifdef FEAT_KEYMAP + if (curbuf->b_kmap_state & KEYMAP_INIT) + (void)keymap_init(); +#endif +#ifdef FEAT_SPELL + // May need to set the spell language. Can only do this after the buffer + // has been properly setup. + if (!curbuf->b_help && curwin->w_p_spell && *curwin->w_s->b_p_spl != NUL) + (void)parse_spelllang(curwin); +#endif +#ifdef FEAT_VIMINFO + curbuf->b_last_used = vim_time(); +#endif + + redraw_later(UPD_NOT_VALID); +} + +#if defined(FEAT_AUTOCHDIR) || defined(PROTO) +/* + * Change to the directory of the current buffer. + * Don't do this while still starting up. + */ + void +do_autochdir(void) +{ + if ((starting == 0 || test_autochdir) + && curbuf->b_ffname != NULL + && vim_chdirfile(curbuf->b_ffname, "auto") == OK) + { + shorten_fnames(TRUE); + last_chdir_reason = "autochdir"; + } +} +#endif + + static void +no_write_message_buf(buf_T *buf UNUSED) +{ +#ifdef FEAT_TERMINAL + if (term_job_running(buf->b_term)) + emsg(_(e_job_still_running_add_bang_to_end_the_job)); + else +#endif + semsg(_(e_no_write_since_last_change_for_buffer_nr_add_bang_to_override), + buf->b_fnum); +} + + void +no_write_message(void) +{ +#ifdef FEAT_TERMINAL + if (term_job_running(curbuf->b_term)) + emsg(_(e_job_still_running_add_bang_to_end_the_job)); + else +#endif + emsg(_(e_no_write_since_last_change_add_bang_to_override)); +} + + void +no_write_message_nobang(buf_T *buf UNUSED) +{ +#ifdef FEAT_TERMINAL + if (term_job_running(buf->b_term)) + emsg(_(e_job_still_running)); + else +#endif + emsg(_(e_no_write_since_last_change)); +} + +/* + * functions for dealing with the buffer list + */ + +/* + * Return TRUE if the current buffer is empty, unnamed, unmodified and used in + * only one window. That means it can be re-used. + */ + int +curbuf_reusable(void) +{ + return (curbuf != NULL + && curbuf->b_ffname == NULL + && curbuf->b_nwindows <= 1 + && (curbuf->b_ml.ml_mfp == NULL || BUFEMPTY()) + && !bt_quickfix(curbuf) + && !curbufIsChanged()); +} + +/* + * Add a file name to the buffer list. Return a pointer to the buffer. + * If the same file name already exists return a pointer to that buffer. + * If it does not exist, or if fname == NULL, a new entry is created. + * If (flags & BLN_CURBUF) is TRUE, may use current buffer. + * If (flags & BLN_LISTED) is TRUE, add new buffer to buffer list. + * If (flags & BLN_DUMMY) is TRUE, don't count it as a real buffer. + * If (flags & BLN_NEW) is TRUE, don't use an existing buffer. + * If (flags & BLN_NOOPT) is TRUE, don't copy options from the current buffer + * if the buffer already exists. + * If (flags & BLN_REUSE) is TRUE, may use buffer number from "buf_reuse". + * This is the ONLY way to create a new buffer. + */ + buf_T * +buflist_new( + char_u *ffname_arg, // full path of fname or relative + char_u *sfname_arg, // short fname or NULL + linenr_T lnum, // preferred cursor line + int flags) // BLN_ defines +{ + char_u *ffname = ffname_arg; + char_u *sfname = sfname_arg; + buf_T *buf; +#ifdef UNIX + stat_T st; +#endif + + if (top_file_num == 1) + hash_init(&buf_hashtab); + + fname_expand(curbuf, &ffname, &sfname); // will allocate ffname + + /* + * If the file name already exists in the list, update the entry. + */ +#ifdef UNIX + // On Unix we can use inode numbers when the file exists. Works better + // for hard links. + if (sfname == NULL || mch_stat((char *)sfname, &st) < 0) + st.st_dev = (dev_T)-1; +#endif + if (ffname != NULL && !(flags & (BLN_DUMMY | BLN_NEW)) && (buf = +#ifdef UNIX + buflist_findname_stat(ffname, &st) +#else + buflist_findname(ffname) +#endif + ) != NULL) + { + vim_free(ffname); + if (lnum != 0) + buflist_setfpos(buf, (flags & BLN_NOCURWIN) ? NULL : curwin, + lnum, (colnr_T)0, FALSE); + + if ((flags & BLN_NOOPT) == 0) + // copy the options now, if 'cpo' doesn't have 's' and not done + // already + buf_copy_options(buf, 0); + + if ((flags & BLN_LISTED) && !buf->b_p_bl) + { + bufref_T bufref; + + buf->b_p_bl = TRUE; + set_bufref(&bufref, buf); + if (!(flags & BLN_DUMMY)) + { + if (apply_autocmds(EVENT_BUFADD, NULL, NULL, FALSE, buf) + && !bufref_valid(&bufref)) + return NULL; + } + } + return buf; + } + + /* + * If the current buffer has no name and no contents, use the current + * buffer. Otherwise: Need to allocate a new buffer structure. + * + * This is the ONLY place where a new buffer structure is allocated! + * (A spell file buffer is allocated in spell.c, but that's not a normal + * buffer.) + */ + buf = NULL; + if ((flags & BLN_CURBUF) && curbuf_reusable()) + { + buf = curbuf; + // It's like this buffer is deleted. Watch out for autocommands that + // change curbuf! If that happens, allocate a new buffer anyway. + buf_freeall(buf, BFA_WIPE | BFA_DEL); + if (buf != curbuf) // autocommands deleted the buffer! + return NULL; +#ifdef FEAT_EVAL + if (aborting()) // autocmds may abort script processing + { + vim_free(ffname); + return NULL; + } +#endif + } + if (buf != curbuf || curbuf == NULL) + { + buf = ALLOC_CLEAR_ONE(buf_T); + if (buf == NULL) + { + vim_free(ffname); + return NULL; + } +#ifdef FEAT_EVAL + // init b: variables + buf->b_vars = dict_alloc_id(aid_newbuf_bvars); + if (buf->b_vars == NULL) + { + vim_free(ffname); + vim_free(buf); + return NULL; + } + init_var_dict(buf->b_vars, &buf->b_bufvar, VAR_SCOPE); +#endif + init_changedtick(buf); + } + + if (ffname != NULL) + { + buf->b_ffname = ffname; + buf->b_sfname = vim_strsave(sfname); + } + + clear_wininfo(buf); + buf->b_wininfo = ALLOC_CLEAR_ONE(wininfo_T); + + if ((ffname != NULL && (buf->b_ffname == NULL || buf->b_sfname == NULL)) + || buf->b_wininfo == NULL) + { + if (buf->b_sfname != buf->b_ffname) + VIM_CLEAR(buf->b_sfname); + else + buf->b_sfname = NULL; + VIM_CLEAR(buf->b_ffname); + if (buf != curbuf) + free_buffer(buf); + return NULL; + } + + if (buf == curbuf) + { + free_buffer_stuff(buf, FALSE); // delete local variables et al. + + // Init the options. + buf->b_p_initialized = FALSE; + buf_copy_options(buf, BCO_ENTER); + +#ifdef FEAT_KEYMAP + // need to reload lmaps and set b:keymap_name + curbuf->b_kmap_state |= KEYMAP_INIT; +#endif + } + else + { + // put the new buffer at the end of the buffer list + buf->b_next = NULL; + if (firstbuf == NULL) // buffer list is empty + { + buf->b_prev = NULL; + firstbuf = buf; + } + else // append new buffer at end of list + { + lastbuf->b_next = buf; + buf->b_prev = lastbuf; + } + lastbuf = buf; + + if ((flags & BLN_REUSE) && buf_reuse.ga_len > 0) + { + // Recycle a previously used buffer number. Used for buffers which + // are normally hidden, e.g. in a popup window. Avoids that the + // buffer number grows rapidly. + --buf_reuse.ga_len; + buf->b_fnum = ((int *)buf_reuse.ga_data)[buf_reuse.ga_len]; + + // Move buffer to the right place in the buffer list. + while (buf->b_prev != NULL && buf->b_fnum < buf->b_prev->b_fnum) + { + buf_T *prev = buf->b_prev; + + prev->b_next = buf->b_next; + if (prev->b_next != NULL) + prev->b_next->b_prev = prev; + buf->b_next = prev; + buf->b_prev = prev->b_prev; + if (buf->b_prev != NULL) + buf->b_prev->b_next = buf; + prev->b_prev = buf; + if (lastbuf == buf) + lastbuf = prev; + if (firstbuf == prev) + firstbuf = buf; + } + } + else + buf->b_fnum = top_file_num++; + if (top_file_num < 0) // wrap around (may cause duplicates) + { + emsg(_("W14: Warning: List of file names overflow")); + if (emsg_silent == 0 && !in_assert_fails) + { + out_flush(); + ui_delay(3001L, TRUE); // make sure it is noticed + } + top_file_num = 1; + } + buf_hashtab_add(buf); + + // Always copy the options from the current buffer. + buf_copy_options(buf, BCO_ALWAYS); + } + + buf->b_wininfo->wi_fpos.lnum = lnum; + buf->b_wininfo->wi_win = curwin; + +#ifdef FEAT_SYN_HL + hash_init(&buf->b_s.b_keywtab); + hash_init(&buf->b_s.b_keywtab_ic); +#endif + + buf->b_fname = buf->b_sfname; +#ifdef UNIX + if (st.st_dev == (dev_T)-1) + buf->b_dev_valid = FALSE; + else + { + buf->b_dev_valid = TRUE; + buf->b_dev = st.st_dev; + buf->b_ino = st.st_ino; + } +#endif + buf->b_u_synced = TRUE; + buf->b_flags = BF_CHECK_RO | BF_NEVERLOADED; + if (flags & BLN_DUMMY) + buf->b_flags |= BF_DUMMY; + buf_clear_file(buf); + clrallmarks(buf); // clear marks + fmarks_check_names(buf); // check file marks for this file + buf->b_p_bl = (flags & BLN_LISTED) ? TRUE : FALSE; // init 'buflisted' + if (!(flags & BLN_DUMMY)) + { + bufref_T bufref; + + // Tricky: these autocommands may change the buffer list. They could + // also split the window with re-using the one empty buffer. This may + // result in unexpectedly losing the empty buffer. + set_bufref(&bufref, buf); + if (apply_autocmds(EVENT_BUFNEW, NULL, NULL, FALSE, buf) + && !bufref_valid(&bufref)) + return NULL; + if (flags & BLN_LISTED) + { + if (apply_autocmds(EVENT_BUFADD, NULL, NULL, FALSE, buf) + && !bufref_valid(&bufref)) + return NULL; + } +#ifdef FEAT_EVAL + if (aborting()) // autocmds may abort script processing + return NULL; +#endif + } + + return buf; +} + +/* + * Free the memory for the options of a buffer. + * If "free_p_ff" is TRUE also free 'fileformat', 'buftype' and + * 'fileencoding'. + */ + void +free_buf_options( + buf_T *buf, + int free_p_ff) +{ + if (free_p_ff) + { + clear_string_option(&buf->b_p_fenc); + clear_string_option(&buf->b_p_ff); + clear_string_option(&buf->b_p_bh); + clear_string_option(&buf->b_p_bt); + } +#ifdef FEAT_FIND_ID + clear_string_option(&buf->b_p_def); + clear_string_option(&buf->b_p_inc); +# ifdef FEAT_EVAL + clear_string_option(&buf->b_p_inex); +# endif +#endif +#if defined(FEAT_EVAL) + clear_string_option(&buf->b_p_inde); + clear_string_option(&buf->b_p_indk); +#endif +#if defined(FEAT_BEVAL) && defined(FEAT_EVAL) + clear_string_option(&buf->b_p_bexpr); +#endif +#if defined(FEAT_CRYPT) + clear_string_option(&buf->b_p_cm); +#endif + clear_string_option(&buf->b_p_fp); +#if defined(FEAT_EVAL) + clear_string_option(&buf->b_p_fex); +#endif +#ifdef FEAT_CRYPT +# ifdef FEAT_SODIUM + if ((buf->b_p_key != NULL) && (*buf->b_p_key != NUL) && + (crypt_get_method_nr(buf) == CRYPT_M_SOD)) + crypt_sodium_munlock(buf->b_p_key, STRLEN(buf->b_p_key)); +# endif + clear_string_option(&buf->b_p_key); +#endif + clear_string_option(&buf->b_p_kp); + clear_string_option(&buf->b_p_mps); + clear_string_option(&buf->b_p_fo); + clear_string_option(&buf->b_p_flp); + clear_string_option(&buf->b_p_isk); +#ifdef FEAT_VARTABS + clear_string_option(&buf->b_p_vsts); + vim_free(buf->b_p_vsts_nopaste); + buf->b_p_vsts_nopaste = NULL; + VIM_CLEAR(buf->b_p_vsts_array); + clear_string_option(&buf->b_p_vts); + VIM_CLEAR(buf->b_p_vts_array); +#endif +#ifdef FEAT_KEYMAP + clear_string_option(&buf->b_p_keymap); + keymap_clear(&buf->b_kmap_ga); + ga_clear(&buf->b_kmap_ga); +#endif + clear_string_option(&buf->b_p_com); +#ifdef FEAT_FOLDING + clear_string_option(&buf->b_p_cms); +#endif + clear_string_option(&buf->b_p_nf); +#ifdef FEAT_SYN_HL + clear_string_option(&buf->b_p_syn); + clear_string_option(&buf->b_s.b_syn_isk); +#endif +#ifdef FEAT_SPELL + clear_string_option(&buf->b_s.b_p_spc); + clear_string_option(&buf->b_s.b_p_spf); + vim_regfree(buf->b_s.b_cap_prog); + buf->b_s.b_cap_prog = NULL; + clear_string_option(&buf->b_s.b_p_spl); + clear_string_option(&buf->b_s.b_p_spo); +#endif + clear_string_option(&buf->b_p_sua); + clear_string_option(&buf->b_p_ft); + clear_string_option(&buf->b_p_cink); + clear_string_option(&buf->b_p_cino); + clear_string_option(&buf->b_p_lop); + clear_string_option(&buf->b_p_cinsd); + clear_string_option(&buf->b_p_cinw); + clear_string_option(&buf->b_p_cpt); +#ifdef FEAT_COMPL_FUNC + clear_string_option(&buf->b_p_cfu); + free_callback(&buf->b_cfu_cb); + clear_string_option(&buf->b_p_ofu); + free_callback(&buf->b_ofu_cb); + clear_string_option(&buf->b_p_tsrfu); + free_callback(&buf->b_tsrfu_cb); +#endif +#ifdef FEAT_QUICKFIX + clear_string_option(&buf->b_p_gp); + clear_string_option(&buf->b_p_mp); + clear_string_option(&buf->b_p_efm); +#endif + clear_string_option(&buf->b_p_ep); + clear_string_option(&buf->b_p_path); + clear_string_option(&buf->b_p_tags); + clear_string_option(&buf->b_p_tc); +#ifdef FEAT_EVAL + clear_string_option(&buf->b_p_tfu); + free_callback(&buf->b_tfu_cb); +#endif + clear_string_option(&buf->b_p_dict); + clear_string_option(&buf->b_p_tsr); + clear_string_option(&buf->b_p_qe); + buf->b_p_ar = -1; + buf->b_p_ul = NO_LOCAL_UNDOLEVEL; + clear_string_option(&buf->b_p_lw); + clear_string_option(&buf->b_p_bkc); + clear_string_option(&buf->b_p_menc); +} + +/* + * Get alternate file "n". + * Set linenr to "lnum" or altfpos.lnum if "lnum" == 0. + * Also set cursor column to altfpos.col if 'startofline' is not set. + * if (options & GETF_SETMARK) call setpcmark() + * if (options & GETF_ALT) we are jumping to an alternate file. + * if (options & GETF_SWITCH) respect 'switchbuf' settings when jumping + * + * Return FAIL for failure, OK for success. + */ + int +buflist_getfile( + int n, + linenr_T lnum, + int options, + int forceit) +{ + buf_T *buf; + win_T *wp = NULL; + pos_T *fpos; + colnr_T col; + + buf = buflist_findnr(n); + if (buf == NULL) + { + if ((options & GETF_ALT) && n == 0) + emsg(_(e_no_alternate_file)); + else + semsg(_(e_buffer_nr_not_found), n); + return FAIL; + } + + // if alternate file is the current buffer, nothing to do + if (buf == curbuf) + return OK; + + if (text_or_buf_locked()) + return FAIL; + + // altfpos may be changed by getfile(), get it now + if (lnum == 0) + { + fpos = buflist_findfpos(buf); + lnum = fpos->lnum; + col = fpos->col; + } + else + col = 0; + + if (options & GETF_SWITCH) + { + // If 'switchbuf' contains "useopen": jump to first window containing + // "buf" if one exists + if (swb_flags & SWB_USEOPEN) + wp = buf_jump_open_win(buf); + + // If 'switchbuf' contains "usetab": jump to first window in any tab + // page containing "buf" if one exists + if (wp == NULL && (swb_flags & SWB_USETAB)) + wp = buf_jump_open_tab(buf); + + // If 'switchbuf' contains "split", "vsplit" or "newtab" and the + // current buffer isn't empty: open new tab or window + if (wp == NULL && (swb_flags & (SWB_VSPLIT | SWB_SPLIT | SWB_NEWTAB)) + && !BUFEMPTY()) + { + if (swb_flags & SWB_NEWTAB) + tabpage_new(); + else if (win_split(0, (swb_flags & SWB_VSPLIT) ? WSP_VERT : 0) + == FAIL) + return FAIL; + RESET_BINDING(curwin); + } + } + + ++RedrawingDisabled; + if (GETFILE_SUCCESS(getfile(buf->b_fnum, NULL, NULL, + (options & GETF_SETMARK), lnum, forceit))) + { + --RedrawingDisabled; + + // cursor is at to BOL and w_cursor.lnum is checked due to getfile() + if (!p_sol && col != 0) + { + curwin->w_cursor.col = col; + check_cursor_col(); + curwin->w_cursor.coladd = 0; + curwin->w_set_curswant = TRUE; + } + return OK; + } + --RedrawingDisabled; + return FAIL; +} + +/* + * go to the last know line number for the current buffer + */ + static void +buflist_getfpos(void) +{ + pos_T *fpos; + + fpos = buflist_findfpos(curbuf); + + curwin->w_cursor.lnum = fpos->lnum; + check_cursor_lnum(); + + if (p_sol) + curwin->w_cursor.col = 0; + else + { + curwin->w_cursor.col = fpos->col; + check_cursor_col(); + curwin->w_cursor.coladd = 0; + curwin->w_set_curswant = TRUE; + } +} + +#if defined(FEAT_QUICKFIX) || defined(FEAT_EVAL) || defined(FEAT_SPELL) || defined(PROTO) +/* + * Find file in buffer list by name (it has to be for the current window). + * Returns NULL if not found. + */ + buf_T * +buflist_findname_exp(char_u *fname) +{ + char_u *ffname; + buf_T *buf = NULL; + + // First make the name into a full path name + ffname = FullName_save(fname, +#ifdef UNIX + TRUE // force expansion, get rid of symbolic links +#else + FALSE +#endif + ); + if (ffname != NULL) + { + buf = buflist_findname(ffname); + vim_free(ffname); + } + return buf; +} +#endif + +/* + * Find file in buffer list by name (it has to be for the current window). + * "ffname" must have a full path. + * Skips dummy buffers. + * Returns NULL if not found. + */ + buf_T * +buflist_findname(char_u *ffname) +{ +#ifdef UNIX + stat_T st; + + if (mch_stat((char *)ffname, &st) < 0) + st.st_dev = (dev_T)-1; + return buflist_findname_stat(ffname, &st); +} + +/* + * Same as buflist_findname(), but pass the stat structure to avoid getting it + * twice for the same file. + * Returns NULL if not found. + */ + static buf_T * +buflist_findname_stat( + char_u *ffname, + stat_T *stp) +{ +#endif + buf_T *buf; + + // Start at the last buffer, expect to find a match sooner. + FOR_ALL_BUFS_FROM_LAST(buf) + if ((buf->b_flags & BF_DUMMY) == 0 && !otherfile_buf(buf, ffname +#ifdef UNIX + , stp +#endif + )) + return buf; + return NULL; +} + +/* + * Find file in buffer list by a regexp pattern. + * Return fnum of the found buffer. + * Return < 0 for error. + */ + int +buflist_findpat( + char_u *pattern, + char_u *pattern_end, // pointer to first char after pattern + int unlisted, // find unlisted buffers + int diffmode UNUSED, // find diff-mode buffers only + int curtab_only) // find buffers in current tab only +{ + buf_T *buf; + int match = -1; + int find_listed; + char_u *pat; + char_u *patend; + int attempt; + char_u *p; + int toggledollar; + + // "%" is current file, "%%" or "#" is alternate file + if ((pattern_end == pattern + 1 && (*pattern == '%' || *pattern == '#')) + || (in_vim9script() && pattern_end == pattern + 2 + && pattern[0] == '%' && pattern[1] == '%')) + { + if (*pattern == '#' || pattern_end == pattern + 2) + match = curwin->w_alt_fnum; + else + match = curbuf->b_fnum; +#ifdef FEAT_DIFF + if (diffmode && !diff_mode_buf(buflist_findnr(match))) + match = -1; +#endif + } + + /* + * Try four ways of matching a listed buffer: + * attempt == 0: without '^' or '$' (at any position) + * attempt == 1: with '^' at start (only at position 0) + * attempt == 2: with '$' at end (only match at end) + * attempt == 3: with '^' at start and '$' at end (only full match) + * Repeat this for finding an unlisted buffer if there was no matching + * listed buffer. + */ + else + { + pat = file_pat_to_reg_pat(pattern, pattern_end, NULL, FALSE); + if (pat == NULL) + return -1; + patend = pat + STRLEN(pat) - 1; + toggledollar = (patend > pat && *patend == '$'); + + // First try finding a listed buffer. If not found and "unlisted" + // is TRUE, try finding an unlisted buffer. + find_listed = TRUE; + for (;;) + { + for (attempt = 0; attempt <= 3; ++attempt) + { + regmatch_T regmatch; + + // may add '^' and '$' + if (toggledollar) + *patend = (attempt < 2) ? NUL : '$'; // add/remove '$' + p = pat; + if (*p == '^' && !(attempt & 1)) // add/remove '^' + ++p; + regmatch.regprog = vim_regcomp(p, magic_isset() ? RE_MAGIC : 0); + + FOR_ALL_BUFS_FROM_LAST(buf) + { + if (regmatch.regprog == NULL) + { + // invalid pattern, possibly after switching engine + vim_free(pat); + return -1; + } + if (buf->b_p_bl == find_listed +#ifdef FEAT_DIFF + && (!diffmode || diff_mode_buf(buf)) +#endif + && buflist_match(®match, buf, FALSE) != NULL) + { + if (curtab_only) + { + // Ignore the match if the buffer is not open in + // the current tab. + win_T *wp; + + FOR_ALL_WINDOWS(wp) + if (wp->w_buffer == buf) + break; + if (wp == NULL) + continue; + } + if (match >= 0) // already found a match + { + match = -2; + break; + } + match = buf->b_fnum; // remember first match + } + } + + vim_regfree(regmatch.regprog); + if (match >= 0) // found one match + break; + } + + // Only search for unlisted buffers if there was no match with + // a listed buffer. + if (!unlisted || !find_listed || match != -1) + break; + find_listed = FALSE; + } + + vim_free(pat); + } + + if (match == -2) + semsg(_(e_more_than_one_match_for_str), pattern); + else if (match < 0) + semsg(_(e_no_matching_buffer_for_str), pattern); + return match; +} + +#ifdef FEAT_VIMINFO +typedef struct { + buf_T *buf; + char_u *match; +} bufmatch_T; +#endif + +/* + * Find all buffer names that match. + * For command line expansion of ":buf" and ":sbuf". + * Return OK if matches found, FAIL otherwise. + */ + int +ExpandBufnames( + char_u *pat, + int *num_file, + char_u ***file, + int options) +{ + int count = 0; + buf_T *buf; + int round; + char_u *p; + int attempt; + char_u *patc = NULL; +#ifdef FEAT_VIMINFO + bufmatch_T *matches = NULL; +#endif + int fuzzy; + fuzmatch_str_T *fuzmatch = NULL; + + *num_file = 0; // return values in case of FAIL + *file = NULL; + +#ifdef FEAT_DIFF + if ((options & BUF_DIFF_FILTER) && !curwin->w_p_diff) + return FAIL; +#endif + + fuzzy = cmdline_fuzzy_complete(pat); + + // Make a copy of "pat" and change "^" to "\(^\|[\/]\)" (if doing regular + // expression matching) + if (!fuzzy) + { + if (*pat == '^') + { + patc = alloc(STRLEN(pat) + 11); + if (patc == NULL) + return FAIL; + STRCPY(patc, "\\(^\\|[\\/]\\)"); + STRCPY(patc + 11, pat + 1); + } + else + patc = pat; + } + + // attempt == 0: try match with '\<', match at start of word + // attempt == 1: try match without '\<', match anywhere + for (attempt = 0; attempt <= (fuzzy ? 0 : 1); ++attempt) + { + regmatch_T regmatch; + int score = 0; + + if (!fuzzy) + { + if (attempt > 0 && patc == pat) + break; // there was no anchor, no need to try again + regmatch.regprog = vim_regcomp(patc + attempt * 11, RE_MAGIC); + } + + // round == 1: Count the matches. + // round == 2: Build the array to keep the matches. + for (round = 1; round <= 2; ++round) + { + count = 0; + FOR_ALL_BUFFERS(buf) + { + if (!buf->b_p_bl) // skip unlisted buffers + continue; +#ifdef FEAT_DIFF + if (options & BUF_DIFF_FILTER) + // Skip buffers not suitable for + // :diffget or :diffput completion. + if (buf == curbuf || !diff_mode_buf(buf)) + continue; +#endif + + if (!fuzzy) + { + if (regmatch.regprog == NULL) + { + // invalid pattern, possibly after recompiling + if (patc != pat) + vim_free(patc); + return FAIL; + } + p = buflist_match(®match, buf, p_wic); + } + else + { + p = NULL; + // first try matching with the short file name + if ((score = fuzzy_match_str(buf->b_sfname, pat)) != 0) + p = buf->b_sfname; + if (p == NULL) + { + // next try matching with the full path file name + if ((score = fuzzy_match_str(buf->b_ffname, pat)) != 0) + p = buf->b_ffname; + } + } + + if (p == NULL) + continue; + + if (round == 1) + { + ++count; + continue; + } + + if (options & WILD_HOME_REPLACE) + p = home_replace_save(buf, p); + else + p = vim_strsave(p); + + if (!fuzzy) + { +#ifdef FEAT_VIMINFO + if (matches != NULL) + { + matches[count].buf = buf; + matches[count].match = p; + count++; + } + else +#endif + (*file)[count++] = p; + } + else + { + fuzmatch[count].idx = count; + fuzmatch[count].str = p; + fuzmatch[count].score = score; + count++; + } + } + if (count == 0) // no match found, break here + break; + if (round == 1) + { + if (!fuzzy) + { + *file = ALLOC_MULT(char_u *, count); + if (*file == NULL) + { + vim_regfree(regmatch.regprog); + if (patc != pat) + vim_free(patc); + return FAIL; + } +#ifdef FEAT_VIMINFO + if (options & WILD_BUFLASTUSED) + matches = ALLOC_MULT(bufmatch_T, count); +#endif + } + else + { + fuzmatch = ALLOC_MULT(fuzmatch_str_T, count); + if (fuzmatch == NULL) + { + *num_file = 0; + *file = NULL; + return FAIL; + } + } + } + } + + if (!fuzzy) + { + vim_regfree(regmatch.regprog); + if (count) // match(es) found, break here + break; + } + } + + if (!fuzzy && patc != pat) + vim_free(patc); + +#ifdef FEAT_VIMINFO + if (!fuzzy) + { + if (matches != NULL) + { + int i; + if (count > 1) + qsort(matches, count, sizeof(bufmatch_T), buf_compare); + // if the current buffer is first in the list, place it at the end + if (matches[0].buf == curbuf) + { + for (i = 1; i < count; i++) + (*file)[i-1] = matches[i].match; + (*file)[count-1] = matches[0].match; + } + else + { + for (i = 0; i < count; i++) + (*file)[i] = matches[i].match; + } + vim_free(matches); + } + } + else + { + if (fuzzymatches_to_strmatches(fuzmatch, file, count, FALSE) == FAIL) + return FAIL; + } +#endif + + *num_file = count; + return (count == 0 ? FAIL : OK); +} + +/* + * Check for a match on the file name for buffer "buf" with regprog "prog". + * Note that rmp->regprog may become NULL when switching regexp engine. + */ + static char_u * +buflist_match( + regmatch_T *rmp, + buf_T *buf, + int ignore_case) // when TRUE ignore case, when FALSE use 'fic' +{ + char_u *match; + + // First try the short file name, then the long file name. + match = fname_match(rmp, buf->b_sfname, ignore_case); + if (match == NULL && rmp->regprog != NULL) + match = fname_match(rmp, buf->b_ffname, ignore_case); + + return match; +} + +/* + * Try matching the regexp in "rmp->regprog" with file name "name". + * Note that rmp->regprog may become NULL when switching regexp engine. + * Return "name" when there is a match, NULL when not. + */ + static char_u * +fname_match( + regmatch_T *rmp, + char_u *name, + int ignore_case) // when TRUE ignore case, when FALSE use 'fic' +{ + char_u *match = NULL; + char_u *p; + + // extra check for valid arguments + if (name == NULL || rmp->regprog == NULL) + return NULL; + + // Ignore case when 'fileignorecase' or the argument is set. + rmp->rm_ic = p_fic || ignore_case; + if (vim_regexec(rmp, name, (colnr_T)0)) + match = name; + else if (rmp->regprog != NULL) + { + // Replace $(HOME) with '~' and try matching again. + p = home_replace_save(NULL, name); + if (p != NULL && vim_regexec(rmp, p, (colnr_T)0)) + match = name; + vim_free(p); + } + + return match; +} + +/* + * Find a file in the buffer list by buffer number. + */ + buf_T * +buflist_findnr(int nr) +{ + char_u key[VIM_SIZEOF_INT * 2 + 1]; + hashitem_T *hi; + + if (nr == 0) + nr = curwin->w_alt_fnum; + sprintf((char *)key, "%x", nr); + hi = hash_find(&buf_hashtab, key); + + if (!HASHITEM_EMPTY(hi)) + return (buf_T *)(hi->hi_key + - ((unsigned)(curbuf->b_key - (char_u *)curbuf))); + return NULL; +} + +/* + * Get name of file 'n' in the buffer list. + * When the file has no name an empty string is returned. + * home_replace() is used to shorten the file name (used for marks). + * Returns a pointer to allocated memory, of NULL when failed. + */ + char_u * +buflist_nr2name( + int n, + int fullname, + int helptail) // for help buffers return tail only +{ + buf_T *buf; + + buf = buflist_findnr(n); + if (buf == NULL) + return NULL; + return home_replace_save(helptail ? buf : NULL, + fullname ? buf->b_ffname : buf->b_fname); +} + +/* + * Set the "lnum" and "col" for the buffer "buf" and the current window. + * When "copy_options" is TRUE save the local window option values. + * When "lnum" is 0 only do the options. + */ + void +buflist_setfpos( + buf_T *buf, + win_T *win, // may be NULL when using :badd + linenr_T lnum, + colnr_T col, + int copy_options) +{ + wininfo_T *wip; + + FOR_ALL_BUF_WININFO(buf, wip) + if (wip->wi_win == win) + break; + if (wip == NULL) + { + // allocate a new entry + wip = ALLOC_CLEAR_ONE(wininfo_T); + if (wip == NULL) + return; + wip->wi_win = win; + if (lnum == 0) // set lnum even when it's 0 + lnum = 1; + } + else + { + // remove the entry from the list + if (wip->wi_prev) + wip->wi_prev->wi_next = wip->wi_next; + else + buf->b_wininfo = wip->wi_next; + if (wip->wi_next) + wip->wi_next->wi_prev = wip->wi_prev; + if (copy_options && wip->wi_optset) + { + clear_winopt(&wip->wi_opt); +#ifdef FEAT_FOLDING + deleteFoldRecurse(&wip->wi_folds); +#endif + } + } + if (lnum != 0) + { + wip->wi_fpos.lnum = lnum; + wip->wi_fpos.col = col; + } + if (win != NULL) + wip->wi_changelistidx = win->w_changelistidx; + if (copy_options && win != NULL) + { + // Save the window-specific option values. + copy_winopt(&win->w_onebuf_opt, &wip->wi_opt); +#ifdef FEAT_FOLDING + wip->wi_fold_manual = win->w_fold_manual; + cloneFoldGrowArray(&win->w_folds, &wip->wi_folds); +#endif + wip->wi_optset = TRUE; + } + + // insert the entry in front of the list + wip->wi_next = buf->b_wininfo; + buf->b_wininfo = wip; + wip->wi_prev = NULL; + if (wip->wi_next) + wip->wi_next->wi_prev = wip; +} + +#ifdef FEAT_DIFF +/* + * Return TRUE when "wip" has 'diff' set and the diff is only for another tab + * page. That's because a diff is local to a tab page. + */ + static int +wininfo_other_tab_diff(wininfo_T *wip) +{ + win_T *wp; + + if (!wip->wi_opt.wo_diff) + return FALSE; + + FOR_ALL_WINDOWS(wp) + // return FALSE when it's a window in the current tab page, thus + // the buffer was in diff mode here + if (wip->wi_win == wp) + return FALSE; + return TRUE; +} +#endif + +/* + * Find info for the current window in buffer "buf". + * If not found, return the info for the most recently used window. + * When "need_options" is TRUE skip entries where wi_optset is FALSE. + * When "skip_diff_buffer" is TRUE avoid windows with 'diff' set that is in + * another tab page. + * Returns NULL when there isn't any info. + */ + static wininfo_T * +find_wininfo( + buf_T *buf, + int need_options, + int skip_diff_buffer UNUSED) +{ + wininfo_T *wip; + + FOR_ALL_BUF_WININFO(buf, wip) + if (wip->wi_win == curwin +#ifdef FEAT_DIFF + && (!skip_diff_buffer || !wininfo_other_tab_diff(wip)) +#endif + + && (!need_options || wip->wi_optset)) + break; + + if (wip != NULL) + return wip; + + // If no wininfo for curwin, use the first in the list (that doesn't have + // 'diff' set and is in another tab page). + // If "need_options" is TRUE skip entries that don't have options set, + // unless the window is editing "buf", so we can copy from the window + // itself. +#ifdef FEAT_DIFF + if (skip_diff_buffer) + { + FOR_ALL_BUF_WININFO(buf, wip) + if (!wininfo_other_tab_diff(wip) + && (!need_options || wip->wi_optset + || (wip->wi_win != NULL + && wip->wi_win->w_buffer == buf))) + break; + } + else +#endif + wip = buf->b_wininfo; + return wip; +} + +/* + * Reset the local window options to the values last used in this window. + * If the buffer wasn't used in this window before, use the values from + * the most recently used window. If the values were never set, use the + * global values for the window. + */ + void +get_winopts(buf_T *buf) +{ + wininfo_T *wip; + + clear_winopt(&curwin->w_onebuf_opt); +#ifdef FEAT_FOLDING + clearFolding(curwin); +#endif + + wip = find_wininfo(buf, TRUE, TRUE); + if (wip != NULL && wip->wi_win != NULL + && wip->wi_win != curwin && wip->wi_win->w_buffer == buf) + { + // The buffer is currently displayed in the window: use the actual + // option values instead of the saved (possibly outdated) values. + win_T *wp = wip->wi_win; + + copy_winopt(&wp->w_onebuf_opt, &curwin->w_onebuf_opt); +#ifdef FEAT_FOLDING + curwin->w_fold_manual = wp->w_fold_manual; + curwin->w_foldinvalid = TRUE; + cloneFoldGrowArray(&wp->w_folds, &curwin->w_folds); +#endif + } + else if (wip != NULL && wip->wi_optset) + { + // the buffer was displayed in the current window earlier + copy_winopt(&wip->wi_opt, &curwin->w_onebuf_opt); +#ifdef FEAT_FOLDING + curwin->w_fold_manual = wip->wi_fold_manual; + curwin->w_foldinvalid = TRUE; + cloneFoldGrowArray(&wip->wi_folds, &curwin->w_folds); +#endif + } + else + copy_winopt(&curwin->w_allbuf_opt, &curwin->w_onebuf_opt); + if (wip != NULL) + curwin->w_changelistidx = wip->wi_changelistidx; + +#ifdef FEAT_FOLDING + // Set 'foldlevel' to 'foldlevelstart' if it's not negative. + if (p_fdls >= 0) + curwin->w_p_fdl = p_fdls; +#endif + after_copy_winopt(curwin); +} + +/* + * Find the position (lnum and col) for the buffer 'buf' for the current + * window. + * Returns a pointer to no_position if no position is found. + */ + pos_T * +buflist_findfpos(buf_T *buf) +{ + wininfo_T *wip; + static pos_T no_position = {1, 0, 0}; + + wip = find_wininfo(buf, FALSE, FALSE); + if (wip != NULL) + return &(wip->wi_fpos); + else + return &no_position; +} + +/* + * Find the lnum for the buffer 'buf' for the current window. + */ + linenr_T +buflist_findlnum(buf_T *buf) +{ + return buflist_findfpos(buf)->lnum; +} + +/* + * List all known file names (for :files and :buffers command). + */ + void +buflist_list(exarg_T *eap) +{ + buf_T *buf = firstbuf; + int len; + int i; + int ro_char; + int changed_char; +#ifdef FEAT_TERMINAL + int job_running; + int job_none_open; +#endif + +#ifdef FEAT_VIMINFO + garray_T buflist; + buf_T **buflist_data = NULL, **p; + + if (vim_strchr(eap->arg, 't')) + { + ga_init2(&buflist, sizeof(buf_T *), 50); + FOR_ALL_BUFFERS(buf) + { + if (ga_grow(&buflist, 1) == OK) + ((buf_T **)buflist.ga_data)[buflist.ga_len++] = buf; + } + + qsort(buflist.ga_data, (size_t)buflist.ga_len, + sizeof(buf_T *), buf_compare); + + buflist_data = (buf_T **)buflist.ga_data; + buf = *buflist_data; + } + p = buflist_data; + + for (; buf != NULL && !got_int; buf = buflist_data != NULL + ? (++p < buflist_data + buflist.ga_len ? *p : NULL) + : buf->b_next) +#else + for (buf = firstbuf; buf != NULL && !got_int; buf = buf->b_next) +#endif + { +#ifdef FEAT_TERMINAL + job_running = term_job_running(buf->b_term); + job_none_open = term_none_open(buf->b_term); +#endif + // skip unlisted buffers, unless ! was used + if ((!buf->b_p_bl && !eap->forceit && !vim_strchr(eap->arg, 'u')) + || (vim_strchr(eap->arg, 'u') && buf->b_p_bl) + || (vim_strchr(eap->arg, '+') + && ((buf->b_flags & BF_READERR) || !bufIsChanged(buf))) + || (vim_strchr(eap->arg, 'a') + && (buf->b_ml.ml_mfp == NULL || buf->b_nwindows == 0)) + || (vim_strchr(eap->arg, 'h') + && (buf->b_ml.ml_mfp == NULL || buf->b_nwindows != 0)) +#ifdef FEAT_TERMINAL + || (vim_strchr(eap->arg, 'R') + && (!job_running || (job_running && job_none_open))) + || (vim_strchr(eap->arg, '?') + && (!job_running || (job_running && !job_none_open))) + || (vim_strchr(eap->arg, 'F') + && (job_running || buf->b_term == NULL)) +#endif + || (vim_strchr(eap->arg, '-') && buf->b_p_ma) + || (vim_strchr(eap->arg, '=') && !buf->b_p_ro) + || (vim_strchr(eap->arg, 'x') && !(buf->b_flags & BF_READERR)) + || (vim_strchr(eap->arg, '%') && buf != curbuf) + || (vim_strchr(eap->arg, '#') + && (buf == curbuf || curwin->w_alt_fnum != buf->b_fnum))) + continue; + if (buf_spname(buf) != NULL) + vim_strncpy(NameBuff, buf_spname(buf), MAXPATHL - 1); + else + home_replace(buf, buf->b_fname, NameBuff, MAXPATHL, TRUE); + if (message_filtered(NameBuff)) + continue; + + changed_char = (buf->b_flags & BF_READERR) ? 'x' + : (bufIsChanged(buf) ? '+' : ' '); +#ifdef FEAT_TERMINAL + if (job_running) + { + if (job_none_open) + ro_char = '?'; + else + ro_char = 'R'; + changed_char = ' '; // bufIsChanged() returns TRUE to avoid + // closing, but it's not actually changed. + } + else if (buf->b_term != NULL) + ro_char = 'F'; + else +#endif + ro_char = !buf->b_p_ma ? '-' : (buf->b_p_ro ? '=' : ' '); + + msg_putchar('\n'); + len = vim_snprintf((char *)IObuff, IOSIZE - 20, "%3d%c%c%c%c%c \"%s\"", + buf->b_fnum, + buf->b_p_bl ? ' ' : 'u', + buf == curbuf ? '%' : + (curwin->w_alt_fnum == buf->b_fnum ? '#' : ' '), + buf->b_ml.ml_mfp == NULL ? ' ' : + (buf->b_nwindows == 0 ? 'h' : 'a'), + ro_char, + changed_char, + NameBuff); + if (len > IOSIZE - 20) + len = IOSIZE - 20; + + // put "line 999" in column 40 or after the file name + i = 40 - vim_strsize(IObuff); + do + IObuff[len++] = ' '; + while (--i > 0 && len < IOSIZE - 18); +#ifdef FEAT_VIMINFO + if (vim_strchr(eap->arg, 't') && buf->b_last_used) + add_time(IObuff + len, (size_t)(IOSIZE - len), buf->b_last_used); + else +#endif + vim_snprintf((char *)IObuff + len, (size_t)(IOSIZE - len), + _("line %ld"), buf == curbuf ? curwin->w_cursor.lnum + : (long)buflist_findlnum(buf)); + msg_outtrans(IObuff); + out_flush(); // output one line at a time + ui_breakcheck(); + } + +#ifdef FEAT_VIMINFO + if (buflist_data) + ga_clear(&buflist); +#endif +} + +/* + * Get file name and line number for file 'fnum'. + * Used by DoOneCmd() for translating '%' and '#'. + * Used by insert_reg() and cmdline_paste() for '#' register. + * Return FAIL if not found, OK for success. + */ + int +buflist_name_nr( + int fnum, + char_u **fname, + linenr_T *lnum) +{ + buf_T *buf; + + buf = buflist_findnr(fnum); + if (buf == NULL || buf->b_fname == NULL) + return FAIL; + + *fname = buf->b_fname; + *lnum = buflist_findlnum(buf); + + return OK; +} + +/* + * 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_arg, + char_u *sfname_arg, + int message) // give message when buffer already exists +{ + char_u *ffname = ffname_arg; + char_u *sfname = sfname_arg; + buf_T *obuf = NULL; +#ifdef UNIX + stat_T st; +#endif + + if (ffname == NULL || *ffname == NUL) + { + // Removing the name. + if (buf->b_sfname != buf->b_ffname) + VIM_CLEAR(buf->b_sfname); + else + buf->b_sfname = NULL; + VIM_CLEAR(buf->b_ffname); +#ifdef UNIX + st.st_dev = (dev_T)-1; +#endif + } + else + { + fname_expand(buf, &ffname, &sfname); // will allocate ffname + if (ffname == NULL) // out of memory + return FAIL; + + /* + * If the file name is already used in another buffer: + * - if the buffer is loaded, fail + * - if the buffer is not loaded, delete it from the list + */ +#ifdef UNIX + if (mch_stat((char *)ffname, &st) < 0) + st.st_dev = (dev_T)-1; +#endif + if (!(buf->b_flags & BF_DUMMY)) +#ifdef UNIX + obuf = buflist_findname_stat(ffname, &st); +#else + obuf = buflist_findname(ffname); +#endif + if (obuf != NULL && obuf != buf) + { + win_T *win; + tabpage_T *tab; + int in_use = FALSE; + + // during startup a window may use a buffer that is not loaded yet + FOR_ALL_TAB_WINDOWS(tab, win) + if (win->w_buffer == obuf) + in_use = TRUE; + + // it's loaded or used in a window, fail + if (obuf->b_ml.ml_mfp != NULL || in_use) + { + if (message) + emsg(_(e_buffer_with_this_name_already_exists)); + vim_free(ffname); + return FAIL; + } + // delete from the list + close_buffer(NULL, obuf, DOBUF_WIPE, FALSE, FALSE); + } + sfname = vim_strsave(sfname); + if (ffname == NULL || sfname == NULL) + { + vim_free(sfname); + vim_free(ffname); + return FAIL; + } +#ifdef USE_FNAME_CASE + fname_case(sfname, 0); // set correct case for short file name +#endif + if (buf->b_sfname != buf->b_ffname) + vim_free(buf->b_sfname); + vim_free(buf->b_ffname); + buf->b_ffname = ffname; + buf->b_sfname = sfname; + } + buf->b_fname = buf->b_sfname; +#ifdef UNIX + if (st.st_dev == (dev_T)-1) + buf->b_dev_valid = FALSE; + else + { + buf->b_dev_valid = TRUE; + buf->b_dev = st.st_dev; + buf->b_ino = st.st_ino; + } +#endif + + buf->b_shortname = FALSE; + + buf_name_changed(buf); + return OK; +} + +/* + * Crude way of changing the name of a buffer. Use with care! + * The name should be relative to the current directory. + */ + void +buf_set_name(int fnum, char_u *name) +{ + buf_T *buf; + + buf = buflist_findnr(fnum); + if (buf == NULL) + return; + + if (buf->b_sfname != buf->b_ffname) + vim_free(buf->b_sfname); + vim_free(buf->b_ffname); + buf->b_ffname = vim_strsave(name); + buf->b_sfname = NULL; + // Allocate ffname and expand into full path. Also resolves .lnk + // files on Win32. + fname_expand(buf, &buf->b_ffname, &buf->b_sfname); + buf->b_fname = buf->b_sfname; +} + +/* + * Take care of what needs to be done when the name of buffer "buf" has + * changed. + */ + void +buf_name_changed(buf_T *buf) +{ + /* + * If the file name changed, also change the name of the swapfile + */ + if (buf->b_ml.ml_mfp != NULL) + ml_setname(buf); + +#ifdef FEAT_TERMINAL + if (buf->b_term != NULL) + term_clear_status_text(buf->b_term); +#endif + + if (curwin->w_buffer == buf) + check_arg_idx(curwin); // check file name for arg list + maketitle(); // set window title + status_redraw_all(); // status lines need to be redrawn + fmarks_check_names(buf); // check named file marks + ml_timestamp(buf); // reset timestamp +} + +/* + * set alternate file name for current window + * + * Used by do_one_cmd(), do_write() and do_ecmd(). + * Return the buffer. + */ + buf_T * +setaltfname( + char_u *ffname, + char_u *sfname, + linenr_T lnum) +{ + buf_T *buf; + + // Create a buffer. 'buflisted' is not set if it's a new buffer + buf = buflist_new(ffname, sfname, lnum, 0); + if (buf != NULL && (cmdmod.cmod_flags & CMOD_KEEPALT) == 0) + curwin->w_alt_fnum = buf->b_fnum; + return buf; +} + +/* + * Get alternate file name for current window. + * Return NULL if there isn't any, and give error message if requested. + */ + char_u * +getaltfname( + int errmsg) // give error message +{ + char_u *fname; + linenr_T dummy; + + if (buflist_name_nr(0, &fname, &dummy) == FAIL) + { + if (errmsg) + emsg(_(e_no_alternate_file)); + return NULL; + } + return fname; +} + +/* + * Add a file name to the buflist and return its number. + * Uses same flags as buflist_new(), except BLN_DUMMY. + * + * used by qf_init(), main() and doarglist() + */ + int +buflist_add(char_u *fname, int flags) +{ + buf_T *buf; + + buf = buflist_new(fname, NULL, (linenr_T)0, flags); + if (buf != NULL) + return buf->b_fnum; + return 0; +} + +#if defined(BACKSLASH_IN_FILENAME) || defined(PROTO) +/* + * Adjust slashes in file names. Called after 'shellslash' was set. + */ + void +buflist_slash_adjust(void) +{ + buf_T *bp; + + FOR_ALL_BUFFERS(bp) + { + if (bp->b_ffname != NULL) + slash_adjust(bp->b_ffname); + if (bp->b_sfname != NULL) + slash_adjust(bp->b_sfname); + } +} +#endif + +/* + * Set alternate cursor position for the current buffer and window "win". + * Also save the local window option values. + */ + void +buflist_altfpos(win_T *win) +{ + buflist_setfpos(curbuf, win, win->w_cursor.lnum, win->w_cursor.col, TRUE); +} + +/* + * Return TRUE if 'ffname' is not the same file as current file. + * Fname must have a full path (expanded by mch_FullName()). + */ + int +otherfile(char_u *ffname) +{ + return otherfile_buf(curbuf, ffname +#ifdef UNIX + , NULL +#endif + ); +} + + static int +otherfile_buf( + buf_T *buf, + char_u *ffname +#ifdef UNIX + , stat_T *stp +#endif + ) +{ + // no name is different + if (ffname == NULL || *ffname == NUL || buf->b_ffname == NULL) + return TRUE; + if (fnamecmp(ffname, buf->b_ffname) == 0) + return FALSE; +#ifdef UNIX + { + stat_T st; + + // If no stat_T given, get it now + if (stp == NULL) + { + if (!buf->b_dev_valid || mch_stat((char *)ffname, &st) < 0) + st.st_dev = (dev_T)-1; + stp = &st; + } + // Use dev/ino to check if the files are the same, even when the names + // are different (possible with links). Still need to compare the + // name above, for when the file doesn't exist yet. + // Problem: The dev/ino changes when a file is deleted (and created + // again) and remains the same when renamed/moved. We don't want to + // mch_stat() each buffer each time, that would be too slow. Get the + // dev/ino again when they appear to match, but not when they appear + // to be different: Could skip a buffer when it's actually the same + // file. + if (buf_same_ino(buf, stp)) + { + buf_setino(buf); + if (buf_same_ino(buf, stp)) + return FALSE; + } + } +#endif + return TRUE; +} + +#if defined(UNIX) || defined(PROTO) +/* + * Set inode and device number for a buffer. + * Must always be called when b_fname is changed!. + */ + void +buf_setino(buf_T *buf) +{ + stat_T st; + + if (buf->b_fname != NULL && mch_stat((char *)buf->b_fname, &st) >= 0) + { + buf->b_dev_valid = TRUE; + buf->b_dev = st.st_dev; + buf->b_ino = st.st_ino; + } + else + buf->b_dev_valid = FALSE; +} + +/* + * Return TRUE if dev/ino in buffer "buf" matches with "stp". + */ + static int +buf_same_ino( + buf_T *buf, + stat_T *stp) +{ + return (buf->b_dev_valid + && stp->st_dev == buf->b_dev + && stp->st_ino == buf->b_ino); +} +#endif + +/* + * Print info about the current buffer. + */ + void +fileinfo( + int fullname, // when non-zero print full path + int shorthelp, + int dont_truncate) +{ + char_u *name; + int n; + char *p; + char *buffer; + size_t len; + + buffer = alloc(IOSIZE); + if (buffer == NULL) + return; + + if (fullname > 1) // 2 CTRL-G: include buffer number + { + vim_snprintf(buffer, IOSIZE, "buf %d: ", curbuf->b_fnum); + p = buffer + STRLEN(buffer); + } + else + p = buffer; + + *p++ = '"'; + if (buf_spname(curbuf) != NULL) + vim_strncpy((char_u *)p, buf_spname(curbuf), IOSIZE - (p - buffer) - 1); + else + { + if (!fullname && curbuf->b_fname != NULL) + name = curbuf->b_fname; + else + name = curbuf->b_ffname; + home_replace(shorthelp ? curbuf : NULL, name, (char_u *)p, + (int)(IOSIZE - (p - buffer)), TRUE); + } + + vim_snprintf_add(buffer, IOSIZE, "\"%s%s%s%s%s%s", + curbufIsChanged() ? (shortmess(SHM_MOD) + ? " [+]" : _(" [Modified]")) : " ", + (curbuf->b_flags & BF_NOTEDITED) && !bt_dontwrite(curbuf) + ? _("[Not edited]") : "", + (curbuf->b_flags & BF_NEW) && !bt_dontwrite(curbuf) + ? new_file_message() : "", + (curbuf->b_flags & BF_READERR) ? _("[Read errors]") : "", + curbuf->b_p_ro ? (shortmess(SHM_RO) ? _("[RO]") + : _("[readonly]")) : "", + (curbufIsChanged() || (curbuf->b_flags & BF_WRITE_MASK) + || curbuf->b_p_ro) ? + " " : ""); + // With 32 bit longs and more than 21,474,836 lines multiplying by 100 + // causes an overflow, thus for large numbers divide instead. + if (curwin->w_cursor.lnum > 1000000L) + n = (int)(((long)curwin->w_cursor.lnum) / + ((long)curbuf->b_ml.ml_line_count / 100L)); + else + n = (int)(((long)curwin->w_cursor.lnum * 100L) / + (long)curbuf->b_ml.ml_line_count); + if (curbuf->b_ml.ml_flags & ML_EMPTY) + vim_snprintf_add(buffer, IOSIZE, "%s", _(no_lines_msg)); + else if (p_ru) + // Current line and column are already on the screen -- webb + vim_snprintf_add(buffer, IOSIZE, + NGETTEXT("%ld line --%d%%--", "%ld lines --%d%%--", + curbuf->b_ml.ml_line_count), + (long)curbuf->b_ml.ml_line_count, n); + else + { + vim_snprintf_add(buffer, IOSIZE, + _("line %ld of %ld --%d%%-- col "), + (long)curwin->w_cursor.lnum, + (long)curbuf->b_ml.ml_line_count, + n); + validate_virtcol(); + len = STRLEN(buffer); + col_print((char_u *)buffer + len, IOSIZE - len, + (int)curwin->w_cursor.col + 1, (int)curwin->w_virtcol + 1); + } + + (void)append_arg_number(curwin, (char_u *)buffer, IOSIZE, + !shortmess(SHM_FILE)); + + if (dont_truncate) + { + // Temporarily set msg_scroll to avoid the message being truncated. + // First call msg_start() to get the message in the right place. + msg_start(); + n = msg_scroll; + msg_scroll = TRUE; + msg(buffer); + msg_scroll = n; + } + else + { + p = msg_trunc_attr(buffer, FALSE, 0); + if (restart_edit != 0 || (msg_scrolled && !need_wait_return)) + // Need to repeat the message after redrawing when: + // - When restart_edit is set (otherwise there will be a delay + // before redrawing). + // - When the screen was scrolled but there is no wait-return + // prompt. + set_keep_msg((char_u *)p, 0); + } + + vim_free(buffer); +} + + void +col_print( + char_u *buf, + size_t buflen, + int col, + int vcol) +{ + if (col == vcol) + vim_snprintf((char *)buf, buflen, "%d", col); + else + vim_snprintf((char *)buf, buflen, "%d-%d", col, vcol); +} + +static char_u *lasttitle = NULL; +static char_u *lasticon = NULL; + +/* + * Put the file name in the title bar and icon of the window. + */ + void +maketitle(void) +{ + char_u *p; + char_u *title_str = NULL; + char_u *icon_str = NULL; + int maxlen = 0; + int len; + int mustset; + char_u buf[IOSIZE]; + int off; + + if (!redrawing()) + { + // Postpone updating the title when 'lazyredraw' is set. + need_maketitle = TRUE; + return; + } + + need_maketitle = FALSE; + if (!p_title && !p_icon && lasttitle == NULL && lasticon == NULL) + return; // nothing to do + + if (p_title) + { + if (p_titlelen > 0) + { + maxlen = p_titlelen * Columns / 100; + if (maxlen < 10) + maxlen = 10; + } + + title_str = buf; + if (*p_titlestring != NUL) + { +#ifdef FEAT_STL_OPT + if (stl_syntax & STL_IN_TITLE) + build_stl_str_hl(curwin, title_str, sizeof(buf), p_titlestring, + (char_u *)"titlestring", 0, + 0, maxlen, NULL, NULL); + else +#endif + title_str = p_titlestring; + } + else + { + // format: "fname + (path) (1 of 2) - VIM" + +#define SPACE_FOR_FNAME (IOSIZE - 100) +#define SPACE_FOR_DIR (IOSIZE - 20) +#define SPACE_FOR_ARGNR (IOSIZE - 10) // at least room for " - VIM" + if (curbuf->b_fname == NULL) + vim_strncpy(buf, (char_u *)_("[No Name]"), SPACE_FOR_FNAME); +#ifdef FEAT_TERMINAL + else if (curbuf->b_term != NULL) + { + vim_strncpy(buf, term_get_status_text(curbuf->b_term), + SPACE_FOR_FNAME); + } +#endif + else + { + p = transstr(gettail(curbuf->b_fname)); + vim_strncpy(buf, p, SPACE_FOR_FNAME); + vim_free(p); + } + +#ifdef FEAT_TERMINAL + if (curbuf->b_term == NULL) +#endif + switch (bufIsChanged(curbuf) + + (curbuf->b_p_ro * 2) + + (!curbuf->b_p_ma * 4)) + { + case 1: STRCAT(buf, " +"); break; + case 2: STRCAT(buf, " ="); break; + case 3: STRCAT(buf, " =+"); break; + case 4: + case 6: STRCAT(buf, " -"); break; + case 5: + case 7: STRCAT(buf, " -+"); break; + } + + if (curbuf->b_fname != NULL +#ifdef FEAT_TERMINAL + && curbuf->b_term == NULL +#endif + ) + { + // Get path of file, replace home dir with ~ + off = (int)STRLEN(buf); + buf[off++] = ' '; + buf[off++] = '('; + home_replace(curbuf, curbuf->b_ffname, + buf + off, SPACE_FOR_DIR - off, TRUE); +#ifdef BACKSLASH_IN_FILENAME + // avoid "c:/name" to be reduced to "c" + if (isalpha(buf[off]) && buf[off + 1] == ':') + off += 2; +#endif + // remove the file name + p = gettail_sep(buf + off); + if (p == buf + off) + { + // must be a help buffer + vim_strncpy(buf + off, (char_u *)_("help"), + (size_t)(SPACE_FOR_DIR - off - 1)); + } + else + *p = NUL; + + // Translate unprintable chars and concatenate. Keep some + // room for the server name. When there is no room (very long + // file name) use (...). + if (off < SPACE_FOR_DIR) + { + p = transstr(buf + off); + vim_strncpy(buf + off, p, (size_t)(SPACE_FOR_DIR - off)); + vim_free(p); + } + else + { + vim_strncpy(buf + off, (char_u *)"...", + (size_t)(SPACE_FOR_ARGNR - off)); + } + STRCAT(buf, ")"); + } + + append_arg_number(curwin, buf, SPACE_FOR_ARGNR, FALSE); + +#if defined(FEAT_CLIENTSERVER) + if (serverName != NULL) + { + STRCAT(buf, " - "); + vim_strcat(buf, serverName, IOSIZE); + } + else +#endif + STRCAT(buf, " - VIM"); + + if (maxlen > 0) + { + // make it shorter by removing a bit in the middle + if (vim_strsize(buf) > maxlen) + trunc_string(buf, buf, maxlen, IOSIZE); + } + } + } + mustset = value_changed(title_str, &lasttitle); + + if (p_icon) + { + icon_str = buf; + if (*p_iconstring != NUL) + { +#ifdef FEAT_STL_OPT + if (stl_syntax & STL_IN_ICON) + build_stl_str_hl(curwin, icon_str, sizeof(buf), p_iconstring, + (char_u *)"iconstring", 0, 0, 0, NULL, NULL); + else +#endif + icon_str = p_iconstring; + } + else + { + if (buf_spname(curbuf) != NULL) + p = buf_spname(curbuf); + else // use file name only in icon + p = gettail(curbuf->b_ffname); + *icon_str = NUL; + // Truncate name at 100 bytes. + len = (int)STRLEN(p); + if (len > 100) + { + len -= 100; + if (has_mbyte) + len += (*mb_tail_off)(p, p + len) + 1; + p += len; + } + STRCPY(icon_str, p); + trans_characters(icon_str, IOSIZE); + } + } + + mustset |= value_changed(icon_str, &lasticon); + + if (mustset) + resettitle(); +} + +/* + * Used for title and icon: Check if "str" differs from "*last". Set "*last" + * from "str" if it does. + * Return TRUE if resettitle() is to be called. + */ + static int +value_changed(char_u *str, char_u **last) +{ + if ((str == NULL) != (*last == NULL) + || (str != NULL && *last != NULL && STRCMP(str, *last) != 0)) + { + vim_free(*last); + if (str == NULL) + { + *last = NULL; + mch_restore_title( + last == &lasttitle ? SAVE_RESTORE_TITLE : SAVE_RESTORE_ICON); + } + else + { + *last = vim_strsave(str); + return TRUE; + } + } + return FALSE; +} + +/* + * Put current window title back (used after calling a shell) + */ + void +resettitle(void) +{ + mch_settitle(lasttitle, lasticon); +} + +# if defined(EXITFREE) || defined(PROTO) + void +free_titles(void) +{ + vim_free(lasttitle); + vim_free(lasticon); +} +# endif + + +#if defined(FEAT_STL_OPT) || defined(FEAT_GUI_TABLINE) || defined(PROTO) + +/* + * Used for building in the status line. + */ +typedef struct +{ + char_u *stl_start; + int stl_minwid; + int stl_maxwid; + enum { + Normal, + Empty, + Group, + Separate, + Highlight, + TabPage, + Trunc + } stl_type; +} stl_item_T; + +static size_t stl_items_len = 20; // Initial value, grows as needed. +static stl_item_T *stl_items = NULL; +static int *stl_groupitem = NULL; +static stl_hlrec_T *stl_hltab = NULL; +static stl_hlrec_T *stl_tabtab = NULL; +static int *stl_separator_locations = NULL; + +/* + * Build a string from the status line items in "fmt". + * Return length of string in screen cells. + * + * Normally works for window "wp", except when working for 'tabline' then it + * is "curwin". + * + * Items are drawn interspersed with the text that surrounds it + * Specials: %-<wid>(xxx%) => group, %= => separation marker, %< => truncation + * Item: %-<minwid>.<maxwid><itemch> All but <itemch> are optional + * + * If maxwidth is not zero, the string will be filled at any middle marker + * or truncated if too long, fillchar is used for all whitespace. + */ + int +build_stl_str_hl( + win_T *wp, + char_u *out, // buffer to write into != NameBuff + size_t outlen, // length of out[] + char_u *fmt, + char_u *opt_name, // option name corresponding to "fmt" + int opt_scope, // scope for "opt_name" + int fillchar, + int maxwidth, + stl_hlrec_T **hltab, // return: HL attributes (can be NULL) + stl_hlrec_T **tabtab) // return: tab page nrs (can be NULL) +{ + linenr_T lnum; + size_t len; + char_u *p; + char_u *s; + char_u *t; + int byteval; +#ifdef FEAT_EVAL + int use_sandbox; + win_T *save_curwin; + buf_T *save_curbuf; + int save_VIsual_active; +#endif + int empty_line; + colnr_T virtcol; + long l; + long n; + int prevchar_isflag; + int prevchar_isitem; + int itemisflag; + int fillable; + char_u *str; + long num; + int width; + int itemcnt; + int curitem; + int group_end_userhl; + int group_start_userhl; + int groupdepth; +#ifdef FEAT_EVAL + int evaldepth; +#endif + int minwid; + int maxwid; + int zeropad; + char_u base; + char_u opt; +#define TMPLEN 70 + char_u buf_tmp[TMPLEN]; + char_u win_tmp[TMPLEN]; + char_u *usefmt = fmt; + stl_hlrec_T *sp; + int save_redraw_not_allowed = redraw_not_allowed; + int save_KeyTyped = KeyTyped; + // TODO: find out why using called_emsg_before makes tests fail, does it + // matter? + // int called_emsg_before = called_emsg; + int did_emsg_before = did_emsg; + + // When inside update_screen() we do not want redrawing a statusline, + // ruler, title, etc. to trigger another redraw, it may cause an endless + // loop. + if (updating_screen) + redraw_not_allowed = TRUE; + + if (stl_items == NULL) + { + stl_items = ALLOC_MULT(stl_item_T, stl_items_len); + stl_groupitem = ALLOC_MULT(int, stl_items_len); + + // Allocate one more, because the last element is used to indicate the + // end of the list. + stl_hltab = ALLOC_MULT(stl_hlrec_T, stl_items_len + 1); + stl_tabtab = ALLOC_MULT(stl_hlrec_T, stl_items_len + 1); + + stl_separator_locations = ALLOC_MULT(int, stl_items_len); + } + +#ifdef FEAT_EVAL + // if "fmt" was set insecurely it needs to be evaluated in the sandbox + use_sandbox = was_set_insecurely(opt_name, opt_scope); + + // When the format starts with "%!" then evaluate it as an expression and + // use the result as the actual format string. + if (fmt[0] == '%' && fmt[1] == '!') + { + typval_T tv; + + tv.v_type = VAR_NUMBER; + tv.vval.v_number = wp->w_id; + set_var((char_u *)"g:statusline_winid", &tv, FALSE); + + usefmt = eval_to_string_safe(fmt + 2, use_sandbox, FALSE, FALSE); + if (usefmt == NULL) + usefmt = fmt; + + do_unlet((char_u *)"g:statusline_winid", TRUE); + } +#endif + + if (fillchar == 0) + fillchar = ' '; + + // The cursor in windows other than the current one isn't always + // up-to-date, esp. because of autocommands and timers. + lnum = wp->w_cursor.lnum; + if (lnum > wp->w_buffer->b_ml.ml_line_count) + { + lnum = wp->w_buffer->b_ml.ml_line_count; + wp->w_cursor.lnum = lnum; + } + + // Get line & check if empty (cursorpos will show "0-1"). Note that + // p will become invalid when getting another buffer line. + p = ml_get_buf(wp->w_buffer, lnum, FALSE); + empty_line = (*p == NUL); + + // Get the byte value now, in case we need it below. This is more efficient + // than making a copy of the line. + len = STRLEN(p); + if (wp->w_cursor.col > (colnr_T)len) + { + // Line may have changed since checking the cursor column, or the lnum + // was adjusted above. + wp->w_cursor.col = (colnr_T)len; + wp->w_cursor.coladd = 0; + byteval = 0; + } + else + byteval = (*mb_ptr2char)(p + wp->w_cursor.col); + + groupdepth = 0; +#ifdef FEAT_EVAL + evaldepth = 0; +#endif + p = out; + curitem = 0; + prevchar_isflag = TRUE; + prevchar_isitem = FALSE; + for (s = usefmt; *s != NUL; ) + { + if (curitem == (int)stl_items_len) + { + size_t new_len = stl_items_len * 3 / 2; + + stl_item_T *new_items = + vim_realloc(stl_items, sizeof(stl_item_T) * new_len); + if (new_items == NULL) + break; + stl_items = new_items; + + int *new_groupitem = + vim_realloc(stl_groupitem, sizeof(int) * new_len); + if (new_groupitem == NULL) + break; + stl_groupitem = new_groupitem; + + stl_hlrec_T *new_hlrec = vim_realloc(stl_hltab, + sizeof(stl_hlrec_T) * (new_len + 1)); + if (new_hlrec == NULL) + break; + stl_hltab = new_hlrec; + new_hlrec = vim_realloc(stl_tabtab, + sizeof(stl_hlrec_T) * (new_len + 1)); + if (new_hlrec == NULL) + break; + stl_tabtab = new_hlrec; + + int *new_separator_locs = vim_realloc(stl_separator_locations, + sizeof(int) * new_len); + if (new_separator_locs == NULL) + break; + stl_separator_locations = new_separator_locs;; + + stl_items_len = new_len; + } + + if (*s != '%') + prevchar_isflag = prevchar_isitem = FALSE; + + /* + * Handle up to the next '%' or the end. + */ + while (*s != NUL && *s != '%' && p + 1 < out + outlen) + *p++ = *s++; + if (*s == NUL || p + 1 >= out + outlen) + break; + + /* + * Handle one '%' item. + */ + s++; + if (*s == NUL) // ignore trailing % + break; + if (*s == '%') + { + if (p + 1 >= out + outlen) + break; + *p++ = *s++; + prevchar_isflag = prevchar_isitem = FALSE; + continue; + } + // STL_SEPARATE: Separation between items, filled with white space. + if (*s == STL_SEPARATE) + { + s++; + if (groupdepth > 0) + continue; + stl_items[curitem].stl_type = Separate; + stl_items[curitem++].stl_start = p; + continue; + } + if (*s == STL_TRUNCMARK) + { + s++; + stl_items[curitem].stl_type = Trunc; + stl_items[curitem++].stl_start = p; + continue; + } + if (*s == ')') + { + s++; + if (groupdepth < 1) + continue; + groupdepth--; + + t = stl_items[stl_groupitem[groupdepth]].stl_start; + *p = NUL; + l = vim_strsize(t); + if (curitem > stl_groupitem[groupdepth] + 1 + && stl_items[stl_groupitem[groupdepth]].stl_minwid == 0) + { + // remove group if all items are empty and highlight group + // doesn't change + group_start_userhl = group_end_userhl = 0; + for (n = stl_groupitem[groupdepth] - 1; n >= 0; n--) + { + if (stl_items[n].stl_type == Highlight) + { + group_start_userhl = group_end_userhl = + stl_items[n].stl_minwid; + break; + } + } + for (n = stl_groupitem[groupdepth] + 1; n < curitem; n++) + { + if (stl_items[n].stl_type == Normal) + break; + if (stl_items[n].stl_type == Highlight) + group_end_userhl = stl_items[n].stl_minwid; + } + if (n == curitem && group_start_userhl == group_end_userhl) + { + // empty group + p = t; + l = 0; + for (n = stl_groupitem[groupdepth] + 1; n < curitem; n++) + { + // do not use the highlighting from the removed group + if (stl_items[n].stl_type == Highlight) + stl_items[n].stl_type = Empty; + // adjust the start position of TabPage to the next + // item position + if (stl_items[n].stl_type == TabPage) + stl_items[n].stl_start = p; + } + } + } + if (l > stl_items[stl_groupitem[groupdepth]].stl_maxwid) + { + // truncate, remove n bytes of text at the start + if (has_mbyte) + { + // Find the first character that should be included. + n = 0; + while (l >= stl_items[stl_groupitem[groupdepth]].stl_maxwid) + { + l -= ptr2cells(t + n); + n += (*mb_ptr2len)(t + n); + } + } + else + n = (long)(p - t) - stl_items[stl_groupitem[groupdepth]] + .stl_maxwid + 1; + + *t = '<'; + mch_memmove(t + 1, t + n, (size_t)(p - (t + n))); + p = p - n + 1; + + // Fill up space left over by half a double-wide char. + while (++l < stl_items[stl_groupitem[groupdepth]].stl_minwid) + MB_CHAR2BYTES(fillchar, p); + + // correct the start of the items for the truncation + for (l = stl_groupitem[groupdepth] + 1; l < curitem; l++) + { + // Minus one for the leading '<' added above. + stl_items[l].stl_start -= n - 1; + if (stl_items[l].stl_start < t) + stl_items[l].stl_start = t; + } + } + else if (abs(stl_items[stl_groupitem[groupdepth]].stl_minwid) > l) + { + // fill + n = stl_items[stl_groupitem[groupdepth]].stl_minwid; + if (n < 0) + { + // fill by appending characters + n = 0 - n; + while (l++ < n && p + 1 < out + outlen) + MB_CHAR2BYTES(fillchar, p); + } + else + { + // fill by inserting characters + l = (n - l) * MB_CHAR2LEN(fillchar); + mch_memmove(t + l, t, (size_t)(p - t)); + if (p + l >= out + outlen) + l = (long)((out + outlen) - p - 1); + p += l; + for (n = stl_groupitem[groupdepth] + 1; n < curitem; n++) + stl_items[n].stl_start += l; + for ( ; l > 0; l--) + MB_CHAR2BYTES(fillchar, t); + } + } + continue; + } + minwid = 0; + maxwid = 9999; + zeropad = FALSE; + l = 1; + if (*s == '0') + { + s++; + zeropad = TRUE; + } + if (*s == '-') + { + s++; + l = -1; + } + if (VIM_ISDIGIT(*s)) + { + minwid = (int)getdigits(&s); + if (minwid < 0) // overflow + minwid = 0; + } + if (*s == STL_USER_HL) + { + stl_items[curitem].stl_type = Highlight; + stl_items[curitem].stl_start = p; + stl_items[curitem].stl_minwid = minwid > 9 ? 1 : minwid; + s++; + curitem++; + continue; + } + if (*s == STL_TABPAGENR || *s == STL_TABCLOSENR) + { + if (*s == STL_TABCLOSENR) + { + if (minwid == 0) + { + // %X ends the close label, go back to the previously + // define tab label nr. + for (n = curitem - 1; n >= 0; --n) + if (stl_items[n].stl_type == TabPage + && stl_items[n].stl_minwid >= 0) + { + minwid = stl_items[n].stl_minwid; + break; + } + } + else + // close nrs are stored as negative values + minwid = - minwid; + } + stl_items[curitem].stl_type = TabPage; + stl_items[curitem].stl_start = p; + stl_items[curitem].stl_minwid = minwid; + s++; + curitem++; + continue; + } + if (*s == '.') + { + s++; + if (VIM_ISDIGIT(*s)) + { + maxwid = (int)getdigits(&s); + if (maxwid <= 0) // overflow + maxwid = 50; + } + } + minwid = (minwid > 50 ? 50 : minwid) * l; + if (*s == '(') + { + stl_groupitem[groupdepth++] = curitem; + stl_items[curitem].stl_type = Group; + stl_items[curitem].stl_start = p; + stl_items[curitem].stl_minwid = minwid; + stl_items[curitem].stl_maxwid = maxwid; + s++; + curitem++; + continue; + } +#ifdef FEAT_EVAL + // Denotes end of expanded %{} block + if (*s == '}' && evaldepth > 0) + { + s++; + evaldepth--; + continue; + } +#endif + if (vim_strchr(STL_ALL, *s) == NULL) + { + if (*s == NUL) // can happen with "%0" + break; + s++; + continue; + } + opt = *s++; + + // OK - now for the real work + base = 'D'; + itemisflag = FALSE; + fillable = TRUE; + num = -1; + str = NULL; + switch (opt) + { + case STL_FILEPATH: + case STL_FULLPATH: + case STL_FILENAME: + fillable = FALSE; // don't change ' ' to fillchar + if (buf_spname(wp->w_buffer) != NULL) + vim_strncpy(NameBuff, buf_spname(wp->w_buffer), MAXPATHL - 1); + else + { + t = (opt == STL_FULLPATH) ? wp->w_buffer->b_ffname + : wp->w_buffer->b_fname; + home_replace(wp->w_buffer, t, NameBuff, MAXPATHL, TRUE); + } + trans_characters(NameBuff, MAXPATHL); + if (opt != STL_FILENAME) + str = NameBuff; + else + str = gettail(NameBuff); + break; + + case STL_VIM_EXPR: // '{' + { +#ifdef FEAT_EVAL + char_u *block_start = s - 1; +#endif + int reevaluate = (*s == '%'); + + if (reevaluate) + s++; + itemisflag = TRUE; + t = p; + while ((*s != '}' || (reevaluate && s[-1] != '%')) + && *s != NUL && p + 1 < out + outlen) + *p++ = *s++; + if (*s != '}') // missing '}' or out of space + break; + s++; + if (reevaluate) + p[-1] = 0; // remove the % at the end of %{% expr %} + else + *p = 0; + p = t; +#ifdef FEAT_EVAL + vim_snprintf((char *)buf_tmp, sizeof(buf_tmp), + "%d", curbuf->b_fnum); + set_internal_string_var((char_u *)"g:actual_curbuf", buf_tmp); + vim_snprintf((char *)win_tmp, sizeof(win_tmp), "%d", curwin->w_id); + set_internal_string_var((char_u *)"g:actual_curwin", win_tmp); + + save_curbuf = curbuf; + save_curwin = curwin; + save_VIsual_active = VIsual_active; + curwin = wp; + curbuf = wp->w_buffer; + // Visual mode is only valid in the current window. + if (curwin != save_curwin) + VIsual_active = FALSE; + + str = eval_to_string_safe(p, use_sandbox, FALSE, FALSE); + + curwin = save_curwin; + curbuf = save_curbuf; + VIsual_active = save_VIsual_active; + do_unlet((char_u *)"g:actual_curbuf", TRUE); + do_unlet((char_u *)"g:actual_curwin", TRUE); + + if (str != NULL && *str != 0) + { + if (*skipdigits(str) == NUL) + { + num = atoi((char *)str); + VIM_CLEAR(str); + itemisflag = FALSE; + } + } + + // If the output of the expression needs to be evaluated + // replace the %{} block with the result of evaluation + if (reevaluate && str != NULL && *str != 0 + && strchr((const char *)str, '%') != NULL + && evaldepth < MAX_STL_EVAL_DEPTH) + { + size_t parsed_usefmt = (size_t)(block_start - usefmt); + size_t str_length = strlen((const char *)str); + size_t fmt_length = strlen((const char *)s); + size_t new_fmt_len = parsed_usefmt + + str_length + fmt_length + 3; + char_u *new_fmt = (char_u *)alloc(new_fmt_len * sizeof(char_u)); + char_u *new_fmt_p = new_fmt; + + new_fmt_p = (char_u *)memcpy(new_fmt_p, usefmt, parsed_usefmt) + + parsed_usefmt; + new_fmt_p = (char_u *)memcpy(new_fmt_p , str, str_length) + + str_length; + new_fmt_p = (char_u *)memcpy(new_fmt_p, "%}", 2) + 2; + new_fmt_p = (char_u *)memcpy(new_fmt_p , s, fmt_length) + + fmt_length; + *new_fmt_p = 0; + new_fmt_p = NULL; + + if (usefmt != fmt) + vim_free(usefmt); + VIM_CLEAR(str); + usefmt = new_fmt; + s = usefmt + parsed_usefmt; + evaldepth++; + continue; + } +#endif + break; + } + case STL_LINE: + num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) + ? 0L : (long)(wp->w_cursor.lnum); + break; + + case STL_NUMLINES: + num = wp->w_buffer->b_ml.ml_line_count; + break; + + case STL_COLUMN: + num = (State & MODE_INSERT) == 0 && empty_line + ? 0 : (int)wp->w_cursor.col + 1; + break; + + case STL_VIRTCOL: + case STL_VIRTCOL_ALT: + virtcol = wp->w_virtcol + 1; + // Don't display %V if it's the same as %c. + if (opt == STL_VIRTCOL_ALT + && (virtcol == (colnr_T)((State & MODE_INSERT) == 0 + && empty_line ? 0 : (int)wp->w_cursor.col + 1))) + break; + num = (long)virtcol; + break; + + case STL_PERCENTAGE: + num = (int)(((long)wp->w_cursor.lnum * 100L) / + (long)wp->w_buffer->b_ml.ml_line_count); + break; + + case STL_ALTPERCENT: + str = buf_tmp; + get_rel_pos(wp, str, TMPLEN); + break; + + case STL_SHOWCMD: + if (p_sc && STRCMP(opt_name, p_sloc) == 0) + str = showcmd_buf; + break; + + case STL_ARGLISTSTAT: + fillable = FALSE; + buf_tmp[0] = 0; + if (append_arg_number(wp, buf_tmp, (int)sizeof(buf_tmp), FALSE)) + str = buf_tmp; + break; + + case STL_KEYMAP: + fillable = FALSE; + if (get_keymap_str(wp, (char_u *)"<%s>", buf_tmp, TMPLEN)) + str = buf_tmp; + break; + case STL_PAGENUM: +#if defined(FEAT_PRINTER) || defined(FEAT_GUI_TABLINE) + num = printer_page_num; +#else + num = 0; +#endif + break; + + case STL_BUFNO: + num = wp->w_buffer->b_fnum; + break; + + case STL_OFFSET_X: + base = 'X'; + // FALLTHROUGH + case STL_OFFSET: +#ifdef FEAT_BYTEOFF + l = ml_find_line_or_offset(wp->w_buffer, wp->w_cursor.lnum, NULL); + num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) || l < 0 + ? 0L : l + 1 + ((State & MODE_INSERT) == 0 && empty_line + ? 0 : (int)wp->w_cursor.col); +#endif + break; + + case STL_BYTEVAL_X: + base = 'X'; + // FALLTHROUGH + case STL_BYTEVAL: + num = byteval; + if (num == NL) + num = 0; + else if (num == CAR && get_fileformat(wp->w_buffer) == EOL_MAC) + num = NL; + break; + + case STL_ROFLAG: + case STL_ROFLAG_ALT: + itemisflag = TRUE; + if (wp->w_buffer->b_p_ro) + str = (char_u *)((opt == STL_ROFLAG_ALT) ? ",RO" : _("[RO]")); + break; + + case STL_HELPFLAG: + case STL_HELPFLAG_ALT: + itemisflag = TRUE; + if (wp->w_buffer->b_help) + str = (char_u *)((opt == STL_HELPFLAG_ALT) ? ",HLP" + : _("[Help]")); + break; + + case STL_FILETYPE: + if (*wp->w_buffer->b_p_ft != NUL + && STRLEN(wp->w_buffer->b_p_ft) < TMPLEN - 3) + { + vim_snprintf((char *)buf_tmp, sizeof(buf_tmp), "[%s]", + wp->w_buffer->b_p_ft); + str = buf_tmp; + } + break; + + case STL_FILETYPE_ALT: + itemisflag = TRUE; + if (*wp->w_buffer->b_p_ft != NUL + && STRLEN(wp->w_buffer->b_p_ft) < TMPLEN - 2) + { + vim_snprintf((char *)buf_tmp, sizeof(buf_tmp), ",%s", + wp->w_buffer->b_p_ft); + for (t = buf_tmp; *t != 0; t++) + *t = TOUPPER_LOC(*t); + str = buf_tmp; + } + break; + +#if defined(FEAT_QUICKFIX) + case STL_PREVIEWFLAG: + case STL_PREVIEWFLAG_ALT: + itemisflag = TRUE; + if (wp->w_p_pvw) + str = (char_u *)((opt == STL_PREVIEWFLAG_ALT) ? ",PRV" + : _("[Preview]")); + break; + + case STL_QUICKFIX: + if (bt_quickfix(wp->w_buffer)) + str = (char_u *)(wp->w_llist_ref + ? _(msg_loclist) + : _(msg_qflist)); + break; +#endif + + case STL_MODIFIED: + case STL_MODIFIED_ALT: + itemisflag = TRUE; + switch ((opt == STL_MODIFIED_ALT) + + bufIsChanged(wp->w_buffer) * 2 + + (!wp->w_buffer->b_p_ma) * 4) + { + case 2: str = (char_u *)"[+]"; break; + case 3: str = (char_u *)",+"; break; + case 4: str = (char_u *)"[-]"; break; + case 5: str = (char_u *)",-"; break; + case 6: str = (char_u *)"[+-]"; break; + case 7: str = (char_u *)",+-"; break; + } + break; + + case STL_HIGHLIGHT: + t = s; + while (*s != '#' && *s != NUL) + ++s; + if (*s == '#') + { + stl_items[curitem].stl_type = Highlight; + stl_items[curitem].stl_start = p; + stl_items[curitem].stl_minwid = -syn_namen2id(t, (int)(s - t)); + curitem++; + } + if (*s != NUL) + ++s; + continue; + } + + stl_items[curitem].stl_start = p; + stl_items[curitem].stl_type = Normal; + if (str != NULL && *str) + { + t = str; + if (itemisflag) + { + if ((t[0] && t[1]) + && ((!prevchar_isitem && *t == ',') + || (prevchar_isflag && *t == ' '))) + t++; + prevchar_isflag = TRUE; + } + l = vim_strsize(t); + if (l > 0) + prevchar_isitem = TRUE; + if (l > maxwid) + { + while (l >= maxwid) + if (has_mbyte) + { + l -= ptr2cells(t); + t += (*mb_ptr2len)(t); + } + else + l -= byte2cells(*t++); + if (p + 1 >= out + outlen) + break; + *p++ = '<'; + } + if (minwid > 0) + { + for (; l < minwid && p + 1 < out + outlen; l++) + { + // Don't put a "-" in front of a digit. + if (l + 1 == minwid && fillchar == '-' && VIM_ISDIGIT(*t)) + *p++ = ' '; + else + MB_CHAR2BYTES(fillchar, p); + } + minwid = 0; + } + else + minwid *= -1; + for (; *t && p + 1 < out + outlen; t++) + { + // Change a space by fillchar, unless fillchar is '-' and a + // digit follows. + if (fillable && *t == ' ' + && (!VIM_ISDIGIT(*(t + 1)) || fillchar != '-')) + MB_CHAR2BYTES(fillchar, p); + else + *p++ = *t; + } + for (; l < minwid && p + 1 < out + outlen; l++) + MB_CHAR2BYTES(fillchar, p); + } + else if (num >= 0) + { + int nbase = (base == 'D' ? 10 : (base == 'O' ? 8 : 16)); + char_u nstr[20]; + + if (p + 20 >= out + outlen) + break; // not sufficient space + prevchar_isitem = TRUE; + t = nstr; + if (opt == STL_VIRTCOL_ALT) + { + *t++ = '-'; + minwid--; + } + *t++ = '%'; + if (zeropad) + *t++ = '0'; + *t++ = '*'; + *t++ = nbase == 16 ? base : (char_u)(nbase == 8 ? 'o' : 'd'); + *t = 0; + + for (n = num, l = 1; n >= nbase; n /= nbase) + l++; + if (opt == STL_VIRTCOL_ALT) + l++; + if (l > maxwid) + { + l += 2; + n = l - maxwid; + while (l-- > maxwid) + num /= nbase; + *t++ = '>'; + *t++ = '%'; + *t = t[-3]; + *++t = 0; + vim_snprintf((char *)p, outlen - (p - out), (char *)nstr, + 0, num, n); + } + else + vim_snprintf((char *)p, outlen - (p - out), (char *)nstr, + minwid, num); + p += STRLEN(p); + } + else + stl_items[curitem].stl_type = Empty; + + if (num >= 0 || (!itemisflag && str != NULL && *str != NUL)) + prevchar_isflag = FALSE; // Item not NULL, but not a flag + // + if (opt == STL_VIM_EXPR) + vim_free(str); + curitem++; + } + *p = NUL; + itemcnt = curitem; + +#ifdef FEAT_EVAL + if (usefmt != fmt) + vim_free(usefmt); +#endif + + width = vim_strsize(out); + if (maxwidth > 0 && width > maxwidth) + { + // Result is too long, must truncate somewhere. + l = 0; + if (itemcnt == 0) + s = out; + else + { + for ( ; l < itemcnt; l++) + if (stl_items[l].stl_type == Trunc) + { + // Truncate at %< item. + s = stl_items[l].stl_start; + break; + } + if (l == itemcnt) + { + // No %< item, truncate first item. + s = stl_items[0].stl_start; + l = 0; + } + } + + if (width - vim_strsize(s) >= maxwidth) + { + // Truncation mark is beyond max length + if (has_mbyte) + { + s = out; + width = 0; + for (;;) + { + width += ptr2cells(s); + if (width >= maxwidth) + break; + s += (*mb_ptr2len)(s); + } + // Fill up for half a double-wide character. + while (++width < maxwidth) + MB_CHAR2BYTES(fillchar, s); + } + else + s = out + maxwidth - 1; + for (l = 0; l < itemcnt; l++) + if (stl_items[l].stl_start > s) + break; + itemcnt = l; + *s++ = '>'; + *s = 0; + } + else + { + if (has_mbyte) + { + n = 0; + while (width >= maxwidth) + { + width -= ptr2cells(s + n); + n += (*mb_ptr2len)(s + n); + } + } + else + n = width - maxwidth + 1; + p = s + n; + STRMOVE(s + 1, p); + *s = '<'; + + // Fill up for half a double-wide character. + while (++width < maxwidth) + { + s = s + STRLEN(s); + MB_CHAR2BYTES(fillchar, s); + *s = NUL; + } + + --n; // count the '<' + for (; l < itemcnt; l++) + { + if (stl_items[l].stl_start - n >= s) + stl_items[l].stl_start -= n; + else + stl_items[l].stl_start = s; + } + } + width = maxwidth; + } + else if (width < maxwidth && STRLEN(out) + maxwidth - width + 1 < outlen) + { + // Find how many separators there are, which we will use when + // figuring out how many groups there are. + int num_separators = 0; + + for (l = 0; l < itemcnt; l++) + { + if (stl_items[l].stl_type == Separate) + { + // Create an array of the start location for each separator + // mark. + stl_separator_locations[num_separators] = l; + num_separators++; + } + } + + // If we have separated groups, then we deal with it now + if (num_separators) + { + int standard_spaces; + int final_spaces; + + standard_spaces = (maxwidth - width) / num_separators; + final_spaces = (maxwidth - width) - + standard_spaces * (num_separators - 1); + for (l = 0; l < num_separators; l++) + { + int dislocation = (l == (num_separators - 1)) ? + final_spaces : standard_spaces; + dislocation *= MB_CHAR2LEN(fillchar); + char_u *start = stl_items[stl_separator_locations[l]].stl_start; + char_u *seploc = start + dislocation; + STRMOVE(seploc, start); + for (s = start; s < seploc;) + MB_CHAR2BYTES(fillchar, s); + + for (int i = stl_separator_locations[l] + 1; i < itemcnt; i++) + stl_items[i].stl_start += dislocation; + } + + width = maxwidth; + } + } + + // Store the info about highlighting. + if (hltab != NULL) + { + *hltab = stl_hltab; + sp = stl_hltab; + for (l = 0; l < itemcnt; l++) + { + if (stl_items[l].stl_type == Highlight) + { + sp->start = stl_items[l].stl_start; + sp->userhl = stl_items[l].stl_minwid; + sp++; + } + } + sp->start = NULL; + sp->userhl = 0; + } + + // Store the info about tab pages labels. + if (tabtab != NULL) + { + *tabtab = stl_tabtab; + sp = stl_tabtab; + for (l = 0; l < itemcnt; l++) + { + if (stl_items[l].stl_type == TabPage) + { + sp->start = stl_items[l].stl_start; + sp->userhl = stl_items[l].stl_minwid; + sp++; + } + } + sp->start = NULL; + sp->userhl = 0; + } + + redraw_not_allowed = save_redraw_not_allowed; + + // A user function may reset KeyTyped, restore it. + KeyTyped = save_KeyTyped; + + // Check for an error. If there is one the display will be messed up and + // might loop redrawing. Avoid that by making the corresponding option + // empty. + // TODO: find out why using called_emsg_before makes tests fail, does it + // matter? + // if (called_emsg > called_emsg_before) + if (did_emsg > did_emsg_before) + set_string_option_direct(opt_name, -1, (char_u *)"", + OPT_FREE | opt_scope, SID_ERROR); + + return width; +} +#endif // FEAT_STL_OPT + +/* + * Get relative cursor position in window into "buf[buflen]", in the form 99%, + * using "Top", "Bot" or "All" when appropriate. + */ + void +get_rel_pos( + win_T *wp, + char_u *buf, + int buflen) +{ + long above; // number of lines above window + long below; // number of lines below window + + if (buflen < 3) // need at least 3 chars for writing + return; + above = wp->w_topline - 1; +#ifdef FEAT_DIFF + above += diff_check_fill(wp, wp->w_topline) - wp->w_topfill; + if (wp->w_topline == 1 && wp->w_topfill >= 1) + above = 0; // All buffer lines are displayed and there is an + // indication of filler lines, that can be considered + // seeing all lines. +#endif + below = wp->w_buffer->b_ml.ml_line_count - wp->w_botline + 1; + if (below <= 0) + vim_strncpy(buf, (char_u *)(above == 0 ? _("All") : _("Bot")), + (size_t)(buflen - 1)); + else if (above <= 0) + vim_strncpy(buf, (char_u *)_("Top"), (size_t)(buflen - 1)); + else + vim_snprintf((char *)buf, (size_t)buflen, "%2d%%", above > 1000000L + ? (int)(above / ((above + below) / 100L)) + : (int)(above * 100L / (above + below))); +} + +/* + * Append (file 2 of 8) to "buf[buflen]", if editing more than one file. + * Return TRUE if it was appended. + */ + static int +append_arg_number( + win_T *wp, + char_u *buf, + int buflen, + int add_file) // Add "file" before the arg number +{ + char_u *p; + + if (ARGCOUNT <= 1) // nothing to do + return FALSE; + + p = buf + STRLEN(buf); // go to the end of the buffer + if (p - buf + 35 >= buflen) // getting too long + return FALSE; + *p++ = ' '; + *p++ = '('; + if (add_file) + { + STRCPY(p, "file "); + p += 5; + } + vim_snprintf((char *)p, (size_t)(buflen - (p - buf)), + wp->w_arg_idx_invalid ? "(%d) of %d)" + : "%d of %d)", wp->w_arg_idx + 1, ARGCOUNT); + return TRUE; +} + +/* + * If fname is not a full path, make it a full path. + * Returns pointer to allocated memory (NULL for failure). + */ + char_u * +fix_fname(char_u *fname) +{ + /* + * Force expanding the path always for Unix, because symbolic links may + * mess up the full path name, even though it starts with a '/'. + * Also expand when there is ".." in the file name, try to remove it, + * because "c:/src/../README" is equal to "c:/README". + * Similarly "c:/src//file" is equal to "c:/src/file". + * For MS-Windows also expand names like "longna~1" to "longname". + */ +#ifdef UNIX + return FullName_save(fname, TRUE); +#else + if (!vim_isAbsName(fname) + || strstr((char *)fname, "..") != NULL + || strstr((char *)fname, "//") != NULL +# ifdef BACKSLASH_IN_FILENAME + || strstr((char *)fname, "\\\\") != NULL +# endif +# if defined(MSWIN) + || vim_strchr(fname, '~') != NULL +# endif + ) + return FullName_save(fname, FALSE); + + fname = vim_strsave(fname); + +# ifdef USE_FNAME_CASE + if (fname != NULL) + fname_case(fname, 0); // set correct case for file name +# endif + + return fname; +#endif +} + +/* + * 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 allocated. + */ + void +fname_expand( + buf_T *buf UNUSED, + char_u **ffname, + char_u **sfname) +{ + if (*ffname == NULL) // no file name given, nothing to do + return; + if (*sfname == NULL) // no short file name given, use ffname + *sfname = *ffname; + *ffname = fix_fname(*ffname); // expand to full path + +#ifdef FEAT_SHORTCUT + if (!buf->b_p_bin) + { + char_u *rfname; + + // If the file name is a shortcut file, use the file it links to. + rfname = mch_resolve_path(*ffname, FALSE); + if (rfname != NULL) + { + vim_free(*ffname); + *ffname = rfname; + *sfname = rfname; + } + } +#endif +} + +/* + * Open a window for a number of buffers. + */ + void +ex_buffer_all(exarg_T *eap) +{ + buf_T *buf; + win_T *wp, *wpnext; + int split_ret = OK; + int p_ea_save; + int open_wins = 0; + int r; + int count; // Maximum number of windows to open. + int all; // When TRUE also load inactive buffers. + int had_tab = cmdmod.cmod_tab; + tabpage_T *tpnext; + + if (eap->addr_count == 0) // make as many windows as possible + count = 9999; + else + count = eap->line2; // make as many windows as specified + if (eap->cmdidx == CMD_unhide || eap->cmdidx == CMD_sunhide) + all = FALSE; + else + all = TRUE; + + // Stop Visual mode, the cursor and "VIsual" may very well be invalid after + // switching to another buffer. + reset_VIsual_and_resel(); + + setpcmark(); + +#ifdef FEAT_GUI + need_mouse_correct = TRUE; +#endif + + /* + * Close superfluous windows (two windows for the same buffer). + * Also close windows that are not full-width. + */ + if (had_tab > 0) + goto_tabpage_tp(first_tabpage, TRUE, TRUE); + for (;;) + { + tpnext = curtab->tp_next; + for (wp = firstwin; wp != NULL; wp = wpnext) + { + wpnext = wp->w_next; + if ((wp->w_buffer->b_nwindows > 1 + || ((cmdmod.cmod_split & WSP_VERT) + ? wp->w_height + wp->w_status_height < Rows - p_ch + - tabline_height() + : wp->w_width != Columns) + || (had_tab > 0 && wp != firstwin)) + && !ONE_WINDOW + && !(wp->w_closing || wp->w_buffer->b_locked > 0) + && !win_unlisted(wp)) + { + if (win_close(wp, FALSE) == FAIL) + break; + // Just in case an autocommand does something strange with + // windows: start all over... + wpnext = firstwin; + tpnext = first_tabpage; + open_wins = 0; + } + else + ++open_wins; + } + + // Without the ":tab" modifier only do the current tab page. + if (had_tab == 0 || tpnext == NULL) + break; + goto_tabpage_tp(tpnext, TRUE, TRUE); + } + + /* + * Go through the buffer list. When a buffer doesn't have a window yet, + * open one. Otherwise move the window to the right position. + * Watch out for autocommands that delete buffers or windows! + */ + // Don't execute Win/Buf Enter/Leave autocommands here. + ++autocmd_no_enter; + win_enter(lastwin, FALSE); + ++autocmd_no_leave; + for (buf = firstbuf; buf != NULL && open_wins < count; buf = buf->b_next) + { + // Check if this buffer needs a window + if ((!all && buf->b_ml.ml_mfp == NULL) || !buf->b_p_bl) + continue; + + if (had_tab != 0) + { + // With the ":tab" modifier don't move the window. + if (buf->b_nwindows > 0) + wp = lastwin; // buffer has a window, skip it + else + wp = NULL; + } + else + { + // Check if this buffer already has a window + FOR_ALL_WINDOWS(wp) + if (wp->w_buffer == buf) + break; + // If the buffer already has a window, move it + if (wp != NULL) + win_move_after(wp, curwin); + } + + if (wp == NULL && split_ret == OK) + { + bufref_T bufref; + + set_bufref(&bufref, buf); + + // Split the window and put the buffer in it + p_ea_save = p_ea; + p_ea = TRUE; // use space from all windows + split_ret = win_split(0, WSP_ROOM | WSP_BELOW); + ++open_wins; + p_ea = p_ea_save; + if (split_ret == FAIL) + continue; + + // Open the buffer in this window. + swap_exists_action = SEA_DIALOG; + set_curbuf(buf, DOBUF_GOTO); + if (!bufref_valid(&bufref)) + { + // autocommands deleted the buffer!!! + swap_exists_action = SEA_NONE; + break; + } + if (swap_exists_action == SEA_QUIT) + { +#if defined(FEAT_EVAL) + cleanup_T cs; + + // Reset the error/interrupt/exception state here so that + // aborting() returns FALSE when closing a window. + enter_cleanup(&cs); +#endif + + // User selected Quit at ATTENTION prompt; close this window. + win_close(curwin, TRUE); + --open_wins; + swap_exists_action = SEA_NONE; + swap_exists_did_quit = TRUE; + +#if defined(FEAT_EVAL) + // Restore the error/interrupt/exception state if not + // discarded by a new aborting error, interrupt, or uncaught + // exception. + leave_cleanup(&cs); +#endif + } + else + handle_swap_exists(NULL); + } + + ui_breakcheck(); + if (got_int) + { + (void)vgetc(); // only break the file loading, not the rest + break; + } +#ifdef FEAT_EVAL + // Autocommands deleted the buffer or aborted script processing!!! + if (aborting()) + break; +#endif + // When ":tab" was used open a new tab for a new window repeatedly. + if (had_tab > 0 && tabpage_index(NULL) <= p_tpm) + cmdmod.cmod_tab = 9999; + } + --autocmd_no_enter; + win_enter(firstwin, FALSE); // back to first window + --autocmd_no_leave; + + /* + * Close superfluous windows. + */ + for (wp = lastwin; open_wins > count; ) + { + r = (buf_hide(wp->w_buffer) || !bufIsChanged(wp->w_buffer) + || autowrite(wp->w_buffer, FALSE) == OK); + if (!win_valid(wp)) + { + // BufWrite Autocommands made the window invalid, start over + wp = lastwin; + } + else if (r) + { + win_close(wp, !buf_hide(wp->w_buffer)); + --open_wins; + wp = lastwin; + } + else + { + wp = wp->w_prev; + if (wp == NULL) + break; + } + } +} + + +static int chk_modeline(linenr_T, int); + +/* + * do_modelines() - process mode lines for the current file + * + * "flags" can be: + * OPT_WINONLY only set options local to window + * OPT_NOWIN don't set options local to window + * + * Returns immediately if the "ml" option isn't set. + */ + void +do_modelines(int flags) +{ + linenr_T lnum; + int nmlines; + static int entered = 0; + + if (!curbuf->b_p_ml || (nmlines = (int)p_mls) == 0) + return; + + // Disallow recursive entry here. Can happen when executing a modeline + // triggers an autocommand, which reloads modelines with a ":do". + if (entered) + return; + + ++entered; + for (lnum = 1; curbuf->b_p_ml && lnum <= curbuf->b_ml.ml_line_count && lnum <= nmlines; + ++lnum) + if (chk_modeline(lnum, flags) == FAIL) + nmlines = 0; + + for (lnum = curbuf->b_ml.ml_line_count; curbuf->b_p_ml && lnum > 0 && lnum > nmlines + && lnum > curbuf->b_ml.ml_line_count - nmlines; --lnum) + if (chk_modeline(lnum, flags) == FAIL) + nmlines = 0; + --entered; +} + +#include "version.h" // for version number + +/* + * chk_modeline() - check a single line for a mode string + * Return FAIL if an error encountered. + */ + static int +chk_modeline( + linenr_T lnum, + int flags) // Same as for do_modelines(). +{ + char_u *s; + char_u *e; + char_u *linecopy; // local copy of any modeline found + int prev; + int vers; + int end; + int retval = OK; + sctx_T save_current_sctx; + + ESTACK_CHECK_DECLARATION + + prev = -1; + for (s = ml_get(lnum); *s != NUL; ++s) + { + if (prev == -1 || vim_isspace(prev)) + { + if ((prev != -1 && STRNCMP(s, "ex:", (size_t)3) == 0) + || STRNCMP(s, "vi:", (size_t)3) == 0) + break; + // Accept both "vim" and "Vim". + if ((s[0] == 'v' || s[0] == 'V') && s[1] == 'i' && s[2] == 'm') + { + if (s[3] == '<' || s[3] == '=' || s[3] == '>') + e = s + 4; + else + e = s + 3; + vers = getdigits(&e); + if (*e == ':' + && (s[0] != 'V' + || STRNCMP(skipwhite(e + 1), "set", 3) == 0) + && (s[3] == ':' + || (VIM_VERSION_100 >= vers && isdigit(s[3])) + || (VIM_VERSION_100 < vers && s[3] == '<') + || (VIM_VERSION_100 > vers && s[3] == '>') + || (VIM_VERSION_100 == vers && s[3] == '='))) + break; + } + } + prev = *s; + } + + if (*s) + { + do // skip over "ex:", "vi:" or "vim:" + ++s; + while (s[-1] != ':'); + + s = linecopy = vim_strsave(s); // copy the line, it will change + if (linecopy == NULL) + return FAIL; + + // prepare for emsg() + estack_push(ETYPE_MODELINE, (char_u *)"modelines", lnum); + ESTACK_CHECK_SETUP + + end = FALSE; + while (end == FALSE) + { + s = skipwhite(s); + if (*s == NUL) + break; + + /* + * Find end of set command: ':' or end of line. + * Skip over "\:", replacing it with ":". + */ + for (e = s; *e != ':' && *e != NUL; ++e) + if (e[0] == '\\' && e[1] == ':') + STRMOVE(e, e + 1); + if (*e == NUL) + end = TRUE; + + /* + * If there is a "set" command, require a terminating ':' and + * ignore the stuff after the ':'. + * "vi:set opt opt opt: foo" -- foo not interpreted + * "vi:opt opt opt: foo" -- foo interpreted + * Accept "se" for compatibility with Elvis. + */ + if (STRNCMP(s, "set ", (size_t)4) == 0 + || STRNCMP(s, "se ", (size_t)3) == 0) + { + if (*e != ':') // no terminating ':'? + break; + end = TRUE; + s = vim_strchr(s, ' ') + 1; + } + *e = NUL; // truncate the set command + + if (*s != NUL) // skip over an empty "::" + { + int secure_save = secure; + + save_current_sctx = current_sctx; + current_sctx.sc_version = 1; +#ifdef FEAT_EVAL + current_sctx.sc_sid = SID_MODELINE; + current_sctx.sc_seq = 0; + current_sctx.sc_lnum = lnum; +#endif + + // Make sure no risky things are executed as a side effect. + secure = 1; + + retval = do_set(s, OPT_MODELINE | OPT_LOCAL | flags); + + secure = secure_save; + current_sctx = save_current_sctx; + if (retval == FAIL) // stop if error found + break; + } + s = e + 1; // advance to next part + } + + ESTACK_CHECK_NOW + estack_pop(); + vim_free(linecopy); + } + return retval; +} + +/* + * Return TRUE if "buf" is a normal buffer, 'buftype' is empty. + */ + int +bt_normal(buf_T *buf) +{ + return buf != NULL && buf->b_p_bt[0] == NUL; +} + +/* + * Return TRUE if "buf" is the quickfix buffer. + */ + int +bt_quickfix(buf_T *buf UNUSED) +{ +#ifdef FEAT_QUICKFIX + return buf != NULL && buf->b_p_bt[0] == 'q'; +#else + return FALSE; +#endif +} + +/* + * Return TRUE if "buf" is a terminal buffer. + */ + int +bt_terminal(buf_T *buf UNUSED) +{ +#if defined(FEAT_TERMINAL) + return buf != NULL && buf->b_p_bt[0] == 't'; +#else + return FALSE; +#endif +} + +/* + * Return TRUE if "buf" is a help buffer. + */ + int +bt_help(buf_T *buf) +{ + return buf != NULL && buf->b_help; +} + +/* + * Return TRUE if "buf" is a prompt buffer. + */ + int +bt_prompt(buf_T *buf) +{ + return buf != NULL && buf->b_p_bt[0] == 'p' && buf->b_p_bt[1] == 'r'; +} + +#if defined(FEAT_PROP_POPUP) || defined(PROTO) +/* + * Return TRUE if "buf" is a buffer for a popup window. + */ + int +bt_popup(buf_T *buf) +{ + return buf != NULL && buf->b_p_bt != NULL + && buf->b_p_bt[0] == 'p' && buf->b_p_bt[1] == 'o'; +} +#endif + +/* + * Return TRUE if "buf" is a "nofile", "acwrite", "terminal" or "prompt" + * buffer. This means the buffer name may not be a file name, at least not for + * writing the buffer. + */ + int +bt_nofilename(buf_T *buf) +{ + return buf != NULL && ((buf->b_p_bt[0] == 'n' && buf->b_p_bt[2] == 'f') + || buf->b_p_bt[0] == 'a' + || buf->b_p_bt[0] == 't' + || buf->b_p_bt[0] == 'p'); +} + +/* + * Return TRUE if "buf" is a "nofile", "quickfix", "terminal" or "prompt" + * buffer. This means the buffer is not to be read from a file. + */ + static int +bt_nofileread(buf_T *buf) +{ + return buf != NULL && ((buf->b_p_bt[0] == 'n' && buf->b_p_bt[2] == 'f') + || buf->b_p_bt[0] == 't' + || buf->b_p_bt[0] == 'q' + || buf->b_p_bt[0] == 'p'); +} + +#if defined(FEAT_QUICKFIX) || defined(PROTO) +/* + * Return TRUE if "buf" has 'buftype' set to "nofile". + */ + int +bt_nofile(buf_T *buf) +{ + return buf != NULL && buf->b_p_bt[0] == 'n' && buf->b_p_bt[2] == 'f'; +} +#endif + +/* + * Return TRUE if "buf" is a "nowrite", "nofile", "terminal", "prompt", or + * "popup" buffer. + */ + int +bt_dontwrite(buf_T *buf) +{ + return buf != NULL && (buf->b_p_bt[0] == 'n' + || buf->b_p_bt[0] == 't' + || buf->b_p_bt[0] == 'p'); +} + + int +bt_dontwrite_msg(buf_T *buf) +{ + if (bt_dontwrite(buf)) + { + emsg(_(e_cannot_write_buftype_option_is_set)); + return TRUE; + } + return FALSE; +} + +/* + * Return TRUE if the buffer should be hidden, according to 'hidden', ":hide" + * and 'bufhidden'. + */ + int +buf_hide(buf_T *buf) +{ + // 'bufhidden' overrules 'hidden' and ":hide", check it first + switch (buf->b_p_bh[0]) + { + case 'u': // "unload" + case 'w': // "wipe" + case 'd': return FALSE; // "delete" + case 'h': return TRUE; // "hide" + } + return (p_hid || (cmdmod.cmod_flags & CMOD_HIDE)); +} + +/* + * Return special buffer name. + * Returns NULL when the buffer has a normal file name. + */ + char_u * +buf_spname(buf_T *buf) +{ +#if defined(FEAT_QUICKFIX) + if (bt_quickfix(buf)) + { + /* + * Differentiate between the quickfix and location list buffers using + * the buffer number stored in the global quickfix stack. + */ + if (buf->b_fnum == qf_stack_get_bufnr()) + return (char_u *)_(msg_qflist); + else + return (char_u *)_(msg_loclist); + } +#endif + + // There is no _file_ when 'buftype' is "nofile", b_sfname + // contains the name as specified by the user. + if (bt_nofilename(buf)) + { +#ifdef FEAT_TERMINAL + if (buf->b_term != NULL) + return term_get_status_text(buf->b_term); +#endif + if (buf->b_fname != NULL) + return buf->b_fname; +#ifdef FEAT_JOB_CHANNEL + if (bt_prompt(buf)) + return (char_u *)_("[Prompt]"); +#endif +#ifdef FEAT_PROP_POPUP + if (bt_popup(buf)) + return (char_u *)_("[Popup]"); +#endif + return (char_u *)_("[Scratch]"); + } + + if (buf->b_fname == NULL) + return buf_get_fname(buf); + return NULL; +} + +/* + * Get "buf->b_fname", use "[No Name]" if it is NULL. + */ + char_u * +buf_get_fname(buf_T *buf) +{ + if (buf->b_fname == NULL) + return (char_u *)_("[No Name]"); + return buf->b_fname; +} + +/* + * Set 'buflisted' for curbuf to "on" and trigger autocommands if it changed. + */ + void +set_buflisted(int on) +{ + if (on == curbuf->b_p_bl) + return; + + curbuf->b_p_bl = on; + if (on) + apply_autocmds(EVENT_BUFADD, NULL, NULL, FALSE, curbuf); + else + apply_autocmds(EVENT_BUFDELETE, NULL, NULL, FALSE, curbuf); +} + +/* + * Read the file for "buf" again and check if the contents changed. + * Return TRUE if it changed or this could not be checked. + */ + int +buf_contents_changed(buf_T *buf) +{ + buf_T *newbuf; + int differ = TRUE; + linenr_T lnum; + aco_save_T aco; + exarg_T ea; + + // Allocate a buffer without putting it in the buffer list. + newbuf = buflist_new(NULL, NULL, (linenr_T)1, BLN_DUMMY); + if (newbuf == NULL) + return TRUE; + + // Force the 'fileencoding' and 'fileformat' to be equal. + if (prep_exarg(&ea, buf) == FAIL) + { + wipe_buffer(newbuf, FALSE); + return TRUE; + } + + // Set curwin/curbuf to buf and save a few things. + aucmd_prepbuf(&aco, newbuf); + if (curbuf != newbuf) + { + // Failed to find a window for "newbuf". + wipe_buffer(newbuf, FALSE); + return TRUE; + } + + if (ml_open(curbuf) == OK + && readfile(buf->b_ffname, buf->b_fname, + (linenr_T)0, (linenr_T)0, (linenr_T)MAXLNUM, + &ea, READ_NEW | READ_DUMMY) == OK) + { + // compare the two files line by line + if (buf->b_ml.ml_line_count == curbuf->b_ml.ml_line_count) + { + differ = FALSE; + for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count; ++lnum) + if (STRCMP(ml_get_buf(buf, lnum, FALSE), ml_get(lnum)) != 0) + { + differ = TRUE; + break; + } + } + } + vim_free(ea.cmd); + + // restore curwin/curbuf and a few other things + aucmd_restbuf(&aco); + + if (curbuf != newbuf) // safety check + wipe_buffer(newbuf, FALSE); + + return differ; +} + +/* + * Wipe out a buffer and decrement the last buffer number if it was used for + * this buffer. Call this to wipe out a temp buffer that does not contain any + * marks. + */ + void +wipe_buffer( + buf_T *buf, + int aucmd) // When TRUE trigger autocommands. +{ + if (buf->b_fnum == top_file_num - 1) + --top_file_num; + + if (!aucmd) // Don't trigger BufDelete autocommands here. + block_autocmds(); + + close_buffer(NULL, buf, DOBUF_WIPE, FALSE, TRUE); + + if (!aucmd) + unblock_autocmds(); +} |