diff options
Diffstat (limited to 'src/nvim/buffer.c')
-rw-r--r-- | src/nvim/buffer.c | 1828 |
1 files changed, 1156 insertions, 672 deletions
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 438a85dd5d..c15a6f1330 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -1,3 +1,6 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + /* * buffer.c: functions for dealing with the buffer structure */ @@ -19,8 +22,11 @@ #include <stdbool.h> #include <string.h> #include <inttypes.h> +#include <assert.h> #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/vim.h" @@ -36,9 +42,11 @@ #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" @@ -48,7 +56,6 @@ #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/misc1.h" -#include "nvim/misc2.h" #include "nvim/garray.h" #include "nvim/move.h" #include "nvim/option.h" @@ -60,7 +67,6 @@ #include "nvim/spell.h" #include "nvim/strings.h" #include "nvim/syntax.h" -#include "nvim/terminal.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/version.h" @@ -69,6 +75,13 @@ #include "nvim/os/os.h" #include "nvim/os/time.h" #include "nvim/os/input.h" +#include "nvim/buffer_updates.h" + +typedef enum { + kBLSUnchanged = 0, + kBLSChanged = 1, + kBLSDeleted = 2, +} BufhlLineStatus; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "buffer.c.generated.h" @@ -78,6 +91,60 @@ 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; + +// 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); + } + + 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. @@ -91,8 +158,9 @@ open_buffer ( ) { int retval = OK; - buf_T *old_curbuf; + 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. @@ -133,32 +201,63 @@ open_buffer ( return FAIL; } - /* The autocommands in readfile() may change the buffer, but only AFTER - * reading the file. */ - old_curbuf = curbuf; - modified_was_set = FALSE; + // 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); + 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 (curbuf->b_help) { + if (bt_help(curbuf)) { fix_help_buffer(); } } else if (read_stdin) { int save_bin = curbuf->b_p_bin; - linenr_T line_count; /* * First read the text in binary mode into the buffer. @@ -172,56 +271,36 @@ open_buffer ( flags | (READ_NEW + READ_STDIN)); curbuf->b_p_bin = save_bin; if (retval == OK) { - line_count = curbuf->b_ml.ml_line_count; - retval = readfile(NULL, NULL, (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; - - /* 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); - apply_autocmds_retval(EVENT_STDINREADPOST, NULL, NULL, FALSE, - curbuf, &retval); + 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); + (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. */ + // 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) - ) + || modified_was_set // ":set modified" used in autocmd + || (aborting() && vim_strchr(p_cpo, CPO_INTMOD) != NULL)) { changed(); - else if (retval != FAIL && !read_stdin) - unchanged(curbuf, FALSE); - save_file_ff(curbuf); /* keep this fileformat */ + } else if (retval != FAIL && !read_stdin && !read_fifo) { + unchanged(curbuf, false); + } + 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()) @@ -246,11 +325,11 @@ open_buffer ( * The autocommands may have changed the current buffer. Apply the * modelines to the correct buffer, if it still exists and is loaded. */ - if (buf_valid(old_curbuf) && old_curbuf->b_ml.ml_mfp != NULL) { + 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); + // 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); @@ -264,11 +343,45 @@ open_buffer ( return retval; } -/// Check that "buf" points to a valid buffer (in the buffer list). +/// 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 { - FOR_ALL_BUFFERS(bp) { + 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; } @@ -276,35 +389,34 @@ bool buf_valid(buf_T *buf) return false; } -/* - * 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 - * 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. - */ -void -close_buffer ( - win_T *win, /* if not NULL, set b_last_cursor */ - buf_T *buf, - int action, - int abort_if_last -) +/// 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, int 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 @@ -328,47 +440,57 @@ close_buffer ( wipe_buf = true; } - if (win_valid(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) + // 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_closing = true; - apply_autocmds(EVENT_BUFWINLEAVE, buf->b_fname, buf->b_fname, - FALSE, buf); - if (!buf_valid(buf)) { - /* Autocommands deleted the buffer. */ + 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_closing = false; + buf->b_locked--; if (abort_if_last && one_window()) { /* Autocommands made this the only window. */ EMSG(_(e_auabort)); return; } - /* When the buffer becomes hidden, but is not unloaded, trigger - * BufHidden */ + // When the buffer becomes hidden, but is not unloaded, trigger + // BufHidden if (!unload_buf) { - buf->b_closing = true; - apply_autocmds(EVENT_BUFHIDDEN, buf->b_fname, buf->b_fname, - FALSE, buf); - if (!buf_valid(buf)) { - /* Autocommands deleted the buffer. */ + 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_closing = false; + buf->b_locked--; if (abort_if_last && one_window()) { /* Autocommands made this the only window. */ EMSG(_(e_auabort)); @@ -378,20 +500,36 @@ close_buffer ( 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; + // 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) + 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) @@ -404,23 +542,29 @@ close_buffer ( /* 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 (win_valid(win) && win->w_buffer == buf) { - win->w_buffer = NULL; // make sure we don't use the buffer now - } - /* Autocommands may have deleted the buffer. */ - if (!buf_valid(buf)) + if (!bufref_valid(&bufref)) { + // Autocommands may have deleted the buffer. return; - if (aborting()) /* autocmds may abort script processing */ + } + if (aborting()) { + // Autocmds may abort script processing. return; - - /* 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; + } /* * It's possible that autocommands change curbuf to the one being deleted. @@ -432,9 +576,24 @@ close_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 + buf_updates_unregister_all(buf); + /* * Remove the buffer from the list. */ @@ -483,35 +642,70 @@ void buf_clear_file(buf_T *buf) buf->b_ml.ml_flags = ML_EMPTY; /* empty buffer */ } -/* - * buf_freeall() - free all things allocated for a buffer that are related to - * the file. 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 - */ +/// 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; - buf->b_closing = true; - apply_autocmds(EVENT_BUFUNLOAD, buf->b_fname, buf->b_fname, FALSE, buf); - if (!buf_valid(buf)) /* autocommands may delete the buffer */ + // 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); - if (!buf_valid(buf)) /* autocommands may delete the buffer */ - return; } - if (flags & BFA_WIPE) { - apply_autocmds(EVENT_BUFWIPEOUT, buf->b_fname, buf->b_fname, - FALSE, buf); - if (!buf_valid(buf)) /* autocommands may delete 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; } - buf->b_closing = false; - if (aborting()) /* autocmds may abort script processing */ + 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. @@ -521,10 +715,11 @@ void buf_freeall(buf_T *buf, int flags) */ if (buf == curbuf && !is_curbuf) return; - diff_buf_delete(buf); /* Can't use 'diff' for unloaded buffer. */ - /* Remove any ownsyntax, unless exiting. */ - if (firstwin != NULL && curwin->w_buffer == buf) + 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) { @@ -550,10 +745,11 @@ void buf_freeall(buf_T *buf, int flags) static void free_buffer(buf_T *buf) { handle_unregister_buffer(buf); - free_buffer_stuff(buf, TRUE); + buf_free_count++; + free_buffer_stuff(buf, true); unref_var_dict(buf->b_vars); aubuflocal_remove(buf); - dict_unref(buf->additional_data); + tv_dict_unref(buf->additional_data); clear_fmark(&buf->b_last_cursor); clear_fmark(&buf->b_last_insert); clear_fmark(&buf->b_last_change); @@ -589,8 +785,18 @@ free_buffer_stuff ( free_buf_options(buf, true); ga_clear(&buf->b_s.b_langp); } + { + // Avoid loosing 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); + buf_init_changedtick(buf); uc_clear(&buf->b_ucmds); // clear local user commands buf_delete_signs(buf); // delete any signs bufhl_clear_all(buf); // delete any highligts @@ -598,6 +804,8 @@ free_buffer_stuff ( map_clear_int(buf, MAP_ALL_MODES, true, true); // clear local abbrevs xfree(buf->b_start_fenc); buf->b_start_fenc = NULL; + + buf_updates_unregister_all(buf); } /* @@ -623,11 +831,13 @@ static void clear_wininfo(buf_T *buf) */ void goto_buffer(exarg_T *eap, int start, int dir, int count) { - (void)do_buffer(*eap->cmd == 's' ? DOBUF_SPLIT : DOBUF_GOTO, - start, dir, count, eap->forceit); - buf_T *old_curbuf = curbuf; + 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; @@ -635,8 +845,8 @@ void goto_buffer(exarg_T *eap, int start, int dir, int count) * aborting() returns FALSE when closing a window. */ enter_cleanup(&cs); - /* Quitting means closing the split window, nothing else. */ - win_close(curwin, TRUE); + // Quitting means closing the split window, nothing else. + win_close(curwin, true); swap_exists_action = SEA_NONE; swap_exists_did_quit = TRUE; @@ -644,36 +854,52 @@ void goto_buffer(exarg_T *eap, int start, int dir, int count) * new aborting error, interrupt, or uncaught exception. */ leave_cleanup(&cs); } else { - handle_swap_exists(old_curbuf); + 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(buf_T *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 (!buf_valid(old_curbuf) || old_curbuf == curbuf) - old_curbuf = buflist_new(NULL, NULL, 1L, BLN_CURBUF | BLN_LISTED); - if (old_curbuf != NULL) { - enter_buffer(old_curbuf); - if (old_tw != curbuf->b_p_tw) + // 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... */ @@ -696,7 +922,7 @@ void handle_swap_exists(buf_T *old_curbuf) * new aborting error, interrupt, or uncaught exception. */ leave_cleanup(&cs); } - swap_exists_action = SEA_NONE; + swap_exists_action = SEA_NONE; // -V519 } /* @@ -824,6 +1050,9 @@ static int empty_curbuf(int close_others, int forceit, int action) 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); @@ -833,15 +1062,17 @@ static int empty_curbuf(int close_others, int forceit, int action) 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 && buf_valid(buf) && buf->b_nwindows == 0) - close_buffer(NULL, buf, action, FALSE); - if (!close_others) - need_fileinfo = FALSE; + // 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; } /* @@ -943,6 +1174,8 @@ do_buffer ( */ 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. */ @@ -951,28 +1184,41 @@ do_buffer ( if (!forceit && (buf->terminal || bufIsChanged(buf))) { if ((p_confirm || cmdmod.confirm) && p_write && !buf->terminal) { - dialog_changed(buf, FALSE); - if (!buf_valid(buf)) - /* Autocommand deleted buffer, oops! It's not changed - * now. */ + 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)) + } + // If it's still changed fail silently, the dialog already + // mentioned why it fails. + if (bufIsChanged(buf)) { return FAIL; + } } else { if (buf->terminal) { - EMSG2(_("E89: %s will be killed(add ! to override)"), - (char *)buf->b_fname); + 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; } - 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. @@ -993,9 +1239,9 @@ do_buffer ( * a window with this buffer. */ while (buf == curbuf - && !(curwin->w_closing || curwin->w_buffer->b_closing) - && (firstwin != lastwin || first_tabpage->tp_next != NULL)) { - if (win_close(curwin, FALSE) == FAIL) + && !(curwin->w_closing || curwin->w_buffer->b_locked > 0) + && (!ONE_WINDOW || first_tabpage->tp_next != NULL)) { + if (win_close(curwin, false) == FAIL) break; } @@ -1003,27 +1249,26 @@ do_buffer ( * If the buffer to be deleted is not the current one, delete it here. */ if (buf != curbuf) { - close_windows(buf, FALSE); - if (buf != curbuf && buf_valid(buf) && buf->b_nwindows <= 0) - close_buffer(NULL, buf, action, FALSE); + 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, 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 != NULL && buf_valid(au_new_curbuf)) - buf = au_new_curbuf; - else if (curwin->w_jumplistlen > 0) { + // 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; @@ -1128,10 +1373,13 @@ do_buffer ( */ if (action == DOBUF_GOTO && !can_abandon(curbuf, forceit)) { if ((p_confirm || cmdmod.confirm) && p_write) { - dialog_changed(curbuf, FALSE); - if (!buf_valid(buf)) - /* Autocommand deleted buffer, oops! */ + bufref_T bufref; + set_bufref(&bufref, buf); + dialog_changed(curbuf, false); + if (!bufref_valid(&bufref)) { + // Autocommand deleted buffer, oops! return FAIL; + } } if (bufIsChanged(curbuf)) { EMSG(_(e_nowrtmsg)); @@ -1177,27 +1425,40 @@ void set_curbuf(buf_T *buf, int action) /* Don't restart Select mode after switching to another buffer. */ VIsual_reselect = FALSE; - /* close_windows() or apply_autocmds() may change curbuf */ + // close_windows() or apply_autocmds() may change curbuf and wipe out "buf" prevbuf = curbuf; - - apply_autocmds(EVENT_BUFLEAVE, NULL, NULL, FALSE, curbuf); - if (buf_valid(prevbuf) && !aborting()) { - if (prevbuf == curwin->w_buffer) + 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 (buf_valid(prevbuf) && !aborting()) { + } + 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 - && !P_HID(prevbuf) - && !bufIsChanged( - prevbuf)) ? DOBUF_UNLOAD : 0, FALSE); - if (curwin != previouswin && win_valid(previouswin)) - /* autocommands changed curwin, Grr! */ + 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 @@ -1249,10 +1510,6 @@ void enter_buffer(buf_T *buf) /* mark cursor position as being invalid */ curwin->w_valid = 0; - if (buf->terminal) { - terminal_resize(buf->terminal, curwin->w_width, curwin->w_height); - } - /* 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 @@ -1310,32 +1567,55 @@ void do_autochdir(void) } } -/* - * functions for dealing with the buffer list - */ +// +// functions for dealing with the buffer list +// -/* - * 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. - * This is the ONLY way to create a new buffer. - */ -static int top_file_num = 1; /* highest file number */ - -buf_T * -buflist_new ( - char_u *ffname, /* full path of fname or relative */ - char_u *sfname, /* short fname or NULL */ - linenr_T lnum, /* preferred cursor line */ - int flags /* BLN_ defines */ -) +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); +} + +/// Add a file name to the buffer list. +/// 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. +/// This is the ONLY way to create a new buffer. +/// +/// @param ffname full path of fname or relative +/// @param sfname short fname or NULL +/// @param lnum preferred cursor line +/// @param flags BLN_ defines +/// @param bufnr +/// +/// @return pointer to the buffer +buf_T * buflist_new(char_u *ffname, char_u *sfname, linenr_T lnum, int flags) { buf_T *buf; - fname_expand(curbuf, &ffname, &sfname); /* will allocate ffname */ + fname_expand(curbuf, &ffname, &sfname); // will allocate ffname /* * If file name already exists in the list, update the entry. @@ -1349,16 +1629,20 @@ buflist_new ( && (buf = buflist_findname_file_id(ffname, &file_id, file_id_valid)) != NULL) { xfree(ffname); - if (lnum != 0) - buflist_setfpos(buf, curwin, lnum, (colnr_T)0, FALSE); - /* copy the options now, if 'cpo' doesn't have 's' and not done - * already */ - buf_copy_options(buf, 0); + if (lnum != 0) { + buflist_setfpos(buf, 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) { - buf->b_p_bl = TRUE; + buf->b_p_bl = true; + bufref_T bufref; + set_bufref(&bufref, buf); if (!(flags & BLN_DUMMY)) { - apply_autocmds(EVENT_BUFADD, NULL, NULL, FALSE, buf); - if (!buf_valid(buf)) { + if (apply_autocmds(EVENT_BUFADD, NULL, NULL, false, buf) + && !bufref_valid(&bufref)) { return NULL; } } @@ -1379,7 +1663,7 @@ buflist_new ( && curbuf != NULL && curbuf->b_ffname == NULL && curbuf->b_nwindows <= 1 - && (curbuf->b_ml.ml_mfp == NULL || bufempty())) { + && (curbuf->b_ml.ml_mfp == NULL || BUFEMPTY())) { buf = curbuf; /* It's like this buffer is deleted. Watch out for autocommands that * change curbuf! If that happens, allocate a new buffer anyway. */ @@ -1397,10 +1681,10 @@ buflist_new ( } if (buf != curbuf || curbuf == NULL) { buf = xcalloc(1, sizeof(buf_T)); - handle_register_buffer(buf); - /* init b: variables */ - buf->b_vars = dict_alloc(); + // init b: variables + buf->b_vars = tv_dict_alloc(); init_var_dict(buf->b_vars, &buf->b_bufvar, VAR_SCOPE); + buf_init_changedtick(buf); } if (ffname != NULL) { @@ -1451,11 +1735,12 @@ buflist_new ( lastbuf = buf; buf->b_fnum = top_file_num++; - if (top_file_num < 0) { /* wrap around (may cause duplicates) */ + handle_register_buffer(buf); + if (top_file_num < 0) { // wrap around (may cause duplicates) EMSG(_("W14: Warning: List of file names overflow")); if (emsg_silent == 0) { ui_flush(); - os_delay(3000L, true); /* make sure it is noticed */ + os_delay(3000L, true); // make sure it is noticed } top_file_num = 1; } @@ -1484,25 +1769,30 @@ buflist_new ( 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' */ + 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' + kv_destroy(buf->update_channels); + kv_init(buf->update_channels); if (!(flags & BLN_DUMMY)) { // 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. - apply_autocmds(EVENT_BUFNEW, NULL, NULL, FALSE, buf); - if (!buf_valid(buf)) { + bufref_T bufref; + set_bufref(&bufref, buf); + if (apply_autocmds(EVENT_BUFNEW, NULL, NULL, false, buf) + && !bufref_valid(&bufref)) { return NULL; } - if (flags & BLN_LISTED) { - apply_autocmds(EVENT_BUFADD, NULL, NULL, FALSE, buf); - if (!buf_valid(buf)) { - return NULL; - } + if ((flags & BLN_LISTED) + && apply_autocmds(EVENT_BUFADD, NULL, NULL, false, buf) + && !bufref_valid(&bufref)) { + return NULL; } - if (aborting()) /* autocmds may abort script processing */ + if (aborting()) { + // Autocmds may abort script processing. return NULL; + } } return buf; @@ -1526,6 +1816,7 @@ void free_buf_options(buf_T *buf, int free_p_ff) clear_string_option(&buf->b_p_inex); clear_string_option(&buf->b_p_inde); clear_string_option(&buf->b_p_indk); + clear_string_option(&buf->b_p_fp); clear_string_option(&buf->b_p_fex); clear_string_option(&buf->b_p_kp); clear_string_option(&buf->b_p_mps); @@ -1533,6 +1824,7 @@ void free_buf_options(buf_T *buf, int free_p_ff) clear_string_option(&buf->b_p_flp); clear_string_option(&buf->b_p_isk); clear_string_option(&buf->b_p_keymap); + keymap_ga_clear(&buf->b_kmap_ga); ga_clear(&buf->b_kmap_ga); clear_string_option(&buf->b_p_com); clear_string_option(&buf->b_p_cms); @@ -1566,18 +1858,18 @@ void free_buf_options(buf_T *buf, int free_p_ff) 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 - */ + +/// 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; @@ -1629,7 +1921,7 @@ int buflist_getfile(int n, linenr_T lnum, int options, int forceit) // 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()) { + && !BUFEMPTY()) { if (swb_flags & SWB_NEWTAB) { tabpage_new(); } else if (win_split(0, (swb_flags & SWB_VSPLIT) ? WSP_VERT : 0) @@ -1640,10 +1932,10 @@ int buflist_getfile(int n, linenr_T lnum, int options, int forceit) } } - ++RedrawingDisabled; - if (getfile(buf->b_fnum, NULL, NULL, (options & GETF_SETMARK), - lnum, forceit) <= 0) { - --RedrawingDisabled; + 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) { @@ -1654,13 +1946,11 @@ int buflist_getfile(int n, linenr_T lnum, int options, int forceit) } return OK; } - --RedrawingDisabled; + RedrawingDisabled--; return FAIL; } -/* - * go to the last know line number for the current buffer - */ +// Go to the last known line number for the current buffer. void buflist_getfpos(void) { pos_T *fpos; @@ -1725,7 +2015,8 @@ buf_T *buflist_findname(char_u *ffname) static buf_T *buflist_findname_file_id(char_u *ffname, FileID *file_id, bool file_id_valid) { - FOR_ALL_BUFFERS(buf) { + // Start at the last buffer, expect to find a match sooner. + FOR_ALL_BUFFERS_BACKWARDS(buf) { if ((buf->b_flags & BF_DUMMY) == 0 && !otherfile_buf(buf, ffname, file_id, file_id_valid)) { return buf; @@ -1799,7 +2090,7 @@ int buflist_findpat( return -1; } - FOR_ALL_BUFFERS(buf) { + FOR_ALL_BUFFERS_BACKWARDS(buf) { if (buf->b_p_bl == find_listed && (!diffmode || diff_mode_buf(buf)) && buflist_match(®match, buf, false) != NULL) { @@ -1913,7 +2204,7 @@ int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options) if (count == 0) /* no match found, break here */ break; if (round == 1) { - *file = xmalloc(count * sizeof(**file)); + *file = xmalloc((size_t)count * sizeof(**file)); } } vim_regfree(regmatch.regprog); @@ -1968,21 +2259,14 @@ static char_u *fname_match(regmatch_T *rmp, char_u *name, bool ignore_case) return match; } -/* - * find file in buffer list by number - */ +/// Find a file in the buffer list by buffer number. buf_T *buflist_findnr(int nr) { if (nr == 0) { nr = curwin->w_alt_fnum; } - FOR_ALL_BUFFERS(buf) { - if (buf->b_fnum == nr) { - return buf; - } - } - return NULL; + return handle_get_buffer((handle_T)nr); } /* @@ -2128,8 +2412,15 @@ void get_winopts(buf_T *buf) clear_winopt(&curwin->w_onebuf_opt); clearFolding(curwin); - wip = find_wininfo(buf, TRUE); - if (wip != NULL && wip->wi_optset) { + wip = find_wininfo(buf, true); + if (wip != NULL && wip->wi_win != curwin && wip->wi_win != NULL + && wip->wi_win->w_buffer == buf) { + win_T *wp = wip->wi_win; + copy_winopt(&wp->w_onebuf_opt, &curwin->w_onebuf_opt); + curwin->w_fold_manual = wp->w_fold_manual; + curwin->w_foldinvalid = true; + cloneFoldGrowArray(&wp->w_folds, &curwin->w_folds); + } else if (wip != NULL && wip->wi_optset) { copy_winopt(&wip->wi_opt, &curwin->w_onebuf_opt); curwin->w_fold_manual = wip->wi_fold_manual; curwin->w_foldinvalid = true; @@ -2140,7 +2431,7 @@ void get_winopts(buf_T *buf) /* Set 'foldlevel' to 'foldlevelstart' if it's not negative. */ if (p_fdls >= 0) curwin->w_p_fdl = p_fdls; - check_colorcolumn(curwin); + didset_window_options(curwin); } /* @@ -2164,9 +2455,7 @@ linenr_T buflist_findlnum(buf_T *buf) return buflist_findfpos(buf)->lnum; } -/* - * List all know file names (for :files and :buffers command). - */ +// List all known file names (for :files and :buffers command). void buflist_list(exarg_T *eap) { buf_T *buf; @@ -2191,12 +2480,17 @@ void buflist_list(exarg_T *eap) && (buf == curbuf || curwin->w_alt_fnum != buf->b_fnum))) { continue; } - msg_putchar('\n'); - if (buf_spname(buf) != NULL) + if (buf_spname(buf) != NULL) { STRLCPY(NameBuff, buf_spname(buf), MAXPATHL); - else - home_replace(buf, buf->b_fname, NameBuff, MAXPATHL, TRUE); + } else { + home_replace(buf, buf->b_fname, NameBuff, MAXPATHL, true); + } + if (message_filtered(NameBuff)) { + continue; + } + + 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', @@ -2365,10 +2659,11 @@ 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 */ + // Create a buffer. 'buflisted' is not set if it's a new buffer buf = buflist_new(ffname, sfname, lnum, 0); - if (buf != NULL && !cmdmod.keepalt) + if (buf != NULL && !cmdmod.keepalt) { curwin->w_alt_fnum = buf->b_fnum; + } return buf; } @@ -2376,9 +2671,8 @@ buf_T *setaltfname(char_u *ffname, char_u *sfname, linenr_T lnum) * 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 * getaltfname( + bool errmsg // give error message ) { char_u *fname; @@ -2403,8 +2697,9 @@ int buflist_add(char_u *fname, int flags) buf_T *buf; buf = buflist_new(fname, NULL, (linenr_T)0, flags); - if (buf != NULL) + if (buf != NULL) { return buf->b_fnum; + } return 0; } @@ -2434,7 +2729,7 @@ void buflist_altfpos(win_T *win) } /// Check that "ffname" is not the same file as current file. -/// Fname must have a full path (expanded by path_get_absolute_path()). +/// Fname must have a full path (expanded by path_to_absolute()). /// /// @param ffname full path name to check bool otherfile(char_u *ffname) @@ -2444,7 +2739,7 @@ bool otherfile(char_u *ffname) } /// Check that "ffname" is not the same file as the file loaded in "buf". -/// Fname must have a full path (expanded by path_get_absolute_path()). +/// Fname must have a full path (expanded by path_to_absolute()). /// /// @param buf buffer to check /// @param ffname full path name to check @@ -2548,7 +2843,7 @@ fileinfo ( else name = curbuf->b_ffname; home_replace(shorthelp ? curbuf : NULL, name, p, - (int)(IOSIZE - (p - buffer)), TRUE); + (size_t)(IOSIZE - (p - buffer)), true); } vim_snprintf_add((char *)buffer, IOSIZE, "\"%s%s%s%s%s%s", @@ -2636,15 +2931,13 @@ static char_u *lasticon = NULL; void maketitle(void) { - char_u *p; char_u *t_str = NULL; char_u *i_name; char_u *i_str = NULL; int maxlen = 0; int len; int mustset; - char_u buf[IOSIZE]; - int off; + char buf[IOSIZE]; if (!redrawing()) { /* Postpone updating the title when 'lazyredraw' is set. */ @@ -2658,102 +2951,123 @@ void maketitle(void) if (p_title) { if (p_titlelen > 0) { - maxlen = p_titlelen * Columns / 100; - if (maxlen < 10) + maxlen = (int)(p_titlelen * Columns / 100); + if (maxlen < 10) { maxlen = 10; + } } - t_str = buf; if (*p_titlestring != NUL) { if (stl_syntax & STL_IN_TITLE) { int use_sandbox = FALSE; int save_called_emsg = called_emsg; use_sandbox = was_set_insecurely((char_u *)"titlestring", 0); - called_emsg = FALSE; - build_stl_str_hl(curwin, t_str, sizeof(buf), - p_titlestring, use_sandbox, - 0, maxlen, NULL, NULL); - if (called_emsg) - set_string_option_direct((char_u *)"titlestring", -1, - (char_u *)"", OPT_FREE, SID_ERROR); + called_emsg = false; + build_stl_str_hl(curwin, (char_u *)buf, sizeof(buf), + p_titlestring, use_sandbox, + 0, maxlen, NULL, NULL); + t_str = (char_u *)buf; + if (called_emsg) { + set_string_option_direct((char_u *)"titlestring", -1, (char_u *)"", + OPT_FREE, SID_ERROR); + } called_emsg |= save_called_emsg; - } else + } else { t_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) - STRLCPY(buf, _("[No Name]"), SPACE_FOR_FNAME + 1); - else { - p = transstr(path_tail(curbuf->b_fname)); - STRLCPY(buf, p, SPACE_FOR_FNAME + 1); - xfree(p); + // Format: "fname + (path) (1 of 2) - VIM". + +#define SPACE_FOR_FNAME (sizeof(buf) - 100) +#define SPACE_FOR_DIR (sizeof(buf) - 20) +#define SPACE_FOR_ARGNR (sizeof(buf) - 10) // At least room for " - NVIM". + char *buf_p = buf; + if (curbuf->b_fname == NULL) { + const size_t size = xstrlcpy(buf_p, _("[No Name]"), + SPACE_FOR_FNAME + 1); + buf_p += MIN(size, SPACE_FOR_FNAME); + } else { + buf_p += transstr_buf((const char *)path_tail(curbuf->b_fname), + buf_p, SPACE_FOR_FNAME + 1); } switch (bufIsChanged(curbuf) - + (curbuf->b_p_ro * 2) - + (!MODIFIABLE(curbuf) * 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; + | (curbuf->b_p_ro << 1) + | (!MODIFIABLE(curbuf) << 2)) { + case 0: break; + case 1: buf_p = strappend(buf_p, " +"); break; + case 2: buf_p = strappend(buf_p, " ="); break; + case 3: buf_p = strappend(buf_p, " =+"); break; + case 4: + case 6: buf_p = strappend(buf_p, " -"); break; + case 5: + case 7: buf_p = strappend(buf_p, " -+"); break; + default: assert(false); } if (curbuf->b_fname != NULL) { - /* 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); + // Get path of file, replace home dir with ~. + *buf_p++ = ' '; + *buf_p++ = '('; + home_replace(curbuf, curbuf->b_ffname, (char_u *)buf_p, + (SPACE_FOR_DIR - (size_t)(buf_p - buf)), true); #ifdef BACKSLASH_IN_FILENAME - /* avoid "c:/name" to be reduced to "c" */ - if (isalpha(buf[off]) && buf[off + 1] == ':') - off += 2; + // Avoid "c:/name" to be reduced to "c". + if (isalpha((uint8_t)buf_p) && *(buf_p + 1) == ':') { + buf_p += 2; + } #endif - /* remove the file name */ - p = path_tail_with_sep(buf + off); - if (p == buf + off) - /* must be a help buffer */ - STRLCPY(buf + off, _("help"), SPACE_FOR_DIR - off); - else + // Remove the file name. + char *p = (char *)path_tail_with_sep((char_u *)buf_p); + if (p == buf_p) { + // Must be a help buffer. + xstrlcpy(buf_p, _("help"), SPACE_FOR_DIR - (size_t)(buf_p - buf)); + } 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); - STRLCPY(buf + off, p, SPACE_FOR_DIR - off + 1); - xfree(p); + // Translate unprintable chars and concatenate. Keep some + // room for the server name. When there is no room (very long + // file name) use (...). + if ((size_t)(buf_p - buf) < SPACE_FOR_DIR) { + char *const tbuf = transstr(buf_p); + const size_t free_space = SPACE_FOR_DIR - (size_t)(buf_p - buf) + 1; + const size_t dir_len = xstrlcpy(buf_p, tbuf, free_space); + buf_p += MIN(dir_len, free_space - 1); + xfree(tbuf); } else { - STRLCPY(buf + off, "...", SPACE_FOR_ARGNR - off + 1); + const size_t free_space = SPACE_FOR_ARGNR - (size_t)(buf_p - buf) + 1; + const size_t dots_len = xstrlcpy(buf_p, "...", free_space); + buf_p += MIN(dots_len, free_space - 1); } - STRCAT(buf, ")"); + *buf_p++ = ')'; + *buf_p = NUL; + } else { + *buf_p = NUL; } - append_arg_number(curwin, buf, SPACE_FOR_ARGNR, FALSE); + append_arg_number(curwin, (char_u *)buf_p, + (int)(SPACE_FOR_ARGNR - (size_t)(buf_p - buf)), false); - STRCAT(buf, " - NVIM"); + xstrlcat(buf_p, " - NVIM", (sizeof(buf) - (size_t)(buf_p - buf))); if (maxlen > 0) { - /* make it shorter by removing a bit in the middle */ - if (vim_strsize(buf) > maxlen) - trunc_string(buf, buf, maxlen, IOSIZE); + // Make it shorter by removing a bit in the middle. + if (vim_strsize((char_u *)buf) > maxlen) { + trunc_string((char_u *)buf, (char_u *)buf, maxlen, sizeof(buf)); + } } + t_str = (char_u *)buf; +#undef SPACE_FOR_FNAME +#undef SPACE_FOR_DIR +#undef SPACE_FOR_ARGNR } } mustset = ti_change(t_str, &lasttitle); if (p_icon) { - i_str = buf; + i_str = (char_u *)buf; if (*p_iconstring != NUL) { if (stl_syntax & STL_IN_ICON) { int use_sandbox = FALSE; @@ -2819,13 +3133,13 @@ static bool ti_change(char_u *str, char_u **last) return false; } -/* - * Put current window title back (used after calling a shell) - */ + +/// Set current window title void resettitle(void) { - ui_set_title((char *)lasttitle); - ui_set_icon((char *)lasticon); + ui_call_set_icon(cstr_as_string((char *)lasticon)); + ui_call_set_title(cstr_as_string((char *)lasttitle)); + ui_flush(); } # if defined(EXITFREE) @@ -2841,7 +3155,6 @@ void free_titles(void) /// be used when printing numbers in the status line. typedef enum { kNumBaseDecimal = 10, - kNumBaseOctal = 8, kNumBaseHexadecimal = 16 } NumberBase; @@ -2877,13 +3190,13 @@ int build_stl_str_hl( size_t outlen, char_u *fmt, int use_sandbox, - int fillchar, + char_u fillchar, int maxwidth, struct stl_hlrec *hltab, StlClickRecord *tabtab ) { - int groupitem[STL_MAX_ITEM]; + int groupitems[STL_MAX_ITEM]; struct stl_item { // Where the item starts in the status line output buffer char_u *start; @@ -2903,7 +3216,7 @@ int build_stl_str_hl( ClickFunc, Trunc } type; - } item[STL_MAX_ITEM]; + } items[STL_MAX_ITEM]; #define TMPLEN 70 char_u tmp[TMPLEN]; char_u *usefmt = fmt; @@ -2929,10 +3242,11 @@ int build_stl_str_hl( // Get the byte value now, in case we need it below. This is more // efficient than making a copy of the line. int byteval; - if (wp->w_cursor.col > (colnr_T)STRLEN(line_ptr)) + if (wp->w_cursor.col > (colnr_T)STRLEN(line_ptr)) { byteval = 0; - else - byteval = (*mb_ptr2char)(line_ptr + wp->w_cursor.col); + } else { + byteval = utf_ptr2char(line_ptr + wp->w_cursor.col); + } int groupdepth = 0; @@ -2990,9 +3304,6 @@ int build_stl_str_hl( // Two `%` in a row is the escape sequence to print a // single `%` in the output buffer. if (*fmt_p == '%') { - // Ignore the character if we're out of room in the output buffer. - if (out_p >= out_end_p) - break; *out_p++ = *fmt_p++; prevchar_isflag = prevchar_isitem = false; continue; @@ -3005,16 +3316,16 @@ int build_stl_str_hl( if (groupdepth > 0) { continue; } - item[curitem].type = Separate; - item[curitem++].start = out_p; + items[curitem].type = Separate; + items[curitem++].start = out_p; continue; } // STL_TRUNCMARK: Where to begin truncating if the statusline is too long. if (*fmt_p == STL_TRUNCMARK) { fmt_p++; - item[curitem].type = Trunc; - item[curitem++].start = out_p; + items[curitem].type = Trunc; + items[curitem++].start = out_p; continue; } @@ -3030,7 +3341,7 @@ int build_stl_str_hl( // Determine how long the group is. // Note: We set the current output position to null // so `vim_strsize` will work. - char_u *t = item[groupitem[groupdepth]].start; + char_u *t = items[groupitems[groupdepth]].start; *out_p = NUL; long group_len = vim_strsize(t); @@ -3040,11 +3351,11 @@ int build_stl_str_hl( // move the output pointer back to where the group started. // Note: This erases any non-item characters that were in the group. // Otherwise there would be no reason to do this step. - if (curitem > groupitem[groupdepth] + 1 - && item[groupitem[groupdepth]].minwid == 0) { + if (curitem > groupitems[groupdepth] + 1 + && items[groupitems[groupdepth]].minwid == 0) { bool has_normal_items = false; - for (long n = groupitem[groupdepth] + 1; n < curitem; n++) { - if (item[n].type == Normal || item[n].type == Highlight) { + for (long n = groupitems[groupdepth] + 1; n < curitem; n++) { + if (items[n].type == Normal || items[n].type == Highlight) { has_normal_items = true; break; } @@ -3058,18 +3369,18 @@ int build_stl_str_hl( // If the group is longer than it is allowed to be // truncate by removing bytes from the start of the group text. - if (group_len > item[groupitem[groupdepth]].maxwid) { + if (group_len > items[groupitems[groupdepth]].maxwid) { // { Determine the number of bytes to remove long n; if (has_mbyte) { /* Find the first character that should be included. */ n = 0; - while (group_len >= item[groupitem[groupdepth]].maxwid) { + while (group_len >= items[groupitems[groupdepth]].maxwid) { group_len -= ptr2cells(t + n); n += (*mb_ptr2len)(t + n); } } else { - n = (long)(out_p - t) - item[groupitem[groupdepth]].maxwid + 1; + n = (long)(out_p - t) - items[groupitems[groupdepth]].maxwid + 1; } // } @@ -3079,25 +3390,26 @@ int build_stl_str_hl( // { Move the truncated output memmove(t + 1, t + n, (size_t)(out_p - (t + n))); out_p = out_p - n + 1; - /* Fill up space left over by half a double-wide char. */ - while (++group_len < item[groupitem[groupdepth]].minwid) + // Fill up space left over by half a double-wide char. + while (++group_len < items[groupitems[groupdepth]].minwid) { *out_p++ = fillchar; + } // } - /* correct the start of the items for the truncation */ - for (int idx = groupitem[groupdepth] + 1; idx < curitem; idx++) { + // correct the start of the items for the truncation + for (int idx = groupitems[groupdepth] + 1; idx < curitem; idx++) { // Shift everything back by the number of removed bytes - item[idx].start -= n; + items[idx].start -= n; // If the item was partially or completely truncated, set its // start to the start of the group - if (item[idx].start < t) { - item[idx].start = t; + if (items[idx].start < t) { + items[idx].start = t; } } // If the group is shorter than the minimum width, add padding characters. - } else if (abs(item[groupitem[groupdepth]].minwid) > group_len) { - long min_group_width = item[groupitem[groupdepth]].minwid; + } else if (abs(items[groupitems[groupdepth]].minwid) > group_len) { + long min_group_width = items[groupitems[groupdepth]].minwid; // If the group is left-aligned, add characters to the right. if (min_group_width < 0) { min_group_width = 0 - min_group_width; @@ -3116,8 +3428,8 @@ int build_stl_str_hl( // } // Adjust item start positions - for (int n = groupitem[groupdepth] + 1; n < curitem; n++) { - item[n].start += group_len; + for (int n = groupitems[groupdepth] + 1; n < curitem; n++) { + items[n].start += group_len; } // Prepend the fill characters @@ -3155,9 +3467,9 @@ int build_stl_str_hl( // User highlight groups override the min width field // to denote the styling to use. if (*fmt_p == STL_USER_HL) { - item[curitem].type = Highlight; - item[curitem].start = out_p; - item[curitem].minwid = minwid > 9 ? 1 : minwid; + items[curitem].type = Highlight; + items[curitem].start = out_p; + items[curitem].minwid = minwid > 9 ? 1 : minwid; fmt_p++; curitem++; continue; @@ -3189,20 +3501,21 @@ int build_stl_str_hl( if (*fmt_p == STL_TABPAGENR || *fmt_p == STL_TABCLOSENR) { if (*fmt_p == STL_TABCLOSENR) { if (minwid == 0) { - /* %X ends the close label, go back to the previously - * define tab label nr. */ - for (long n = curitem - 1; n >= 0; --n) - if (item[n].type == TabPage && item[n].minwid >= 0) { - minwid = item[n].minwid; + // %X ends the close label, go back to the previous tab label nr. + for (long n = curitem - 1; n >= 0; n--) { + if (items[n].type == TabPage && items[n].minwid >= 0) { + minwid = items[n].minwid; break; } - } else - /* close nrs are stored as negative values */ + } + } else { + // close nrs are stored as negative values minwid = -minwid; + } } - item[curitem].type = TabPage; - item[curitem].start = out_p; - item[curitem].minwid = minwid; + items[curitem].type = TabPage; + items[curitem].start = out_p; + items[curitem].minwid = minwid; fmt_p++; curitem++; continue; @@ -3217,10 +3530,10 @@ int build_stl_str_hl( if (*fmt_p != STL_CLICK_FUNC) { break; } - item[curitem].type = ClickFunc; - item[curitem].start = out_p; - item[curitem].cmd = xmemdupz(t, (size_t) (((char *) fmt_p - t))); - item[curitem].minwid = minwid; + items[curitem].type = ClickFunc; + items[curitem].start = out_p; + items[curitem].cmd = xmemdupz(t, (size_t)(((char *)fmt_p - t))); + items[curitem].minwid = minwid; fmt_p++; curitem++; continue; @@ -3243,11 +3556,11 @@ int build_stl_str_hl( // Denotes the start of a new group if (*fmt_p == '(') { - groupitem[groupdepth++] = curitem; - item[curitem].type = Group; - item[curitem].start = out_p; - item[curitem].minwid = minwid; - item[curitem].maxwid = maxwid; + groupitems[groupdepth++] = curitem; + items[curitem].type = Group; + items[curitem].start = out_p; + items[curitem].minwid = minwid; + items[curitem].maxwid = maxwid; fmt_p++; curitem++; continue; @@ -3274,7 +3587,7 @@ int build_stl_str_hl( case STL_FULLPATH: case STL_FILENAME: { - // Set fillable to false to that ' ' in the filename will not + // Set fillable to false so that ' ' in the filename will not // get replaced with the fillchar fillable = false; if (buf_spname(wp->w_buffer) != NULL) { @@ -3313,7 +3626,7 @@ int build_stl_str_hl( // Store the current buffer number as a string variable vim_snprintf((char *)tmp, sizeof(tmp), "%d", curbuf->b_fnum); - set_internal_string_var((char_u *)"actual_curbuf", tmp); + set_internal_string_var((char_u *)"g:actual_curbuf", tmp); buf_T *o_curbuf = curbuf; win_T *o_curwin = curwin; @@ -3327,7 +3640,7 @@ int build_stl_str_hl( curbuf = o_curbuf; // Remove the variable we just stored - do_unlet((char_u *)"g:actual_curbuf", true); + do_unlet(S_LEN("g:actual_curbuf"), true); // } @@ -3409,7 +3722,7 @@ int build_stl_str_hl( case STL_KEYMAP: fillable = false; - if (get_keymap_str(wp, tmp, TMPLEN)) + if (get_keymap_str(wp, (char_u *)"<%s>", tmp, TMPLEN)) str = tmp; break; case STL_PAGENUM: @@ -3422,9 +3735,11 @@ int build_stl_str_hl( case STL_OFFSET_X: base = kNumBaseHexadecimal; + FALLTHROUGH; case STL_OFFSET: { - long l = ml_find_line_or_offset(wp->w_buffer, wp->w_cursor.lnum, NULL); + long l = ml_find_line_or_offset(wp->w_buffer, wp->w_cursor.lnum, NULL, + false); num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) || l < 0 ? 0L : l + 1 + (!(State & INSERT) && empty_line ? 0 : (int)wp->w_cursor.col); @@ -3432,6 +3747,7 @@ int build_stl_str_hl( } case STL_BYTEVAL_X: base = kNumBaseHexadecimal; + FALLTHROUGH; case STL_BYTEVAL: num = byteval; if (num == NL) @@ -3479,7 +3795,7 @@ int build_stl_str_hl( wp->w_buffer->b_p_ft); // Uppercase the file extension for (char_u *t = tmp; *t != 0; t++) { - *t = TOUPPER_LOC(*t); + *t = (char_u)TOUPPER_LOC(*t); } str = tmp; } @@ -3526,9 +3842,9 @@ int build_stl_str_hl( // Create a highlight item based on the name if (*fmt_p == '#') { - item[curitem].type = Highlight; - item[curitem].start = out_p; - item[curitem].minwid = -syn_namen2id(t, (int)(fmt_p - t)); + items[curitem].type = Highlight; + items[curitem].start = out_p; + items[curitem].minwid = -syn_namen2id(t, (int)(fmt_p - t)); curitem++; fmt_p++; } @@ -3539,8 +3855,8 @@ int build_stl_str_hl( // If we made it this far, the item is normal and starts at // our current position in the output buffer. // Non-normal items would have `continued`. - item[curitem].start = out_p; - item[curitem].type = Normal; + items[curitem].start = out_p; + items[curitem].type = Normal; // Copy the item string into the output buffer if (str != NULL && *str) { @@ -3635,9 +3951,7 @@ int build_stl_str_hl( // Note: The `*` means we take the width as one of the arguments *t++ = '*'; - *t++ = (char_u) (base == kNumBaseHexadecimal ? 'X' - : (base == kNumBaseOctal ? 'o' - : 'd')); + *t++ = (char_u)(base == kNumBaseHexadecimal ? 'X' : 'd'); *t = 0; // } @@ -3656,7 +3970,8 @@ int build_stl_str_hl( } // } - size_t remaining_buf_len = (out_end_p - out_p) + 1; + assert(out_end_p >= out_p); + size_t remaining_buf_len = (size_t)(out_end_p - out_p) + 1; // If the number is going to take up too much room // Figure out the approximate number in "scientific" type notation. @@ -3671,7 +3986,7 @@ int build_stl_str_hl( // { Reduce the number by base^n while (num_chars-- > maxwid) { - num /= base; + num /= (long)base; } // } @@ -3696,7 +4011,7 @@ int build_stl_str_hl( // Otherwise, there was nothing to print so mark the item as empty } else { - item[curitem].type = Empty; + items[curitem].type = Empty; } // Only free the string buffer if we allocated it. @@ -3721,8 +4036,7 @@ int build_stl_str_hl( } // We have now processed the entire statusline format string. - // What follows is post-processing to handle alignment and - // highlighting factors. + // What follows is post-processing to handle alignment and highlighting. int width = vim_strsize(out); if (maxwidth > 0 && width > maxwidth) { @@ -3737,16 +4051,17 @@ int build_stl_str_hl( // Otherwise, look for the truncation item } else { // Default to truncating at the first item - trunc_p = item[0].start; + trunc_p = items[0].start; item_idx = 0; - for (int i = 0; i < itemcnt; i++) - if (item[i].type == Trunc) { - // Truncate at %< item. - trunc_p = item[i].start; + for (int i = 0; i < itemcnt; i++) { + if (items[i].type == Trunc) { + // Truncate at %< items. + trunc_p = items[i].start; item_idx = i; break; } + } } // If the truncation point we found is beyond the maximum @@ -3776,7 +4091,7 @@ int build_stl_str_hl( // Ignore any items in the statusline that occur after // the truncation point for (int i = 0; i < itemcnt; i++) { - if (item[i].start > trunc_p) { + if (items[i].start > trunc_p) { itemcnt = i; break; } @@ -3831,12 +4146,12 @@ int build_stl_str_hl( for (int i = item_idx; i < itemcnt; i++) { // Items starting at or after the end of the truncated section need // to be moved backwards. - if (item[i].start >= trunc_end_p) { - item[i].start -= item_offset; + if (items[i].start >= trunc_end_p) { + items[i].start -= item_offset; // Anything inside the truncated area is set to start // at the `<` truncation character. } else { - item[i].start = trunc_p; + items[i].start = trunc_p; } } // } @@ -3847,12 +4162,12 @@ int build_stl_str_hl( // add characters at the separate marker (if there is one) to // fill up the available space. } else if (width < maxwidth - && STRLEN(out) + maxwidth - width + 1 < outlen) { + && STRLEN(out) + (size_t)(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 (int i = 0; i < itemcnt; i++) { - if (item[i].type == Separate) { + if (items[i].type == Separate) { num_separators++; } } @@ -3864,7 +4179,7 @@ int build_stl_str_hl( int separator_locations[STL_MAX_ITEM]; int index = 0; for (int i = 0; i < itemcnt; i++) { - if (item[i].type == Separate) { + if (items[i].type == Separate) { separator_locations[index] = i; index++; } @@ -3875,18 +4190,18 @@ int build_stl_str_hl( standard_spaces * (num_separators - 1); for (int i = 0; i < num_separators; i++) { - int dislocation = (i == (num_separators - 1)) ? - final_spaces : standard_spaces; - char_u *sep_loc = item[separator_locations[i]].start + dislocation; - STRMOVE(sep_loc, item[separator_locations[i]].start); - for (char_u *s = item[separator_locations[i]].start; s < sep_loc; s++) { + int dislocation = (i == (num_separators - 1)) + ? final_spaces : standard_spaces; + char_u *seploc = items[separator_locations[i]].start + dislocation; + STRMOVE(seploc, items[separator_locations[i]].start); + for (char_u *s = items[separator_locations[i]].start; s < seploc; s++) { *s = fillchar; } for (int item_idx = separator_locations[i] + 1; item_idx < itemcnt; item_idx++) { - item[item_idx].start += dislocation; + items[item_idx].start += dislocation; } } @@ -3898,9 +4213,9 @@ int build_stl_str_hl( if (hltab != NULL) { struct stl_hlrec *sp = hltab; for (long l = 0; l < itemcnt; l++) { - if (item[l].type == Highlight) { - sp->start = item[l].start; - sp->userhl = item[l].minwid; + if (items[l].type == Highlight) { + sp->start = items[l].start; + sp->userhl = items[l].minwid; sp++; } } @@ -3912,14 +4227,14 @@ int build_stl_str_hl( if (tabtab != NULL) { StlClickRecord *cur_tab_rec = tabtab; for (long l = 0; l < itemcnt; l++) { - if (item[l].type == TabPage) { - cur_tab_rec->start = (char *) item[l].start; - if (item[l].minwid == 0) { + if (items[l].type == TabPage) { + cur_tab_rec->start = (char *)items[l].start; + if (items[l].minwid == 0) { cur_tab_rec->def.type = kStlClickDisabled; cur_tab_rec->def.tabnr = 0; } else { - int tabnr = item[l].minwid; - if (item[l].minwid > 0) { + int tabnr = items[l].minwid; + if (items[l].minwid > 0) { cur_tab_rec->def.type = kStlClickTabSwitch; } else { cur_tab_rec->def.type = kStlClickTabClose; @@ -3929,11 +4244,11 @@ int build_stl_str_hl( } cur_tab_rec->def.func = NULL; cur_tab_rec++; - } else if (item[l].type == ClickFunc) { - cur_tab_rec->start = (char *) item[l].start; + } else if (items[l].type == ClickFunc) { + cur_tab_rec->start = (char *)items[l].start; cur_tab_rec->def.type = kStlClickFuncRun; - cur_tab_rec->def.tabnr = item[l].minwid; - cur_tab_rec->def.func = item[l].cmd; + cur_tab_rec->def.tabnr = items[l].minwid; + cur_tab_rec->def.func = items[l].cmd; cur_tab_rec++; } } @@ -4028,14 +4343,12 @@ void fname_expand(buf_T *buf, char_u **ffname, char_u **sfname) #ifdef WIN32 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_shortcut(*ffname); + // If the file name is a shortcut file, use the file it links to. + char *rfname = os_resolve_shortcut((const char *)(*ffname)); if (rfname != NULL) { xfree(*ffname); - *ffname = rfname; - *sfname = rfname; + *ffname = (char_u *)rfname; + *sfname = (char_u *)rfname; } } #endif @@ -4085,6 +4398,8 @@ do_arg_all ( win_T *new_curwin = NULL; tabpage_T *new_curtab = NULL; + assert(firstwin != NULL); // satisfy coverity + if (ARGCOUNT <= 0) { /* Don't give an error message. We don't want it when the ":all" * command is in the .vimrc. */ @@ -4093,7 +4408,7 @@ do_arg_all ( setpcmark(); opened_len = ARGCOUNT; - opened = xcalloc(opened_len, 1); + opened = xcalloc((size_t)opened_len, 1); /* Autocommands may do anything to the argument list. Make sure it's not * freed while we are working here by "locking" it. We still have to @@ -4121,17 +4436,15 @@ do_arg_all ( wpnext = wp->w_next; buf = wp->w_buffer; if (buf->b_ffname == NULL - || (!keep_tabs && buf->b_nwindows > 1) - || wp->w_width != Columns - ) + || (!keep_tabs && (buf->b_nwindows > 1 || wp->w_width != Columns))) { i = opened_len; - else { - /* check if the buffer in this window is in the arglist */ + } else { + // check if the buffer in this window is in the arglist for (i = 0; i < opened_len; ++i) { if (i < alist->al_ga.ga_len && (AARGLIST(alist)[i].ae_fnum == buf->b_fnum || path_full_compare(alist_name(&AARGLIST(alist)[i]), - buf->b_ffname, TRUE) & kEqualFiles)) { + buf->b_ffname, true) & kEqualFiles)) { int weight = 1; if (old_curtab == curtab) { @@ -4164,29 +4477,32 @@ do_arg_all ( } wp->w_arg_idx = i; - if (i == opened_len && !keep_tabs) { /* close this window */ - if (P_HID(buf) || forceit || buf->b_nwindows > 1 + if (i == opened_len && !keep_tabs) { // close this window + if (buf_hide(buf) || forceit || buf->b_nwindows > 1 || !bufIsChanged(buf)) { /* If the buffer was changed, and we would like to hide it, * try autowriting. */ - if (!P_HID(buf) && buf->b_nwindows <= 1 - && bufIsChanged(buf)) { - (void)autowrite(buf, FALSE); - /* check if autocommands removed the window */ - if (!win_valid(wp) || !buf_valid(buf)) { - wpnext = firstwin; /* start all over... */ + if (!buf_hide(buf) && buf->b_nwindows <= 1 && bufIsChanged(buf)) { + bufref_T bufref; + set_bufref(&bufref, buf); + (void)autowrite(buf, false); + // Check if autocommands removed the window. + if (!win_valid(wp) || !bufref_valid(&bufref)) { + wpnext = firstwin; // Start all over... continue; } } - /* don't close last window */ - if (firstwin == lastwin - && (first_tabpage->tp_next == NULL || !had_tab)) - use_firstwin = TRUE; - else { - win_close(wp, !P_HID(buf) && !bufIsChanged(buf)); - /* check if autocommands removed the next window */ - if (!win_valid(wpnext)) - wpnext = firstwin; /* start all over... */ + // don't close last window + if (ONE_WINDOW + && (first_tabpage->tp_next == NULL || !had_tab)) { + use_firstwin = true; + } else { + win_close(wp, !buf_hide(buf) && !bufIsChanged(buf)); + // check if autocommands removed the next window + if (!win_valid(wpnext)) { + // start all over... + wpnext = firstwin; + } } } } @@ -4215,15 +4531,17 @@ do_arg_all ( last_curwin = curwin; last_curtab = curtab; win_enter(lastwin, false); - /* ":drop all" should re-use an empty window to avoid "--remote-tab" - * leaving an empty tab page when executed locally. */ - if (keep_tabs && bufempty() && curbuf->b_nwindows == 1 - && curbuf->b_ffname == NULL && !curbuf->b_changed) - use_firstwin = TRUE; - - for (i = 0; i < count && i < opened_len && !got_int; ++i) { - if (alist == &global_alist && i == global_alist.al_ga.ga_len - 1) - arg_had_last = TRUE; + // ":drop all" should re-use an empty window to avoid "--remote-tab" + // leaving an empty tab page when executed locally. + if (keep_tabs && BUFEMPTY() && curbuf->b_nwindows == 1 + && curbuf->b_ffname == NULL && !curbuf->b_changed) { + use_firstwin = true; + } + + for (i = 0; i < count && i < opened_len && !got_int; i++) { + if (alist == &global_alist && i == global_alist.al_ga.ga_len - 1) { + arg_had_last = true; + } if (opened[i] > 0) { /* Move the already present window to below the current window */ if (curwin->w_arg_idx != i) { @@ -4258,14 +4576,15 @@ do_arg_all ( new_curwin = curwin; new_curtab = curtab; } - (void)do_ecmd(0, alist_name(&AARGLIST(alist)[i]), NULL, NULL, - ECMD_ONE, - ((P_HID(curwin->w_buffer) - || bufIsChanged(curwin->w_buffer)) ? ECMD_HIDE : 0) - + ECMD_OLDBUF, curwin); - if (use_firstwin) - ++autocmd_no_leave; - use_firstwin = FALSE; + (void)do_ecmd(0, alist_name(&AARGLIST(alist)[i]), NULL, NULL, ECMD_ONE, + ((buf_hide(curwin->w_buffer) + || bufIsChanged(curwin->w_buffer)) + ? ECMD_HIDE : 0) + ECMD_OLDBUF, + curwin); + if (use_firstwin) { + autocmd_no_leave++; + } + use_firstwin = false; } os_breakcheck(); @@ -4277,19 +4596,23 @@ do_arg_all ( /* Remove the "lock" on the argument list. */ alist_unlink(alist); - --autocmd_no_enter; - /* restore last referenced tabpage's curwin */ + autocmd_no_enter--; + // restore last referenced tabpage's curwin if (last_curtab != new_curtab) { - if (valid_tabpage(last_curtab)) - goto_tabpage_tp(last_curtab, TRUE, TRUE); - if (win_valid(last_curwin)) + if (valid_tabpage(last_curtab)) { + goto_tabpage_tp(last_curtab, true, true); + } + if (win_valid(last_curwin)) { win_enter(last_curwin, false); + } + } + // to window with first arg + if (valid_tabpage(new_curtab)) { + goto_tabpage_tp(new_curtab, true, true); } - /* to window with first arg */ - if (valid_tabpage(new_curtab)) - goto_tabpage_tp(new_curtab, TRUE, TRUE); - if (win_valid(new_curwin)) + if (win_valid(new_curwin)) { win_enter(new_curwin, false); + } --autocmd_no_leave; xfree(opened); @@ -4306,8 +4629,8 @@ void ex_buffer_all(exarg_T *eap) bool 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. */ + long count; // Maximum number of windows to open. + int all; // When TRUE also load inactive buffers. int had_tab = cmdmod.tab; tabpage_T *tpnext; @@ -4338,14 +4661,14 @@ void ex_buffer_all(exarg_T *eap) ? wp->w_height + wp->w_status_height < Rows - p_ch - tabline_height() : wp->w_width != Columns) - || (had_tab > 0 && wp != firstwin) - ) && firstwin != lastwin - && !(wp->w_closing || wp->w_buffer->b_closing) + || (had_tab > 0 && wp != firstwin)) + && !ONE_WINDOW + && !(wp->w_closing || wp->w_buffer->b_locked > 0) ) { - win_close(wp, FALSE); - wpnext = firstwin; /* just in case an autocommand does - something strange with windows */ - tpnext = first_tabpage; /* start all over...*/ + win_close(wp, false); + wpnext = firstwin; // just in case an autocommand does + // something strange with windows + tpnext = first_tabpage; // start all over... open_wins = 0; } else ++open_wins; @@ -4388,7 +4711,9 @@ void ex_buffer_all(exarg_T *eap) } if (wp == NULL && split_ret == OK) { - /* Split the window and put the buffer in it */ + 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); @@ -4400,7 +4725,8 @@ void ex_buffer_all(exarg_T *eap) /* Open the buffer in this window. */ swap_exists_action = SEA_DIALOG; set_curbuf(buf, DOBUF_GOTO); - if (!buf_valid(buf)) { /* autocommands deleted the buffer!!! */ + if (!bufref_valid(&bufref)) { + // Autocommands deleted the buffer. swap_exists_action = SEA_NONE; break; } @@ -4411,9 +4737,9 @@ void ex_buffer_all(exarg_T *eap) * aborting() returns FALSE when closing a window. */ enter_cleanup(&cs); - /* User selected Quit at ATTENTION prompt; close this window. */ - win_close(curwin, TRUE); - --open_wins; + // 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; @@ -4445,14 +4771,14 @@ void ex_buffer_all(exarg_T *eap) * Close superfluous windows. */ for (wp = lastwin; open_wins > count; ) { - r = (P_HID(wp->w_buffer) || !bufIsChanged(wp->w_buffer) - || autowrite(wp->w_buffer, FALSE) == OK); + 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, !P_HID(wp->w_buffer)); - --open_wins; + win_close(wp, !buf_hide(wp->w_buffer)); + open_wins--; wp = lastwin; } else { wp = wp->w_prev; @@ -4514,7 +4840,7 @@ chk_modeline ( char_u *e; char_u *linecopy; /* local copy of any modeline found */ int prev; - int vers; + intmax_t vers; int end; int retval = OK; char_u *save_sourcing_name; @@ -4533,7 +4859,10 @@ chk_modeline ( e = s + 4; else e = s + 3; - vers = getdigits_int(&e); + if (getdigits_safe(&e, &vers) != OK) { + continue; + } + if (*e == ':' && (s[0] != 'V' || STRNCMP(skipwhite(e + 1), "set", 3) == 0) @@ -4541,8 +4870,9 @@ chk_modeline ( || (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] == '='))) + || (VIM_VERSION_100 == vers && s[3] == '='))) { break; + } } } prev = *s; @@ -4614,6 +4944,12 @@ chk_modeline ( return retval; } +// Return true if "buf" is a help buffer. +bool bt_help(const buf_T *const buf) +{ + return buf != NULL && buf->b_help; +} + /* * Return special buffer name. * Returns NULL when the buffer has a normal file name. @@ -4753,13 +5089,16 @@ linenr_T buf_change_sign_type( return (linenr_T)0; } -int buf_getsigntype( - buf_T *buf, - linenr_T lnum, - int type /* SIGN_ICON, SIGN_TEXT, SIGN_ANY, SIGN_LINEHL */ - ) +/// Gets a sign from a given line. +/// In case of multiple signs, returns the most recently placed one. +/// +/// @param buf Buffer in which to search +/// @param lnum Line in which to search +/// @param type Type of sign to look for +/// @return Identifier of the first matching sign, or 0 +int buf_getsigntype(buf_T *buf, linenr_T lnum, SignType type) { - signlist_T *sign; /* a sign in a b_signlist */ + signlist_T *sign; // a sign in a b_signlist for (sign = buf->b_signlist; sign != NULL; sign = sign->next) { if (sign->lnum == lnum @@ -4767,7 +5106,9 @@ int buf_getsigntype( || (type == SIGN_TEXT && sign_get_text(sign->typenr) != NULL) || (type == SIGN_LINEHL - && sign_get_attr(sign->typenr, TRUE) != 0))) { + && sign_get_attr(sign->typenr, SIGN_LINEHL) != 0) + || (type == SIGN_NUMHL + && sign_get_attr(sign->typenr, SIGN_NUMHL) != 0))) { return sign->typenr; } } @@ -4824,7 +5165,7 @@ int buf_findsign( for (sign = buf->b_signlist; sign != NULL; sign = sign->next) { if (sign->id == id) { - return sign->lnum; + return (int)sign->lnum; } } @@ -4900,7 +5241,7 @@ void sign_list_placed(buf_T *rbuf) while (buf != NULL && !got_int) { if (buf->b_signlist != NULL) { vim_snprintf(lbuf, BUFSIZ, _("Signs for %s:"), buf->b_fname); - MSG_PUTS_ATTR(lbuf, hl_attr(HLF_D)); + MSG_PUTS_ATTR(lbuf, HL_ATTR(HLF_D)); msg_putchar('\n'); } for (p = buf->b_signlist; p != NULL && !got_int; p = p->next) { @@ -4938,6 +5279,30 @@ void sign_mark_adjust(linenr_T line1, linenr_T line2, long amount, long amount_a // bufhl: plugin highlights associated with a buffer +/// Get reference to line in kbtree_t +/// +/// @param b the three +/// @param line the linenumber to lookup +/// @param put if true, put a new line when not found +/// if false, return NULL when not found +BufhlLine *bufhl_tree_ref(BufhlInfo *b, linenr_T line, bool put) +{ + BufhlLine t = BUFHLLINE_INIT(line); + + // kp_put() only works if key is absent, try get first + BufhlLine **pp = kb_get(bufhl, b, &t); + if (pp) { + return *pp; + } else if (!put) { + return NULL; + } + + BufhlLine *p = xmalloc(sizeof(*p)); + *p = (BufhlLine)BUFHLLINE_INIT(line); + kb_put(bufhl, b, p); + return p; +} + /// Adds a highlight to buffer. /// /// Unlike matchaddpos() highlights follow changes to line numbering (as lines @@ -4966,34 +5331,105 @@ int bufhl_add_hl(buf_T *buf, int hl_id, linenr_T lnum, colnr_T col_start, - colnr_T col_end) { - static int next_src_id = 1; + colnr_T col_end) +{ if (src_id == 0) { - src_id = next_src_id++; + src_id = (int)nvim_create_namespace((String)STRING_INIT); } if (hl_id <= 0) { // no highlight group or invalid line, just return src_id return src_id; } - if (!buf->b_bufhl_info) { - buf->b_bufhl_info = map_new(linenr_T, bufhl_vec_T)(); - } - bufhl_vec_T* lineinfo = map_ref(linenr_T, bufhl_vec_T)(buf->b_bufhl_info, - lnum, true); - bufhl_hl_item_T *hlentry = kv_pushp(*lineinfo); + BufhlLine *lineinfo = bufhl_tree_ref(&buf->b_bufhl_info, lnum, true); + + BufhlItem *hlentry = kv_pushp(lineinfo->items); hlentry->src_id = src_id; hlentry->hl_id = hl_id; hlentry->start = col_start; hlentry->stop = col_end; if (0 < lnum && lnum <= buf->b_ml.ml_line_count) { - changed_lines_buf(buf, lnum, lnum+1, 0); - redraw_buf_later(buf, VALID); + redraw_buf_line_later(buf, lnum); + } + return src_id; +} + +/// Add highlighting to a buffer, bounded by two cursor positions, +/// with an offset. +/// +/// @param buf Buffer to add highlights to +/// @param src_id src_id to use or 0 to use a new src_id group, +/// or -1 for ungrouped highlight. +/// @param hl_id Highlight group id +/// @param pos_start Cursor position to start the hightlighting at +/// @param pos_end Cursor position to end the highlighting at +/// @param offset Move the whole highlighting this many columns to the right +void bufhl_add_hl_pos_offset(buf_T *buf, + int src_id, + int hl_id, + lpos_T pos_start, + lpos_T pos_end, + colnr_T offset) +{ + colnr_T hl_start = 0; + colnr_T hl_end = 0; + + for (linenr_T lnum = pos_start.lnum; lnum <= pos_end.lnum; lnum ++) { + if (pos_start.lnum < lnum && lnum < pos_end.lnum) { + hl_start = offset; + hl_end = MAXCOL; + } else if (lnum == pos_start.lnum && lnum < pos_end.lnum) { + hl_start = pos_start.col + offset + 1; + hl_end = MAXCOL; + } else if (pos_start.lnum < lnum && lnum == pos_end.lnum) { + hl_start = offset; + hl_end = pos_end.col + offset; + } else if (pos_start.lnum == lnum && pos_end.lnum == lnum) { + hl_start = pos_start.col + offset + 1; + hl_end = pos_end.col + offset; + } + (void)bufhl_add_hl(buf, src_id, hl_id, lnum, hl_start, hl_end); + } +} + +int bufhl_add_virt_text(buf_T *buf, + int src_id, + linenr_T lnum, + VirtText virt_text) +{ + if (src_id == 0) { + src_id = (int)nvim_create_namespace((String)STRING_INIT); + } + + BufhlLine *lineinfo = bufhl_tree_ref(&buf->b_bufhl_info, lnum, true); + + bufhl_clear_virttext(&lineinfo->virt_text); + if (kv_size(virt_text) > 0) { + lineinfo->virt_text_src = src_id; + lineinfo->virt_text = virt_text; + } else { + lineinfo->virt_text_src = 0; + // currently not needed, but allow a future caller with + // 0 size and non-zero capacity + kv_destroy(virt_text); + } + + if (0 < lnum && lnum <= buf->b_ml.ml_line_count) { + redraw_buf_line_later(buf, lnum); } return src_id; } +static void bufhl_clear_virttext(VirtText *text) +{ + for (size_t i = 0; i < kv_size(*text); i++) { + xfree(kv_A(*text, i).text); + } + kv_destroy(*text); + *text = (VirtText)KV_INITIAL_VALUE; +} + /// Clear bufhl highlights from a given source group and range of lines. /// /// @param buf The buffer to remove highlights from @@ -5003,33 +5439,29 @@ int bufhl_add_hl(buf_T *buf, void bufhl_clear_line_range(buf_T *buf, int src_id, linenr_T line_start, - linenr_T line_end) { - if (!buf->b_bufhl_info) { - return; - } - linenr_T line; - linenr_T first_changed = MAXLNUM, last_changed = -1; - // In the case line_start - line_end << bufhl_info->size - // it might be better to reverse this, i e loop over the lines - // to clear on. - bufhl_vec_T unused; - map_foreach(buf->b_bufhl_info, line, unused, { - (void)unused; - if (line_start <= line && line <= line_end) { - if (bufhl_clear_line(buf->b_bufhl_info, src_id, line)) { - if (line > last_changed) { - last_changed = line; - } - if (line < first_changed) { - first_changed = line; - } + linenr_T line_end) +{ + kbitr_t(bufhl) itr; + BufhlLine *l, t = BUFHLLINE_INIT(line_start); + if (!kb_itr_get(bufhl, &buf->b_bufhl_info, &t, &itr)) { + kb_itr_next(bufhl, &buf->b_bufhl_info, &itr); + } + for (; kb_itr_valid(&itr); kb_itr_next(bufhl, &buf->b_bufhl_info, &itr)) { + l = kb_itr_key(&itr); + linenr_T line = l->line; + if (line > line_end) { + break; + } + if (line_start <= line) { + BufhlLineStatus status = bufhl_clear_line(l, src_id, line); + if (status != kBLSUnchanged) { + redraw_buf_line_later(buf, line); + } + if (status == kBLSDeleted) { + kb_del_itr(bufhl, &buf->b_bufhl_info, &itr); + xfree(l); } } - }) - - if (last_changed != -1) { - changed_lines_buf(buf, first_changed, last_changed+1, 0); - redraw_buf_later(buf, VALID); } } @@ -5038,40 +5470,52 @@ void bufhl_clear_line_range(buf_T *buf, /// @param bufhl_info The highlight info for the buffer /// @param src_id Highlight source group to clear, or -1 to clear all groups. /// @param lnum Linenr where the highlight should be cleared -static bool bufhl_clear_line(bufhl_info_T *bufhl_info, int src_id, int lnum) { - bufhl_vec_T* lineinfo = map_ref(linenr_T, bufhl_vec_T)(bufhl_info, - lnum, false); - size_t oldsize = kv_size(*lineinfo); +static BufhlLineStatus bufhl_clear_line(BufhlLine *lineinfo, int src_id, + linenr_T lnum) +{ + BufhlLineStatus changed = kBLSUnchanged; + size_t oldsize = kv_size(lineinfo->items); if (src_id < 0) { - kv_size(*lineinfo) = 0; + kv_size(lineinfo->items) = 0; } else { - size_t newind = 0; - for (size_t i = 0; i < kv_size(*lineinfo); i++) { - if (kv_A(*lineinfo, i).src_id != src_id) { - if (i != newind) { - kv_A(*lineinfo, newind) = kv_A(*lineinfo, i); + size_t newidx = 0; + for (size_t i = 0; i < kv_size(lineinfo->items); i++) { + if (kv_A(lineinfo->items, i).src_id != src_id) { + if (i != newidx) { + kv_A(lineinfo->items, newidx) = kv_A(lineinfo->items, i); } - newind++; + newidx++; } } - kv_size(*lineinfo) = newind; + kv_size(lineinfo->items) = newidx; + } + if (kv_size(lineinfo->items) != oldsize) { + changed = kBLSChanged; + } + + if (kv_size(lineinfo->virt_text) != 0 + && (src_id < 0 || src_id == lineinfo->virt_text_src)) { + bufhl_clear_virttext(&lineinfo->virt_text); + lineinfo->virt_text_src = 0; + changed = kBLSChanged; } - if (kv_size(*lineinfo) == 0) { - kv_destroy(*lineinfo); - map_del(linenr_T, bufhl_vec_T)(bufhl_info, lnum); + if (kv_size(lineinfo->items) == 0 && kv_size(lineinfo->virt_text) == 0) { + kv_destroy(lineinfo->items); + return kBLSDeleted; } - return kv_size(*lineinfo) != oldsize; + return changed; } + /// Remove all highlights and free the highlight data -void bufhl_clear_all(buf_T* buf) { - if (!buf->b_bufhl_info) { - return; - } +void bufhl_clear_all(buf_T *buf) +{ bufhl_clear_line_range(buf, -1, 1, MAXLNUM); - map_free(linenr_T, bufhl_vec_T)(buf->b_bufhl_info); - buf->b_bufhl_info = NULL; + kb_destroy(bufhl, (&buf->b_bufhl_info)); + kb_init(&buf->b_bufhl_info); + kv_destroy(buf->b_bufhl_move_space); + kv_init(buf->b_bufhl_move_space); } /// Adjust a placed highlight for inserted/deleted lines. @@ -5079,29 +5523,49 @@ void bufhl_mark_adjust(buf_T* buf, linenr_T line1, linenr_T line2, long amount, - long amount_after) { - if (!buf->b_bufhl_info) { + long amount_after, + bool end_temp) +{ + kbitr_t(bufhl) itr; + BufhlLine *l, t = BUFHLLINE_INIT(line1); + if (end_temp && amount < 0) { + // Move all items from b_bufhl_move_space to the btree. + for (size_t i = 0; i < kv_size(buf->b_bufhl_move_space); i++) { + l = kv_A(buf->b_bufhl_move_space, i); + l->line += amount; + kb_put(bufhl, &buf->b_bufhl_info, l); + } + kv_size(buf->b_bufhl_move_space) = 0; return; } - bufhl_info_T *newmap = map_new(linenr_T, bufhl_vec_T)(); - linenr_T line; - bufhl_vec_T lineinfo; - map_foreach(buf->b_bufhl_info, line, lineinfo, { - if (line >= line1 && line <= line2) { + if (!kb_itr_get(bufhl, &buf->b_bufhl_info, &t, &itr)) { + kb_itr_next(bufhl, &buf->b_bufhl_info, &itr); + } + for (; kb_itr_valid(&itr); kb_itr_next(bufhl, &buf->b_bufhl_info, &itr)) { + l = kb_itr_key(&itr); + if (l->line >= line1 && l->line <= line2) { + if (end_temp && amount > 0) { + kb_del_itr(bufhl, &buf->b_bufhl_info, &itr); + kv_push(buf->b_bufhl_move_space, l); + } if (amount == MAXLNUM) { - bufhl_clear_line(buf->b_bufhl_info, -1, line); - continue; + if (bufhl_clear_line(l, -1, l->line) == kBLSDeleted) { + kb_del_itr(bufhl, &buf->b_bufhl_info, &itr); + xfree(l); + } else { + assert(false); + } } else { - line += amount; + l->line += amount; } - } else if (line > line2) { - line += amount_after; + } else if (l->line > line2) { + if (amount_after == 0) { + break; + } + l->line += amount_after; } - map_put(linenr_T, bufhl_vec_T)(newmap, line, lineinfo); - }); - map_free(linenr_T, bufhl_vec_T)(buf->b_bufhl_info); - buf->b_bufhl_info = newmap; + } } @@ -5111,14 +5575,15 @@ void bufhl_mark_adjust(buf_T* buf, /// @param lnum The line number /// @param[out] info The highligts for the line /// @return true if there was highlights to display -bool bufhl_start_line(buf_T *buf, linenr_T lnum, bufhl_lineinfo_T *info) { - if (!buf->b_bufhl_info) { +bool bufhl_start_line(buf_T *buf, linenr_T lnum, BufhlLineInfo *info) +{ + BufhlLine *lineinfo = bufhl_tree_ref(&buf->b_bufhl_info, lnum, false); + if (!lineinfo) { return false; } - info->valid_to = -1; - info->entries = map_get(linenr_T, bufhl_vec_T)(buf->b_bufhl_info, lnum); - return kv_size(info->entries) > 0; + info->line = lineinfo; + return true; } /// get highlighting at column col @@ -5131,14 +5596,15 @@ bool bufhl_start_line(buf_T *buf, linenr_T lnum, bufhl_lineinfo_T *info) { /// @param info The info returned by bufhl_start_line /// @param col The column to get the attr for /// @return The highilight attr to display at the column -int bufhl_get_attr(bufhl_lineinfo_T *info, colnr_T col) { +int bufhl_get_attr(BufhlLineInfo *info, colnr_T col) +{ if (col <= info->valid_to) { return info->current; } int attr = 0; info->valid_to = MAXCOL; - for (size_t i = 0; i < kv_size(info->entries); i++) { - bufhl_hl_item_T entry = kv_A(info->entries, i); + for (size_t i = 0; i < kv_size(info->line->items); i++) { + BufhlItem entry = kv_A(info->line->items, i); if (entry.start <= col && col <= entry.stop) { int entry_attr = syn_id2attr(entry.hl_id); attr = hl_combine_attr(attr, entry_attr); @@ -5231,12 +5697,30 @@ wipe_buffer ( 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. */ + if (!aucmd) { + // Don't trigger BufDelete autocommands here. block_autocmds(); - close_buffer(NULL, buf, DOBUF_WIPE, FALSE); - if (!aucmd) + } + close_buffer(NULL, buf, DOBUF_WIPE, false); + if (!aucmd) { unblock_autocmds(); + } +} + +/// Creates or switches to a scratch buffer. :h special-buffers +/// Scratch buffer is: +/// - buftype=nofile bufhidden=hide noswapfile +/// - Always considered 'nomodified' +/// +/// @param bufnr Buffer to switch to, or 0 to create a new buffer. +/// +/// @see curbufIsChanged() +void buf_open_scratch(handle_T bufnr, char *bufname) +{ + (void)do_ecmd((int)bufnr, NULL, NULL, NULL, ECMD_ONE, ECMD_HIDE, NULL); + (void)setfname(curbuf, (char_u *)bufname, NULL, true); + set_option_value("bh", 0L, "hide", OPT_LOCAL); + set_option_value("bt", 0L, "nofile", OPT_LOCAL); + set_option_value("swf", 0L, NULL, OPT_LOCAL); + RESET_BINDING(curwin); } |