// This sample is from Neovim's 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 #include #include #include #include "nvim/api/private/handle.h" #include "nvim/api/private/helpers.h" #include "nvim/api/vim.h" #include "nvim/ascii.h" #include "nvim/assert.h" #include "nvim/channel.h" #include "nvim/vim.h" #include "nvim/buffer.h" #include "nvim/change.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/diff.h" #include "nvim/digraph.h" #include "nvim/eval.h" #include "nvim/ex_cmds2.h" #include "nvim/ex_cmds.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" #include "nvim/ex_getln.h" #include "nvim/fileio.h" #include "nvim/file_search.h" #include "nvim/fold.h" #include "nvim/getchar.h" #include "nvim/hashtab.h" #include "nvim/highlight.h" #include "nvim/indent.h" #include "nvim/indent_c.h" #include "nvim/main.h" #include "nvim/mark.h" #include "nvim/extmark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/misc1.h" #include "nvim/garray.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/os_unix.h" #include "nvim/path.h" #include "nvim/quickfix.h" #include "nvim/regexp.h" #include "nvim/screen.h" #include "nvim/sign.h" #include "nvim/spell.h" #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/version.h" #include "nvim/window.h" #include "nvim/shada.h" #include "nvim/os/os.h" #include "nvim/os/time.h" #include "nvim/os/input.h" #include "nvim/buffer_updates.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "buffer.c.generated.h" #endif static char *msg_loclist = N_("[Location List]"); static char *msg_qflist = N_("[Quickfix List]"); static char *e_auabort = N_("E855: Autocommands caused command to abort"); // Number of times free_buffer() was called. static int buf_free_count = 0; typedef enum { kBffClearWinInfo = 1, kBffInitChangedtick = 2, } BufFreeFlags; // 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, (linenr_T)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, false); } } else { // Delete the converted lines. while (curbuf->b_ml.ml_line_count > line_count) { ml_delete(line_count, false); } } // 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 != FAIL) { unchanged(curbuf, false, true); } apply_autocmds_retval(EVENT_STDINREADPOST, NULL, NULL, false, curbuf, &retval); } return retval; } // 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 // extra flags for readfile() ) { int retval = OK; bufref_T old_curbuf; long old_tw = curbuf->b_p_tw; 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); curbuf = NULL; FOR_ALL_BUFFERS(buf) { if (buf->b_ml.ml_mfp != NULL) { curbuf = buf; break; } } /* * if there is no memfile at all, exit * This is OK, since there are no changes to lose. */ if (curbuf == NULL) { EMSG(_("E82: Cannot allocate any buffer, exiting...")); getout(2); } EMSG(_("E83: Cannot allocate buffer, using other one...")); enter_buffer(curbuf); if (old_tw != curbuf->b_p_tw) { check_colorcolumn(curwin); } 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; if (curbuf->b_ffname != NULL) { int old_msg_silent = msg_silent; #ifdef UNIX int save_bin = curbuf->b_p_bin; int perm; perm = os_getperm((const char *)curbuf->b_ffname); if (perm >= 0 && (0 # ifdef S_ISFIFO || S_ISFIFO(perm) # endif # ifdef S_ISSOCK || S_ISSOCK(perm) # endif # 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; // 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 auto commands. // When reading stdin, the buffer contents always needs writing, so set // the changed flag. Unless in readonly mode: "ls | nvim -R -". // 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 || (aborting() && vim_strchr(p_cpo, CPO_INTMOD) != NULL)) { changed(); } else if (retval != FAIL && !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 = buf_get_changedtick(curbuf); curbuf->b_last_changedtick_pum = buf_get_changedtick(curbuf); // require "!" to overwrite the file, because it wasn't read completely if (aborting()) { curbuf->b_flags |= BF_READERR; } /* Need to update automatic folding. Do this before the autocommands, * they may use the fold info. */ foldUpdateAll(curwin); // need to set w_topline, unless some autocommand already did that. if (!(curwin->w_valid & VALID_TOPLINE)) { curwin->w_topline = 1; curwin->w_topfill = 0; } apply_autocmds_retval(EVENT_BUFENTER, NULL, NULL, false, curbuf, &retval); if (retval == FAIL) { return FAIL; } /* * 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. aucmd_prepbuf(&aco, old_curbuf.br_buf); do_modelines(0); curbuf->b_flags &= ~(BF_CHECK_RO | BF_NEVERLOADED); apply_autocmds_retval(EVENT_BUFWINENTER, NULL, NULL, false, curbuf, &retval); // restore curwin/curbuf and a few other things aucmd_restbuf(&aco); } return retval; } /// Store "buf" in "bufref" and set the free count. /// /// @param bufref Reference to be used for the buffer. /// @param buf The buffer to reference. 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. /// /// @param bufref Buffer reference to check for. bool 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; } /// Check that "buf" points to a valid buffer in the buffer list. /// /// Can be slow if there are many buffers, prefer using bufref_valid(). /// /// @param buf The buffer to check for. bool buf_valid(buf_T *buf) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { if (buf == NULL) { return false; } // Assume that we more often have a recent buffer, // start with the last one. for (buf_T *bp = lastbuf; bp != NULL; bp = bp->b_prev) { if (bp == buf) { return true; } } return false; } /// Close the link to a buffer. /// /// @param win If not NULL, set b_last_cursor. /// @param buf /// @param action Used when there is no longer a window for the buffer. /// Possible values: /// 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 /// 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. /// @param abort_if_last /// If true, 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. void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last) { bool unload_buf = (action != 0); bool del_buf = (action == DOBUF_DEL || action == DOBUF_WIPE); bool wipe_buf = (action == DOBUF_WIPE); bool is_curwin = (curwin != NULL && curwin->w_buffer == buf); win_T *the_curwin = curwin; tabpage_T *the_curtab = curtab; // Force unloading or deleting when 'bufhidden' says so, but not for terminal // buffers. // The caller must take care of NOT deleting/freeing when 'bufhidden' is // "hide" (otherwise we could never free or delete a buffer). if (!buf->terminal) { 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; } if (buf->terminal && (unload_buf || del_buf || wipe_buf)) { // terminal buffers can only be wiped unload_buf = true; del_buf = true; wipe_buf = true; } // Disallow deleting the buffer when it is locked (already being closed or // halfway a command that relies on it). Unloading is allowed. if (buf->b_locked > 0 && (del_buf || wipe_buf)) { EMSG(_("E937: Attempt to delete a buffer that is in use")); return; } if (win != NULL // Avoid bogus clang warning. && 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); } bufref_T bufref; set_bufref(&bufref, buf); // When the buffer is no longer in a window, trigger BufWinLeave if (buf->b_nwindows == 1) { buf->b_locked++; if (apply_autocmds(EVENT_BUFWINLEAVE, buf->b_fname, buf->b_fname, false, buf) && !bufref_valid(&bufref)) { // Autocommands deleted the buffer. EMSG(_(e_auabort)); return; } buf->b_locked--; if (abort_if_last && last_nonfloat(win)) { // Autocommands made this the only window. EMSG(_(e_auabort)); return; } // When the buffer becomes hidden, but is not unloaded, trigger // BufHidden if (!unload_buf) { buf->b_locked++; if (apply_autocmds(EVENT_BUFHIDDEN, buf->b_fname, buf->b_fname, false, buf) && !bufref_valid(&bufref)) { // Autocommands deleted the buffer. EMSG(_(e_auabort)); return; } buf->b_locked--; if (abort_if_last && last_nonfloat(win)) { // Autocommands made this the only window. EMSG(_(e_auabort)); return; } } if (aborting()) { // autocmds may abort script processing return; } } // 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(); } int nwindows = buf->b_nwindows; // decrease the link count from windows (unless not in any window) if (buf->b_nwindows > 0) { buf->b_nwindows--; } if (diffopt_hiddenoff() && !unload_buf && buf->b_nwindows == 0) { diff_buf_delete(buf); // Clear 'diff' for hidden buffer. } /* Return when a window is displaying the buffer or when it's not * unloaded. */ if (buf->b_nwindows > 0 || !unload_buf) { return; } if (buf->terminal) { terminal_close(buf->terminal, NULL); } // Always remove the buffer when there is no file name. if (buf->b_ffname == NULL) { del_buf = true; } /* * 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. */ bool is_curbuf = (buf == curbuf); // When closing the current buffer stop Visual mode before freeing // anything. if (is_curbuf && VIsual_active #if defined(EXITFREE) && !entered_free_all_mem #endif ) { end_visual_mode(); } buf->b_nwindows = nwindows; buf_freeall(buf, (del_buf ? BFA_DEL : 0) + (wipe_buf ? BFA_WIPE : 0)); if (!bufref_valid(&bufref)) { // Autocommands may have deleted the buffer. return; } if (aborting()) { // Autocmds may abort script processing. return; } /* * 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; } if (win != NULL // Avoid bogus clang warning. && 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--; } // Change directories when the 'acd' option is set. do_autochdir(); // Disable buffer-updates for the current buffer. // No need to check `unload_buf`: in that case the function returned above. buf_updates_unregister_all(buf); /* * Remove the buffer from the list. */ if (wipe_buf) { xfree(buf->b_ffname); xfree(buf->b_sfname); 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, kBffClearWinInfo | kBffInitChangedtick); // 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; } } } /// 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_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 } /// Clears the current buffer contents. void buf_clear(void) { linenr_T line_count = curbuf->b_ml.ml_line_count; while (!(curbuf->b_ml.ml_flags & ML_EMPTY)) { ml_delete((linenr_T)1, false); } deleted_lines_mark(1, line_count); // prepare for display ml_close(curbuf, true); // free memline_T buf_clear_file(curbuf); } /// buf_freeall() - free all things allocated for a buffer that are related to /// the file. Careful: get here with "curwin" NULL when exiting. /// /// @param flags BFA_DEL buffer is going to be deleted /// BFA_WIPE buffer is going to be wiped out /// BFA_KEEP_UNDO do not free undo information void buf_freeall(buf_T *buf, int flags) { bool is_curbuf = (buf == curbuf); 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++; bufref_T bufref; set_bufref(&bufref, buf); if ((buf->b_ml.ml_mfp != NULL) && 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 && apply_autocmds(EVENT_BUFDELETE, buf->b_fname, buf->b_fname, false, buf) && !bufref_valid(&bufref)) { // Autocommands may delete the buffer. return; } if ((flags & BFA_WIPE) && apply_autocmds(EVENT_BUFWIPEOUT, buf->b_fname, buf->b_fname, false, buf) && !bufref_valid(&bufref)) { // Autocommands may delete the buffer. return; } buf->b_locked--; // 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(); } if (aborting()) { // autocmds may abort script processing return; } /* * 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; } diff_buf_delete(buf); // Can't use 'diff' for unloaded buffer. // Remove any ownsyntax, unless exiting. if (curwin != NULL && curwin->w_buffer == buf) { reset_synblock(curwin); } // No folds in an empty buffer. FOR_ALL_TAB_WINDOWS(tp, win) { if (win->w_buffer == buf) { clearFolding(win); } } 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 } syntax_clear(&buf->b_s); // reset syntax info 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) { handle_unregister_buffer(buf); buf_free_count++; // b:changedtick uses an item in buf_T. free_buffer_stuff(buf, kBffClearWinInfo); if (buf->b_vars->dv_refcount > DO_NOT_FREE_CNT) { tv_dict_add(buf->b_vars, tv_dict_item_copy((dictitem_T *)(&buf->changedtick_di))); } unref_var_dict(buf->b_vars); aubuflocal_remove(buf); tv_dict_unref(buf->additional_data); xfree(buf->b_prompt_text); callback_free(&buf->b_prompt_callback); callback_free(&buf->b_prompt_interrupt); clear_fmark(&buf->b_last_cursor); clear_fmark(&buf->b_last_insert); clear_fmark(&buf->b_last_change); for (size_t i = 0; i < NMARKS; i++) { free_fmark(buf->b_namedm[i]); } for (int i = 0; i < buf->b_changelistlen; i++) { free_fmark(buf->b_changelist[i]); } if (autocmd_busy) { // Do not free the buffer structure while autocommands are executing, // it's still needed. Free it when autocmd_busy is reset. memset(&buf->b_namedm[0], 0, sizeof(buf->b_namedm)); memset(&buf->b_changelist[0], 0, sizeof(buf->b_changelist)); buf->b_next = au_pending_free_buf; au_pending_free_buf = buf; } else { xfree(buf); } } /// Free stuff in the buffer for ":bdel" and when wiping out the buffer. /// /// @param buf Buffer pointer /// @param free_flags BufFreeFlags static void free_buffer_stuff(buf_T *buf, int free_flags) { if (free_flags & kBffClearWinInfo) { clear_wininfo(buf); // including window-local options free_buf_options(buf, true); ga_clear(&buf->b_s.b_langp); } { // Avoid losing b:changedtick when deleting buffer: clearing variables // implies using clear_tv() on b:changedtick and that sets changedtick to // zero. hashitem_T *const changedtick_hi = hash_find( &buf->b_vars->dv_hashtab, (const char_u *)"changedtick"); assert(changedtick_hi != NULL); hash_remove(&buf->b_vars->dv_hashtab, changedtick_hi); } vars_clear(&buf->b_vars->dv_hashtab); // free all internal variables hash_init(&buf->b_vars->dv_hashtab); if (free_flags & kBffInitChangedtick) { buf_init_changedtick(buf); } uc_clear(&buf->b_ucmds); // clear local user commands buf_delete_signs(buf, (char_u *)"*"); // delete any signs extmark_free_all(buf); // delete any extmarks map_clear_int(buf, MAP_ALL_MODES, true, false); // clear local mappings map_clear_int(buf, MAP_ALL_MODES, true, true); // clear local abbrevs XFREE_CLEAR(buf->b_start_fenc); buf_updates_unregister_all(buf); } /* * 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; if (wip->wi_optset) { clear_winopt(&wip->wi_opt); deleteFoldRecurse(&wip->wi_folds); } xfree(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; set_bufref(&old_curbuf, curbuf); 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') { cleanup_T cs; // Reset the error/interrupt/exception state here so that // aborting() returns false when closing a window. enter_cleanup(&cs); // Quitting means closing the split window, nothing else. win_close(curwin, true); swap_exists_action = SEA_NONE; swap_exists_did_quit = true; /* Restore the error/interrupt/exception state if not discarded by a * new aborting error, interrupt, or uncaught exception. */ leave_cleanup(&cs); } 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. /// /// @param old_curbuf The buffer to check for. void handle_swap_exists(bufref_T *old_curbuf) { cleanup_T cs; long old_tw = curbuf->b_p_tw; buf_T *buf; if (swap_exists_action == SEA_QUIT) { // Reset the error/interrupt/exception state here so that // aborting() returns false when closing a buffer. enter_cleanup(&cs); // 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); if (old_curbuf == NULL || !bufref_valid(old_curbuf) || old_curbuf->br_buf == curbuf) { buf = buflist_new(NULL, NULL, 1L, BLN_CURBUF | BLN_LISTED); } 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; if (old_tw != curbuf->b_p_tw) { check_colorcolumn(curwin); } } // If "old_curbuf" is NULL we are in big trouble here... /* Restore the error/interrupt/exception state if not discarded by a * new aborting error, interrupt, or uncaught exception. */ leave_cleanup(&cs); } else if (swap_exists_action == SEA_RECOVER) { // Reset the error/interrupt/exception state here so that // aborting() returns false when closing a buffer. enter_cleanup(&cs); // 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); /* Restore the error/interrupt/exception state if not discarded by a * new aborting error, interrupt, or uncaught exception. */ leave_cleanup(&cs); } swap_exists_action = SEA_NONE; // -V519 } /* * 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_u * 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_u *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 (char_u *)_(e_trailing); } bnr = start_bnr; } else { // addr_count == 1 bnr = end_bnr; } for (; !got_int; os_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(command, DOBUF_FIRST, FORWARD, bnr, forceit) == 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 (!ascii_isdigit(*arg)) { p = skiptowhite_esc(arg); bnr = buflist_findpat(arg, p, command == DOBUF_WIPE, false, false); if (bnr < 0) { // failed break; } arg = p; } else { bnr = getdigits_int(&arg, false, 0); } } } 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, _("E515: No buffers were unloaded")); } else if (command == DOBUF_DEL) { STRCPY(IObuff, _("E516: No buffers were deleted")); } else { STRCPY(IObuff, _("E517: No buffers were wiped out")); } errormsg = IObuff; } else if (deleted >= p_report) { if (command == DOBUF_UNLOAD) { if (deleted == 1) { MSG(_("1 buffer unloaded")); } else { smsg(_("%d buffers unloaded"), deleted); } } else if (command == DOBUF_DEL) { if (deleted == 1) { MSG(_("1 buffer deleted")); } else { smsg(_("%d buffers deleted"), deleted); } } else { if (deleted == 1) { MSG(_("1 buffer wiped out")); } else { smsg(_("%d buffers wiped out"), deleted); } } } } return errormsg; } /* * 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; if (action == DOBUF_UNLOAD) { EMSG(_("E90: Cannot unload last buffer")); return FAIL; } bufref_T bufref; 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); } 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 * * 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. */ int do_buffer( int action, int start, int dir, // FORWARD or BACKWARD int count, // buffer number or number of buffers int forceit // true for :...! ) { buf_T *buf; buf_T *bp; int unload = (action == DOBUF_UNLOAD || action == DOBUF_DEL || action == DOBUF_WIPE); 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(_("E84: 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(_("E85: 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) { EMSGN(_(e_nobufnr), count); } } else if (dir == FORWARD) { EMSG(_("E87: Cannot go beyond last buffer")); } else { EMSG(_("E88: Cannot go before first buffer")); } return FAIL; } /* * delete buffer buf from memory and/or the list */ if (unload) { int forward; bufref_T bufref; set_bufref(&bufref, buf); /* When unloading or deleting a buffer that's already unloaded and * unlisted: fail silently. */ if (action != DOBUF_WIPE && buf->b_ml.ml_mfp == NULL && !buf->b_p_bl) { return FAIL; } if (!forceit && (buf->terminal || bufIsChanged(buf))) { if ((p_confirm || cmdmod.confirm) && p_write && !buf->terminal) { 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 { if (buf->terminal) { if (p_confirm || cmdmod.confirm) { if (!dialog_close_terminal(buf)) { return FAIL; } } else { EMSG2(_("E89: %s will be killed (add ! to override)"), (char *)buf->b_fname); return FAIL; } } else { EMSGN(_("E89: No write since last change for buffer %" PRId64 " (add ! to override)"), buf->b_fnum); 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. */ bp = NULL; FOR_ALL_BUFFERS(bp2) { if (bp2->b_p_bl && bp2 != buf) { bp = bp2; break; } } if (bp == NULL && buf == curbuf) { return empty_curbuf(true, 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); } 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) { if (buf == curbuf || !buf->b_p_bl) { buf = NULL; // skip current and unlisted bufs } 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) { 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(buf2) { if (buf2->b_p_bl && buf2 != curbuf) { buf = buf2; 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 (buf == NULL) { /* Autocommands must have wiped out all other buffers. Only option * now is to make the current buffer empty. */ return empty_curbuf(false, forceit, action); } /* * make buf 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, forceit)) { if ((p_confirm || cmdmod.confirm) && p_write) { 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; } } // Go to the other buffer. set_curbuf(buf, action); if (action == DOBUF_SPLIT) { RESET_BINDING(curwin); // reset 'scrollbind' and 'cursorbind' } if (aborting()) { // autocmds may abort script processing return FAIL; } return OK; } /* * 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 */ void set_curbuf(buf_T *buf, int action) { buf_T *prevbuf; int unload = (action == DOBUF_UNLOAD || action == DOBUF_DEL || action == DOBUF_WIPE); long old_tw = curbuf->b_p_tw; setpcmark(); if (!cmdmod.keepalt) { 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; bufref_T newbufref; bufref_T prevbufref; set_bufref(&prevbufref, prevbuf); set_bufref(&newbufref, buf); // Autocommands may delete the curren buffer and/or the buffer we wan 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) && !aborting())) { if (prevbuf == curwin->w_buffer) { reset_synblock(curwin); } if (unload) { close_windows(prevbuf, false); } if (bufref_valid(&prevbufref) && !aborting()) { win_T *previouswin = curwin; if (prevbuf == curbuf) { 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); 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 */ if ((buf_valid(buf) && buf != curbuf && !aborting() ) || curwin->w_buffer == NULL ) { enter_buffer(buf); if (old_tw != curbuf->b_p_tw) { check_colorcolumn(curwin); } } if (bufref_valid(&prevbufref) && prevbuf->terminal != NULL) { terminal_check_size(prevbuf->terminal); } } /* * Enter a new current buffer. * Old curbuf must have been abandoned already! This also means "curbuf" may * be pointing to freed memory. */ void enter_buffer(buf_T *buf) { // 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); } else { // Remove all folds in the window. clearFolding(curwin); } foldUpdateAll(curwin); // update folds (later). // Get the buffer in the current window. curwin->w_buffer = buf; curbuf = buf; curbuf->b_nwindows++; if (curwin->w_p_diff) { diff_buf_add(curbuf); } curwin->w_s = &(curbuf->b_s); // 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; // 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; curwin->w_topfill = 0; 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); // redisplay at correct position } // Change directories when the 'acd' option is set. do_autochdir(); if (curbuf->b_kmap_state & KEYMAP_INIT) { (void)keymap_init(); } // 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)did_set_spelllang(curwin); } redraw_later(NOT_VALID); } // Change to the directory of the current buffer. // Don't do this while still starting up. void do_autochdir(void) { if (p_acd) { if (starting == 0 && curbuf->b_ffname != NULL && vim_chdirfile(curbuf->b_ffname) == OK) { post_chdir(kCdScopeGlobal, false); shorten_fnames(true); } } } void no_write_message(void) { EMSG(_("E37: No write since last change (add ! to override)")); } void no_write_message_nobang(void) { EMSG(_("E37: No write since last change")); } // // functions for dealing with the buffer list // static int top_file_num = 1; ///< highest file number /// Initialize b:changedtick and changedtick_val attribute /// /// @param[out] buf Buffer to intialize for. static inline void buf_init_changedtick(buf_T *const buf) FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL { STATIC_ASSERT(sizeof("changedtick") <= sizeof(buf->changedtick_di.di_key), "buf->changedtick_di cannot hold large enough keys"); buf->changedtick_di = (ChangedtickDictItem) { .di_flags = DI_FLAGS_RO|DI_FLAGS_FIX, // Must not include DI_FLAGS_ALLOC. .di_tv = (typval_T) { .v_type = VAR_NUMBER, .v_lock = VAR_FIXED, .vval.v_number = buf_get_changedtick(buf), }, .di_key = "changedtick", }; tv_dict_add(buf->b_vars, (dictitem_T *)&buf->changedtick_di); }