diff options
50 files changed, 969 insertions, 419 deletions
diff --git a/.github/scripts/reviews.js b/.github/scripts/reviews.js index c1318edec0..a31c94eebd 100644 --- a/.github/scripts/reviews.js +++ b/.github/scripts/reviews.js @@ -20,12 +20,17 @@ module.exports = async ({github, context}) => { if (labels.includes('ci')) { reviewers.add("dundargoc") reviewers.add("jamessan") + reviewers.add("justinmk") } if (labels.includes('column')) { reviewers.add("lewis6991") } + if (labels.includes('dependencies')) { + reviewers.add("jamessan") + } + if (labels.includes('diagnostic')) { reviewers.add("gpanders") } @@ -34,10 +39,6 @@ module.exports = async ({github, context}) => { reviewers.add("lewis6991") } - if (labels.includes('dependencies')) { - reviewers.add("jamessan") - } - if (labels.includes('distribution')) { reviewers.add("jamessan") } @@ -61,6 +62,19 @@ module.exports = async ({github, context}) => { reviewers.add("mfussenegger") } + if (labels.includes('project-management')) { + reviewers.add("bfredl") + reviewers.add("justinmk") + } + + if (labels.includes('refactor')) { + reviewers.add("bfredl") + } + + if (labels.includes('test')) { + reviewers.add("justinmk") + } + if (labels.includes('treesitter')) { reviewers.add("bfredl") reviewers.add("clason") diff --git a/.github/workflows/reviews.yml b/.github/workflows/add-reviewers.yml index 6c8c7ff8ed..6c8c7ff8ed 100644 --- a/.github/workflows/reviews.yml +++ b/.github/workflows/add-reviewers.yml diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql.yml index 6ba0d43fe0..6ba0d43fe0 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql.yml diff --git a/.github/workflows/coverity-scan.yml b/.github/workflows/coverity.yml index ce7822b5c1..ce7822b5c1 100644 --- a/.github/workflows/coverity-scan.yml +++ b/.github/workflows/coverity.yml diff --git a/.github/workflows/commitlint.yml b/.github/workflows/lintcommit.yml index a7a227865d..a7a227865d 100644 --- a/.github/workflows/commitlint.yml +++ b/.github/workflows/lintcommit.yml diff --git a/.github/workflows/remove-reviewers-on-draft.yml b/.github/workflows/remove-reviewers.yml index f707f79737..f707f79737 100644 --- a/.github/workflows/remove-reviewers-on-draft.yml +++ b/.github/workflows/remove-reviewers.yml diff --git a/cmake.deps/CMakeLists.txt b/cmake.deps/CMakeLists.txt index 28d60c45eb..a7c6502a5f 100644 --- a/cmake.deps/CMakeLists.txt +++ b/cmake.deps/CMakeLists.txt @@ -153,8 +153,8 @@ set(MSGPACK_URL https://github.com/msgpack/msgpack-c/releases/download/c-4.0.0/m set(MSGPACK_SHA256 420fe35e7572f2a168d17e660ef981a589c9cbe77faa25eb34a520e1fcc032c8) # https://github.com/LuaJIT/LuaJIT/tree/v2.1 -set(LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/6c4826f12c4d33b8b978004bc681eb1eef2be977.tar.gz) -set(LUAJIT_SHA256 19a911fdd77af69e48fa50749606a9009696f543cc88e898b4480d8d3c8828f5) +set(LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/564147f518af5a5d8985d9e09fc3a768231f4e75.tar.gz) +set(LUAJIT_SHA256 67019962f0c8102f90f0ba540be2f3462ed3ca8d22672b32a214ffa63addd40b) set(LUA_URL https://www.lua.org/ftp/lua-5.1.5.tar.gz) set(LUA_SHA256 2640fc56a795f29d28ef15e13c34a47e223960b0240e8cb0a82d9b0738695333) diff --git a/cmake/GenerateVersion.cmake b/cmake/GenerateVersion.cmake index 7ce6ee430e..8cea39e4de 100644 --- a/cmake/GenerateVersion.cmake +++ b/cmake/GenerateVersion.cmake @@ -2,7 +2,7 @@ set(NVIM_VERSION "v${NVIM_VERSION_MAJOR}.${NVIM_VERSION_MINOR}.${NVIM_VERSION_PATCH}${NVIM_VERSION_PRERELEASE}") execute_process( - COMMAND git describe --first-parent --dirty --always + COMMAND git --git-dir=${NVIM_SOURCE_DIR}/.git --work-tree=${NVIM_SOURCE_DIR} describe --first-parent --dirty --always OUTPUT_VARIABLE GIT_TAG OUTPUT_STRIP_TRAILING_WHITESPACE RESULT_VARIABLE RES) diff --git a/runtime/autoload/provider/node.vim b/runtime/autoload/provider/node.vim index 45b1dd4fd7..87af0094fe 100644 --- a/runtime/autoload/provider/node.vim +++ b/runtime/autoload/provider/node.vim @@ -71,13 +71,11 @@ function! provider#node#Detect() abort let yarn_opts = deepcopy(s:NodeHandler) let yarn_opts.entry_point = '/node_modules/neovim/bin/cli.js' " `yarn global dir` is slow (> 250ms), try the default path first - " XXX: The following code is not portable " https://github.com/yarnpkg/yarn/issues/2049#issuecomment-263183768 - if has('unix') - let yarn_default_path = $HOME . '/.config/yarn/global/' . yarn_opts.entry_point - if filereadable(yarn_default_path) - return [yarn_default_path, ''] - endif + let yarn_config_dir = has('win32') ? '/AppData/Local/Yarn/Data' : '/.config/yarn' + let yarn_default_path = $HOME . yarn_config_dir . '/global/' . yarn_opts.entry_point + if filereadable(yarn_default_path) + return [yarn_default_path, ''] endif let yarn_opts.job_id = jobstart('yarn global dir', yarn_opts) endif diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index acfe8821ea..fdf3d29e94 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -1,5 +1,7 @@ local M = {} +local iswin = vim.loop.os_uname().sysname == 'Windows_NT' + --- Iterate over all the parents of the given file or directory. --- --- Example: @@ -40,7 +42,19 @@ function M.dirname(file) if file == nil then return nil end - return vim.fn.fnamemodify(file, ':h') + vim.validate({ file = { file, 's' } }) + if iswin and file:match('^%w:[\\/]?$') then + return (file:gsub('\\', '/')) + elseif not file:match('[\\/]') then + return '.' + elseif file == '/' or file:match('^/[^/]+$') then + return '/' + end + local dir = file:match('[/\\]$') and file:sub(1, #file - 1) or file:match('^([/\\]?.+)[/\\]') + if iswin and dir:match('^%w:$') then + return dir .. '/' + end + return (dir:gsub('\\', '/')) end --- Return the basename of the given file or directory @@ -48,7 +62,14 @@ end ---@param file (string) File or directory ---@return (string) Basename of {file} function M.basename(file) - return vim.fn.fnamemodify(file, ':t') + if file == nil then + return nil + end + vim.validate({ file = { file, 's' } }) + if iswin and file:match('^%w:[\\/]?$') then + return '' + end + return file:match('[/\\]$') and '' or (file:match('[^\\/]*$'):gsub('\\', '/')) end --- Return an iterator over the files and directories located in {path} @@ -228,7 +249,12 @@ end ---@return (string) Normalized path function M.normalize(path) vim.validate({ path = { path, 's' } }) - return (path:gsub('^~/', vim.env.HOME .. '/'):gsub('%$([%w_]+)', vim.env):gsub('\\', '/')) + return ( + path + :gsub('^~/', vim.loop.os_homedir() .. '/') + :gsub('%$([%w_]+)', vim.loop.os_getenv) + :gsub('\\', '/') + ) end return M diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 6511fe6fbb..5a64128bcb 100755 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -206,6 +206,7 @@ add_custom_target(update_version_stamp -DNVIM_VERSION_PATCH=${NVIM_VERSION_PATCH} -DNVIM_VERSION_PRERELEASE=${NVIM_VERSION_PRERELEASE} -DOUTPUT=${NVIM_VERSION_GIT_H} + -DNVIM_SOURCE_DIR=${CMAKE_SOURCE_DIR} -P ${PROJECT_SOURCE_DIR}/cmake/GenerateVersion.cmake BYPRODUCTS ${NVIM_VERSION_GIT_H}) diff --git a/src/nvim/api/private/defs.h b/src/nvim/api/private/defs.h index 8693751c97..8acbf0d9de 100644 --- a/src/nvim/api/private/defs.h +++ b/src/nvim/api/private/defs.h @@ -12,7 +12,7 @@ #define ARRAY_DICT_INIT KV_INITIAL_VALUE #define STRING_INIT { .data = NULL, .size = 0 } #define OBJECT_INIT { .type = kObjectTypeNil } -#define ERROR_INIT { .type = kErrorTypeNone, .msg = NULL } +#define ERROR_INIT ((Error) { .type = kErrorTypeNone, .msg = NULL }) #define REMOTE_TYPE(type) typedef handle_T type #define ERROR_SET(e) ((e)->type != kErrorTypeNone) diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index 57b5adca53..ab6c22ff6c 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -659,9 +659,22 @@ void free_all_autocmds(void) api_free_string(name); }) map_destroy(int, String)(&map_augroup_id_to_name); + + // aucmd_win[] is freed in win_free_all() } #endif +/// Return true if "win" is an active entry in aucmd_win[]. +bool is_aucmd_win(win_T *win) +{ + for (int i = 0; i < AUCMD_WIN_COUNT; i++) { + if (aucmd_win[i].auc_win_used && aucmd_win[i].auc_win == win) { + return true; + } + } + return false; +} + // Return the event number for event name "start". // Return NUM_EVENTS if the event name was not found. // Return a pointer to the next event name in "end". @@ -1319,7 +1332,7 @@ void ex_doautoall(exarg_T *eap) // Execute the modeline settings, but don't set window-local // options if we are using the current window for another // buffer. - do_modelines(curwin == aucmd_win ? OPT_NOWIN : 0); + do_modelines(is_aucmd_win(curwin) ? OPT_NOWIN : 0); } // restore the current window @@ -1358,7 +1371,7 @@ bool check_nomodeline(char **argp) /// Prepare for executing autocommands for (hidden) buffer `buf`. /// If the current buffer is not in any visible window, put it in a temporary -/// floating window `aucmd_win`. +/// floating window using an entry in `aucmd_win[]`. /// Set `curbuf` and `curwin` to match `buf`. /// /// @param aco structure to save values in @@ -1381,15 +1394,29 @@ void aucmd_prepbuf(aco_save_T *aco, buf_T *buf) } } - // Allocate the `aucmd_win` dummy floating window. - if (win == NULL && aucmd_win == NULL) { - win_alloc_aucmd_win(); - need_append = false; - } - if (win == NULL && aucmd_win_used) { - // Strange recursive autocommand, fall back to using the current - // window. Expect a few side effects... - win = curwin; + // Allocate a window when needed. + win_T *auc_win = NULL; + int auc_idx = AUCMD_WIN_COUNT; + if (win == NULL) { + for (auc_idx = 0; auc_idx < AUCMD_WIN_COUNT; auc_idx++) { + if (!aucmd_win[auc_idx].auc_win_used) { + break; + } + } + + if (auc_idx == AUCMD_WIN_COUNT) { + kv_push(aucmd_win_vec, ((aucmdwin_T){ + .auc_win = NULL, + .auc_win_used = false, + })); + } + + if (aucmd_win[auc_idx].auc_win == NULL) { + win_alloc_aucmd_win(auc_idx); + need_append = false; + } + auc_win = aucmd_win[auc_idx].auc_win; + aucmd_win[auc_idx].auc_win_used = true; } aco->save_curwin_handle = curwin->handle; @@ -1399,42 +1426,41 @@ void aucmd_prepbuf(aco_save_T *aco, buf_T *buf) // There is a window for "buf" in the current tab page, make it the // curwin. This is preferred, it has the least side effects (esp. if // "buf" is curbuf). - aco->use_aucmd_win = false; + aco->use_aucmd_win_idx = -1; curwin = win; } else { - // There is no window for "buf", use "aucmd_win". To minimize the side + // There is no window for "buf", use "auc_win". To minimize the side // effects, insert it in the current tab page. // Anything related to a window (e.g., setting folds) may have // unexpected results. - aco->use_aucmd_win = true; - aucmd_win_used = true; - aucmd_win->w_buffer = buf; - aucmd_win->w_s = &buf->b_s; + aco->use_aucmd_win_idx = auc_idx; + auc_win->w_buffer = buf; + auc_win->w_s = &buf->b_s; buf->b_nwindows++; - win_init_empty(aucmd_win); // set cursor and topline to safe values + win_init_empty(auc_win); // set cursor and topline to safe values // Make sure w_localdir and globaldir are NULL to avoid a chdir() in // win_enter_ext(). - XFREE_CLEAR(aucmd_win->w_localdir); + XFREE_CLEAR(auc_win->w_localdir); aco->globaldir = globaldir; globaldir = NULL; block_autocmds(); // We don't want BufEnter/WinEnter autocommands. if (need_append) { - win_append(lastwin, aucmd_win); - pmap_put(handle_T)(&window_handles, aucmd_win->handle, aucmd_win); - win_config_float(aucmd_win, aucmd_win->w_float_config); + win_append(lastwin, auc_win); + pmap_put(handle_T)(&window_handles, auc_win->handle, auc_win); + win_config_float(auc_win, auc_win->w_float_config); } // Prevent chdir() call in win_enter_ext(), through do_autochdir() int save_acd = p_acd; p_acd = false; // no redrawing and don't set the window title RedrawingDisabled++; - win_enter(aucmd_win, false); + win_enter(auc_win, false); RedrawingDisabled--; p_acd = save_acd; unblock_autocmds(); - curwin = aucmd_win; + curwin = auc_win; } curbuf = buf; aco->new_curwin_handle = curwin->handle; @@ -1451,18 +1477,20 @@ void aucmd_prepbuf(aco_save_T *aco, buf_T *buf) /// @param aco structure holding saved values void aucmd_restbuf(aco_save_T *aco) { - if (aco->use_aucmd_win) { + if (aco->use_aucmd_win_idx >= 0) { + win_T *awp = aucmd_win[aco->use_aucmd_win_idx].auc_win; + curbuf->b_nwindows--; - // Find "aucmd_win", it can't be closed, but it may be in another tab page. + // Find "awp", it can't be closed, but it may be in another tab page. // Do not trigger autocommands here. block_autocmds(); - if (curwin != aucmd_win) { + if (curwin != awp) { FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp == aucmd_win) { + if (wp == awp) { if (tp != curtab) { goto_tabpage_tp(tp, true, true); } - win_goto(aucmd_win); + win_goto(awp); goto win_found; } } @@ -1477,7 +1505,9 @@ win_found: grid_free(&curwin->w_grid_alloc); } - aucmd_win_used = false; + // The window is marked as not used, but it is not freed, it can be + // used again. + aucmd_win[aco->use_aucmd_win_idx].auc_win_used = false; if (!valid_tabpage_win(curtab)) { // no valid window in current tabpage @@ -1498,8 +1528,8 @@ win_found: entering_window(curwin); prevwin = win_find_by_handle(aco->save_prevwin_handle); - vars_clear(&aucmd_win->w_vars->dv_hashtab); // free all w: variables - hash_init(&aucmd_win->w_vars->dv_hashtab); // re-use the hashtab + vars_clear(&awp->w_vars->dv_hashtab); // free all w: variables + hash_init(&awp->w_vars->dv_hashtab); // re-use the hashtab xfree(globaldir); globaldir = aco->globaldir; diff --git a/src/nvim/autocmd.h b/src/nvim/autocmd.h index c159254c29..791b589167 100644 --- a/src/nvim/autocmd.h +++ b/src/nvim/autocmd.h @@ -25,7 +25,7 @@ struct AutoPat_S; // not the current buffer. typedef struct { buf_T *save_curbuf; ///< saved curbuf - bool use_aucmd_win; ///< using aucmd_win + int use_aucmd_win_idx; ///< index in aucmd_win[] if >= 0 handle_T save_curwin_handle; ///< ID of saved curwin handle_T new_curwin_handle; ///< ID of new curwin handle_T save_prevwin_handle; ///< ID of saved prevwin diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 6cb171978b..c9fe5f9670 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -175,6 +175,20 @@ static int read_buffer(int read_stdin, exarg_T *eap, int flags) return retval; } +/// Ensure buffer "buf" is loaded. Does not trigger the swap-exists action. +void buffer_ensure_loaded(buf_T *buf) +{ + if (buf->b_ml.ml_mfp == NULL) { + aco_save_T aco; + + // Make sure the buffer is in a window. + aucmd_prepbuf(&aco, buf); + swap_exists_action = SEA_NONE; + open_buffer(false, NULL, 0); + aucmd_restbuf(&aco); + } +} + /// Open current buffer, that is: open the memfile and read the file into /// memory. /// @@ -352,7 +366,7 @@ int open_buffer(int read_stdin, exarg_T *eap, int flags_arg) 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. + // Go to the buffer that was opened, make sure it is in a window. aucmd_prepbuf(&aco, old_curbuf.br_buf); do_modelines(0); curbuf->b_flags &= ~(BF_CHECK_RO | BF_NEVERLOADED); @@ -1316,7 +1330,7 @@ int do_buffer(int action, int start, int dir, int count, int forceit) // Repeat this so long as we end up in a window with this buffer. while (buf == curbuf && !(curwin->w_closing || curwin->w_buffer->b_locked > 0) - && (lastwin == aucmd_win || !last_window(curwin))) { + && (is_aucmd_win(lastwin) || !last_window(curwin))) { if (win_close(curwin, false, false) == FAIL) { break; } @@ -4155,7 +4169,7 @@ bool buf_contents_changed(buf_T *buf) exarg_T ea; prep_exarg(&ea, buf); - // set curwin/curbuf to buf and save a few things + // Set curwin/curbuf to buf and save a few things. aco_save_T aco; aucmd_prepbuf(&aco, newbuf); diff --git a/src/nvim/diff.c b/src/nvim/diff.c index 1ae0e91cda..c913260a80 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -1890,7 +1890,7 @@ static void count_filler_lines_and_topline(int *curlinenum_to, int *linesfiller, const diff_T *curdif = thistopdiff; int ch_virtual_lines = 0; int isfiller = 0; - while (virtual_lines_passed) { + while (virtual_lines_passed > 0) { if (ch_virtual_lines) { virtual_lines_passed--; ch_virtual_lines--; @@ -1946,7 +1946,6 @@ static void calculate_topfill_and_topline(const int fromidx, const int toidx, co virtual_lines_passed -= from_topfill; // count the same amount of virtual lines in the toidx buffer - curdif = thistopdiff; int curlinenum_to = thistopdiff->df_lnum[toidx]; int linesfiller = 0; count_filler_lines_and_topline(&curlinenum_to, &linesfiller, @@ -2628,7 +2627,7 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp) break; } } - if (dp->is_linematched) { + if (dp != NULL && dp->is_linematched) { while (dp && dp->df_next && lnum == dp->df_count[idx] + dp->df_lnum[idx] && dp->df_next->df_lnum[idx] == lnum) { @@ -2943,7 +2942,7 @@ void ex_diffgetput(exarg_T *eap) // Need to make the other buffer the current buffer to be able to make // changes in it. - // set curwin/curbuf to buf and save a few things + // Set curwin/curbuf to buf and save a few things. aucmd_prepbuf(&aco, curtab->tp_diffbuf[idx_other]); } diff --git a/src/nvim/eval.c b/src/nvim/eval.c index dfa9238327..c4afd6934c 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -4174,8 +4174,11 @@ bool garbage_collect(bool testing) ABORTING(set_ref_in_fmark)(wp->w_jumplist[i].fmark, copyID); } } - if (aucmd_win != NULL) { - ABORTING(set_ref_in_item)(&aucmd_win->w_winvar.di_tv, copyID, NULL, NULL); + // window-local variables in autocmd windows + for (int i = 0; i < AUCMD_WIN_COUNT; i++) { + if (aucmd_win[i].auc_win != NULL) { + ABORTING(set_ref_in_item)(&aucmd_win[i].auc_win->w_winvar.di_tv, copyID, NULL, NULL); + } } // registers (ShaDa additional data) diff --git a/src/nvim/eval/buffer.c b/src/nvim/eval/buffer.c index 4816a955a8..72eb0f8d67 100644 --- a/src/nvim/eval/buffer.c +++ b/src/nvim/eval/buffer.c @@ -302,13 +302,8 @@ void f_bufload(typval_T *argvars, typval_T *unused, EvalFuncData fptr) { buf_T *buf = get_buf_arg(&argvars[0]); - if (buf != NULL && buf->b_ml.ml_mfp == NULL) { - aco_save_T aco; - - aucmd_prepbuf(&aco, buf); - swap_exists_action = SEA_NONE; - open_buffer(false, NULL, 0); - aucmd_restbuf(&aco); + if (buf != NULL) { + buffer_ensure_loaded(buf); } } diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c index 5459eac5bf..d37631af8c 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -1800,7 +1800,7 @@ void f_setbufvar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) if (*varname == '&') { aco_save_T aco; - // set curbuf to be our buf, temporarily + // Set curbuf to be our buf, temporarily. aucmd_prepbuf(&aco, buf); set_option_from_tv(varname + 1, varp); diff --git a/src/nvim/eval/window.c b/src/nvim/eval/window.c index d6c761bc66..7cb346216d 100644 --- a/src/nvim/eval/window.c +++ b/src/nvim/eval/window.c @@ -590,7 +590,7 @@ static void win_move_into_split(win_T *wp, win_T *targetwin, int size, int flags int height = wp->w_height; win_T *oldwin = curwin; - if (wp == targetwin || wp == aucmd_win) { + if (wp == targetwin || is_aucmd_win(wp)) { return; } @@ -674,7 +674,7 @@ void f_win_gettype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) return; } } - if (wp == aucmd_win) { + if (is_aucmd_win(wp)) { rettv->vval.v_string = xstrdup("autocmd"); } else if (wp->w_p_pvw) { rettv->vval.v_string = xstrdup("preview"); diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 96e61c13fb..8ae7646268 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -719,187 +719,6 @@ sortend: } } -/// ":retab". -void ex_retab(exarg_T *eap) -{ - linenr_T lnum; - bool got_tab = false; - long num_spaces = 0; - long num_tabs; - long len; - long col; - long vcol; - long start_col = 0; // For start of white-space string - long start_vcol = 0; // For start of white-space string - long old_len; - char *ptr; - char *new_line = (char *)1; // init to non-NULL - bool did_undo; // called u_save for current line - long *new_vts_array = NULL; - char *new_ts_str; // string value of tab argument - - int save_list; - linenr_T first_line = 0; // first changed line - linenr_T last_line = 0; // last changed line - - save_list = curwin->w_p_list; - curwin->w_p_list = 0; // don't want list mode here - - new_ts_str = eap->arg; - if (!tabstop_set(eap->arg, &new_vts_array)) { - return; - } - while (ascii_isdigit(*(eap->arg)) || *(eap->arg) == ',') { - (eap->arg)++; - } - - // This ensures that either new_vts_array and new_ts_str are freshly - // allocated, or new_vts_array points to an existing array and new_ts_str - // is null. - if (new_vts_array == NULL) { - new_vts_array = curbuf->b_p_vts_array; - new_ts_str = NULL; - } else { - new_ts_str = xstrnsave(new_ts_str, (size_t)(eap->arg - new_ts_str)); - } - for (lnum = eap->line1; !got_int && lnum <= eap->line2; lnum++) { - ptr = ml_get(lnum); - col = 0; - vcol = 0; - did_undo = false; - for (;;) { - if (ascii_iswhite(ptr[col])) { - if (!got_tab && num_spaces == 0) { - // First consecutive white-space - start_vcol = vcol; - start_col = col; - } - if (ptr[col] == ' ') { - num_spaces++; - } else { - got_tab = true; - } - } else { - if (got_tab || (eap->forceit && num_spaces > 1)) { - // Retabulate this string of white-space - - // len is virtual length of white string - len = num_spaces = vcol - start_vcol; - num_tabs = 0; - if (!curbuf->b_p_et) { - int t, s; - - tabstop_fromto((colnr_T)start_vcol, (colnr_T)vcol, - curbuf->b_p_ts, new_vts_array, &t, &s); - num_tabs = t; - num_spaces = s; - } - if (curbuf->b_p_et || got_tab - || (num_spaces + num_tabs < len)) { - if (did_undo == false) { - did_undo = true; - if (u_save((linenr_T)(lnum - 1), - (linenr_T)(lnum + 1)) == FAIL) { - new_line = NULL; // flag out-of-memory - break; - } - } - - // len is actual number of white characters used - len = num_spaces + num_tabs; - old_len = (long)strlen(ptr); - const long new_len = old_len - col + start_col + len + 1; - if (new_len <= 0 || new_len >= MAXCOL) { - emsg(_(e_resulting_text_too_long)); - break; - } - new_line = xmalloc((size_t)new_len); - - if (start_col > 0) { - memmove(new_line, ptr, (size_t)start_col); - } - memmove(new_line + start_col + len, - ptr + col, (size_t)(old_len - col + 1)); - ptr = new_line + start_col; - for (col = 0; col < len; col++) { - ptr[col] = (col < num_tabs) ? '\t' : ' '; - } - if (ml_replace(lnum, new_line, false) == OK) { - // "new_line" may have been copied - new_line = curbuf->b_ml.ml_line_ptr; - extmark_splice_cols(curbuf, lnum - 1, 0, (colnr_T)old_len, - (colnr_T)new_len - 1, kExtmarkUndo); - } - if (first_line == 0) { - first_line = lnum; - } - last_line = lnum; - ptr = new_line; - col = start_col + len; - } - } - got_tab = false; - num_spaces = 0; - } - if (ptr[col] == NUL) { - break; - } - vcol += win_chartabsize(curwin, ptr + col, (colnr_T)vcol); - if (vcol >= MAXCOL) { - emsg(_(e_resulting_text_too_long)); - break; - } - col += utfc_ptr2len(ptr + col); - } - if (new_line == NULL) { // out of memory - break; - } - line_breakcheck(); - } - if (got_int) { - emsg(_(e_interr)); - } - - // If a single value was given then it can be considered equal to - // either the value of 'tabstop' or the value of 'vartabstop'. - if (tabstop_count(curbuf->b_p_vts_array) == 0 - && tabstop_count(new_vts_array) == 1 - && curbuf->b_p_ts == tabstop_first(new_vts_array)) { - // not changed - } else if (tabstop_count(curbuf->b_p_vts_array) > 0 - && tabstop_eq(curbuf->b_p_vts_array, new_vts_array)) { - // not changed - } else { - redraw_curbuf_later(UPD_NOT_VALID); - } - if (first_line != 0) { - changed_lines(first_line, 0, last_line + 1, 0L, true); - } - - curwin->w_p_list = save_list; // restore 'list' - - if (new_ts_str != NULL) { // set the new tabstop - // If 'vartabstop' is in use or if the value given to retab has more - // than one tabstop then update 'vartabstop'. - long *old_vts_ary = curbuf->b_p_vts_array; - - if (tabstop_count(old_vts_ary) > 0 || tabstop_count(new_vts_array) > 1) { - set_string_option_direct("vts", -1, new_ts_str, OPT_FREE | OPT_LOCAL, 0); - curbuf->b_p_vts_array = new_vts_array; - xfree(old_vts_ary); - } else { - // 'vartabstop' wasn't in use and a single value was given to - // retab then update 'tabstop'. - curbuf->b_p_ts = tabstop_first(new_vts_array); - xfree(new_vts_array); - } - xfree(new_ts_str); - } - coladvance(curwin->w_curswant); - - u_clearline(); -} - /// :move command - move lines line1-line2 to line dest /// /// @return FAIL for failure, OK otherwise diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index c2d73360e3..1ea344dc0e 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -766,53 +766,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) // of interrupts or errors to exceptions, and ensure that no more // commands are executed. if (did_throw) { - assert(current_exception != NULL); - char *p = NULL; - msglist_T *messages = NULL; - msglist_T *next; - - // If the uncaught exception is a user exception, report it as an - // error. If it is an error exception, display the saved error - // message now. For an interrupt exception, do nothing; the - // interrupt message is given elsewhere. - switch (current_exception->type) { - case ET_USER: - vim_snprintf((char *)IObuff, IOSIZE, - _("E605: Exception not caught: %s"), - current_exception->value); - p = xstrdup((char *)IObuff); - break; - case ET_ERROR: - messages = current_exception->messages; - current_exception->messages = NULL; - break; - case ET_INTERRUPT: - break; - } - - estack_push(ETYPE_EXCEPT, current_exception->throw_name, current_exception->throw_lnum); - current_exception->throw_name = NULL; - - discard_current_exception(); // uses IObuff if 'verbose' - suppress_errthrow = true; - force_abort = true; - msg_ext_set_kind("emsg"); // kind=emsg for :throw, exceptions. #9993 - - if (messages != NULL) { - do { - next = messages->next; - emsg(messages->msg); - xfree(messages->msg); - xfree(messages->sfile); - xfree(messages); - messages = next; - } while (messages != NULL); - } else if (p != NULL) { - emsg(p); - xfree(p); - } - xfree(SOURCING_NAME); - estack_pop(); + handle_did_throw(); } else if (got_int || (did_emsg && force_abort)) { // On an interrupt or an aborting error not converted to an exception, // disable the conversion of errors to exceptions. (Interrupts are not @@ -902,6 +856,57 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) return retval; } +/// Handle when "did_throw" is set after executing commands. +void handle_did_throw(void) +{ + assert(current_exception != NULL); + char *p = NULL; + msglist_T *messages = NULL; + + // If the uncaught exception is a user exception, report it as an + // error. If it is an error exception, display the saved error + // message now. For an interrupt exception, do nothing; the + // interrupt message is given elsewhere. + switch (current_exception->type) { + case ET_USER: + vim_snprintf((char *)IObuff, IOSIZE, + _("E605: Exception not caught: %s"), + current_exception->value); + p = xstrdup((char *)IObuff); + break; + case ET_ERROR: + messages = current_exception->messages; + current_exception->messages = NULL; + break; + case ET_INTERRUPT: + break; + } + + estack_push(ETYPE_EXCEPT, current_exception->throw_name, current_exception->throw_lnum); + current_exception->throw_name = NULL; + + discard_current_exception(); // uses IObuff if 'verbose' + suppress_errthrow = true; + force_abort = true; + msg_ext_set_kind("emsg"); // kind=emsg for :throw, exceptions. #9993 + + if (messages != NULL) { + do { + msglist_T *next = messages->next; + emsg(messages->msg); + xfree(messages->msg); + xfree(messages->sfile); + xfree(messages); + messages = next; + } while (messages != NULL); + } else if (p != NULL) { + emsg(p); + xfree(p); + } + xfree(SOURCING_NAME); + estack_pop(); +} + /// Obtain a line when inside a ":while" or ":for" loop. static char *get_loop_line(int c, void *cookie, int indent, bool do_concat) { @@ -4621,7 +4626,7 @@ static void ex_pclose(exarg_T *eap) void ex_win_close(int forceit, win_T *win, tabpage_T *tp) { // Never close the autocommand window. - if (win == aucmd_win) { + if (is_aucmd_win(win)) { emsg(_(e_autocmd_close)); return; } diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 2bff0fa2f9..eaaed8b25c 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -5003,7 +5003,7 @@ void buf_reload(buf_T *buf, int orig_mode, bool reload_options) aco_save_T aco; int flags = READ_NEW; - // set curwin/curbuf for "buf" and save some things + // Set curwin/curbuf for "buf" and save some things. aucmd_prepbuf(&aco, buf); // Unless reload_options is set, we only want to read the text from the diff --git a/src/nvim/generators/gen_ex_cmds.lua b/src/nvim/generators/gen_ex_cmds.lua index ac60b9f8e9..3a022d45c8 100644 --- a/src/nvim/generators/gen_ex_cmds.lua +++ b/src/nvim/generators/gen_ex_cmds.lua @@ -66,6 +66,7 @@ defsfile:write(string.format([[ #include "nvim/ex_session.h" #include "nvim/hardcopy.h" #include "nvim/help.h" +#include "nvim/indent.h" #include "nvim/locale.h" #include "nvim/lua/executor.h" #include "nvim/mapping.h" diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 737c92bc8c..8f9fa6673c 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -320,7 +320,7 @@ EXTERN int garbage_collect_at_exit INIT(= false); #define SID_STR (-10) // for sourcing a string with no script item // Script CTX being sourced or was sourced to define the current function. -EXTERN sctx_T current_sctx INIT(= { 0 COMMA 0 COMMA 0 }); +EXTERN sctx_T current_sctx INIT(= { 0, 0, 0 }); // ID of the current channel making a client API call EXTERN uint64_t current_channel_id INIT(= 0); @@ -422,8 +422,18 @@ EXTERN win_T *prevwin INIT(= NULL); // previous window EXTERN win_T *curwin; // currently active window -EXTERN win_T *aucmd_win; // window used in aucmd_prepbuf() -EXTERN int aucmd_win_used INIT(= false); // aucmd_win is being used +typedef struct { + win_T *auc_win; ///< Window used in aucmd_prepbuf(). When not NULL the + ///< window has been allocated. + bool auc_win_used; ///< This auc_win is being used. +} aucmdwin_T; + +/// When executing autocommands for a buffer that is not in any window, a +/// special window is created to handle the side effects. When autocommands +/// nest we may need more than one. +EXTERN kvec_t(aucmdwin_T) aucmd_win_vec INIT(= KV_INITIAL_VALUE); +#define aucmd_win (aucmd_win_vec.items) +#define AUCMD_WIN_COUNT ((int)aucmd_win_vec.size) // The window layout is kept in a tree of frames. topframe points to the top // of the tree. diff --git a/src/nvim/indent.c b/src/nvim/indent.c index 1905128c3c..2777ebd18c 100644 --- a/src/nvim/indent.c +++ b/src/nvim/indent.c @@ -13,9 +13,11 @@ #include "nvim/change.h" #include "nvim/charset.h" #include "nvim/cursor.h" +#include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/eval/typval_defs.h" +#include "nvim/ex_docmd.h" #include "nvim/extmark.h" #include "nvim/gettext.h" #include "nvim/globals.h" @@ -27,6 +29,8 @@ #include "nvim/message.h" #include "nvim/move.h" #include "nvim/option.h" +#include "nvim/optionstr.h" +#include "nvim/os/input.h" #include "nvim/plines.h" #include "nvim/pos.h" #include "nvim/regexp.h" @@ -195,6 +199,7 @@ void tabstop_fromto(colnr_T start_col, colnr_T end_col, long ts_arg, const long long padding = 0; int t; long ts = ts_arg == 0 ? curbuf->b_p_ts : ts_arg; + assert(ts != 0); // suppress clang "Division by zero" if (vts == NULL || vts[0] == 0) { int tabs = 0; @@ -915,6 +920,197 @@ bool may_do_si(void) return curbuf->b_p_si && !curbuf->b_p_cin && *curbuf->b_p_inde == NUL && !p_paste; } +/// Give a "resulting text too long" error and maybe set got_int. +static void emsg_text_too_long(void) +{ + emsg(_(e_resulting_text_too_long)); + // when not inside a try/catch set got_int to break out of any loop + if (trylevel == 0) { + got_int = true; + } +} + +/// ":retab". +void ex_retab(exarg_T *eap) +{ + linenr_T lnum; + bool got_tab = false; + long num_spaces = 0; + long num_tabs; + long len; + long col; + long vcol; + long start_col = 0; // For start of white-space string + long start_vcol = 0; // For start of white-space string + long old_len; + char *ptr; + char *new_line = (char *)1; // init to non-NULL + bool did_undo; // called u_save for current line + long *new_vts_array = NULL; + char *new_ts_str; // string value of tab argument + + int save_list; + linenr_T first_line = 0; // first changed line + linenr_T last_line = 0; // last changed line + + save_list = curwin->w_p_list; + curwin->w_p_list = 0; // don't want list mode here + + new_ts_str = eap->arg; + if (!tabstop_set(eap->arg, &new_vts_array)) { + return; + } + while (ascii_isdigit(*(eap->arg)) || *(eap->arg) == ',') { + (eap->arg)++; + } + + // This ensures that either new_vts_array and new_ts_str are freshly + // allocated, or new_vts_array points to an existing array and new_ts_str + // is null. + if (new_vts_array == NULL) { + new_vts_array = curbuf->b_p_vts_array; + new_ts_str = NULL; + } else { + new_ts_str = xstrnsave(new_ts_str, (size_t)(eap->arg - new_ts_str)); + } + for (lnum = eap->line1; !got_int && lnum <= eap->line2; lnum++) { + ptr = ml_get(lnum); + col = 0; + vcol = 0; + did_undo = false; + for (;;) { + if (ascii_iswhite(ptr[col])) { + if (!got_tab && num_spaces == 0) { + // First consecutive white-space + start_vcol = vcol; + start_col = col; + } + if (ptr[col] == ' ') { + num_spaces++; + } else { + got_tab = true; + } + } else { + if (got_tab || (eap->forceit && num_spaces > 1)) { + // Retabulate this string of white-space + + // len is virtual length of white string + len = num_spaces = vcol - start_vcol; + num_tabs = 0; + if (!curbuf->b_p_et) { + int t, s; + + tabstop_fromto((colnr_T)start_vcol, (colnr_T)vcol, + curbuf->b_p_ts, new_vts_array, &t, &s); + num_tabs = t; + num_spaces = s; + } + if (curbuf->b_p_et || got_tab + || (num_spaces + num_tabs < len)) { + if (did_undo == false) { + did_undo = true; + if (u_save((linenr_T)(lnum - 1), + (linenr_T)(lnum + 1)) == FAIL) { + new_line = NULL; // flag out-of-memory + break; + } + } + + // len is actual number of white characters used + len = num_spaces + num_tabs; + old_len = (long)strlen(ptr); + const long new_len = old_len - col + start_col + len + 1; + if (new_len <= 0 || new_len >= MAXCOL) { + emsg_text_too_long(); + break; + } + new_line = xmalloc((size_t)new_len); + + if (start_col > 0) { + memmove(new_line, ptr, (size_t)start_col); + } + memmove(new_line + start_col + len, + ptr + col, (size_t)(old_len - col + 1)); + ptr = new_line + start_col; + for (col = 0; col < len; col++) { + ptr[col] = (col < num_tabs) ? '\t' : ' '; + } + if (ml_replace(lnum, new_line, false) == OK) { + // "new_line" may have been copied + new_line = curbuf->b_ml.ml_line_ptr; + extmark_splice_cols(curbuf, lnum - 1, 0, (colnr_T)old_len, + (colnr_T)new_len - 1, kExtmarkUndo); + } + if (first_line == 0) { + first_line = lnum; + } + last_line = lnum; + ptr = new_line; + col = start_col + len; + } + } + got_tab = false; + num_spaces = 0; + } + if (ptr[col] == NUL) { + break; + } + vcol += win_chartabsize(curwin, ptr + col, (colnr_T)vcol); + if (vcol >= MAXCOL) { + emsg_text_too_long(); + break; + } + col += utfc_ptr2len(ptr + col); + } + if (new_line == NULL) { // out of memory + break; + } + line_breakcheck(); + } + if (got_int) { + emsg(_(e_interr)); + } + + // If a single value was given then it can be considered equal to + // either the value of 'tabstop' or the value of 'vartabstop'. + if (tabstop_count(curbuf->b_p_vts_array) == 0 + && tabstop_count(new_vts_array) == 1 + && curbuf->b_p_ts == tabstop_first(new_vts_array)) { + // not changed + } else if (tabstop_count(curbuf->b_p_vts_array) > 0 + && tabstop_eq(curbuf->b_p_vts_array, new_vts_array)) { + // not changed + } else { + redraw_curbuf_later(UPD_NOT_VALID); + } + if (first_line != 0) { + changed_lines(first_line, 0, last_line + 1, 0L, true); + } + + curwin->w_p_list = save_list; // restore 'list' + + if (new_ts_str != NULL) { // set the new tabstop + // If 'vartabstop' is in use or if the value given to retab has more + // than one tabstop then update 'vartabstop'. + long *old_vts_ary = curbuf->b_p_vts_array; + + if (tabstop_count(old_vts_ary) > 0 || tabstop_count(new_vts_array) > 1) { + set_string_option_direct("vts", -1, new_ts_str, OPT_FREE | OPT_LOCAL, 0); + curbuf->b_p_vts_array = new_vts_array; + xfree(old_vts_ary); + } else { + // 'vartabstop' wasn't in use and a single value was given to + // retab then update 'tabstop'. + curbuf->b_p_ts = tabstop_first(new_vts_array); + xfree(new_vts_array); + } + xfree(new_ts_str); + } + coladvance(curwin->w_curswant); + + u_clearline(); +} + /// Get indent level from 'indentexpr'. int get_expr_indent(void) { @@ -959,6 +1155,12 @@ int get_expr_indent(void) check_cursor(); State = save_State; + // Reset did_throw, unless 'debug' has "throw" and inside a try/catch. + if (did_throw && (vim_strchr(p_debug, 't') == NULL || trylevel == 0)) { + handle_did_throw(); + did_throw = false; + } + // If there is an error, just keep the current indent. if (indent < 0) { indent = get_indent(); diff --git a/src/nvim/macros.h b/src/nvim/macros.h index 9b7562e86f..242e3c381a 100644 --- a/src/nvim/macros.h +++ b/src/nvim/macros.h @@ -11,7 +11,6 @@ #else # ifndef INIT # define INIT(...) __VA_ARGS__ -# define COMMA , # endif #endif diff --git a/src/nvim/msgpack_rpc/unpacker.c b/src/nvim/msgpack_rpc/unpacker.c index c082bba660..897ea1f768 100644 --- a/src/nvim/msgpack_rpc/unpacker.c +++ b/src/nvim/msgpack_rpc/unpacker.c @@ -186,7 +186,7 @@ void unpacker_init(Unpacker *p) mpack_parser_init(&p->parser, 0); p->parser.data.p = p; mpack_tokbuf_init(&p->reader); - p->unpack_error = (Error)ERROR_INIT; + p->unpack_error = ERROR_INIT; p->arena = (Arena)ARENA_EMPTY; } diff --git a/src/nvim/option.c b/src/nvim/option.c index e67bacce61..6d461d9b9d 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -5170,7 +5170,26 @@ int option_set_callback_func(char *optval, Callback *optcb) // treat everything else as a function name string tv = xcalloc(1, sizeof(*tv)); tv->v_type = VAR_STRING; - tv->vval.v_string = xstrdup(optval); + + // Function name starting with "s:" are supported only in a vimscript + // context. + if (strncmp(optval, "s:", 2) == 0) { + char sid_buf[25]; + + if (!SCRIPT_ID_VALID(current_sctx.sc_sid)) { + emsg(_(e_usingsid)); + return FAIL; + } + // Expand s: prefix into <SNR>nr_<name> + snprintf(sid_buf, sizeof(sid_buf), "<SNR>%" PRId64 "_", + (int64_t)current_sctx.sc_sid); + char *funcname = xmalloc(strlen(sid_buf) + strlen(optval + 2) + 1); + STRCPY(funcname, sid_buf); + STRCAT(funcname, optval + 2); + tv->vval.v_string = funcname; + } else { + tv->vval.v_string = xstrdup(optval); + } } Callback cb; diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index f083b6792b..deac0bb8a1 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -5656,6 +5656,7 @@ static buf_T *load_dummy_buffer(char *fname, char *dirname_start, char *resultin // Restore curwin/curbuf and a few other things. aucmd_restbuf(&aco); + if (newbuf_to_wipe.br_buf != NULL && bufref_valid(&newbuf_to_wipe)) { wipe_buffer(newbuf_to_wipe.br_buf, false); } diff --git a/src/nvim/runtime.h b/src/nvim/runtime.h index d40bb6c1c1..de363020f8 100644 --- a/src/nvim/runtime.h +++ b/src/nvim/runtime.h @@ -79,6 +79,7 @@ typedef struct scriptitem_S { /// Growarray to store info about already sourced scripts. extern garray_T script_items; #define SCRIPT_ITEM(id) (((scriptitem_T *)script_items.ga_data)[(id) - 1]) +#define SCRIPT_ID_VALID(id) ((id) > 0 && (id) <= script_items.ga_len) typedef void (*DoInRuntimepathCB)(char *, void *); diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 206d9ac836..10a0c9c18c 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -387,7 +387,7 @@ void terminal_check_size(Terminal *term) // Check if there is a window that displays the terminal and find the maximum width and height. // Skip the autocommand window which isn't actually displayed. FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp == aucmd_win) { + if (is_aucmd_win(wp)) { continue; } if (wp->w_buffer && wp->w_buffer->terminal == term) { diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index a1b04a4601..15f66fc1ad 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -171,6 +171,7 @@ func RunTheTest(test) endtry endif + au VimLeavePre * call EarlyExit(g:testfunc) if a:test =~ 'Test_nocatch_' " Function handles errors itself. This avoids skipping commands after the " error. @@ -182,10 +183,7 @@ func RunTheTest(test) endif else try - let s:test = a:test - au VimLeavePre * call EarlyExit(s:test) exe 'call ' . a:test - au! VimLeavePre catch /^\cskipped/ call add(s:messages, ' Skipped') call add(s:skipped, 'SKIPPED ' . a:test . ': ' . substitute(v:exception, '^\S*\s\+', '', '')) @@ -193,6 +191,7 @@ func RunTheTest(test) call add(v:errors, 'Caught exception in ' . a:test . ': ' . v:exception . ' @ ' . v:throwpoint) endtry endif + au! VimLeavePre " In case 'insertmode' was set and something went wrong, make sure it is " reset to avoid trouble with anything else. @@ -255,11 +254,11 @@ func AfterTheTest(func_name) if len(v:errors) > 0 if match(s:may_fail_list, '^' .. a:func_name) >= 0 let s:fail_expected += 1 - call add(s:errors_expected, 'Found errors in ' . s:test . ':') + call add(s:errors_expected, 'Found errors in ' . g:testfunc . ':') call extend(s:errors_expected, v:errors) else let s:fail += 1 - call add(s:errors, 'Found errors in ' . s:test . ':') + call add(s:errors, 'Found errors in ' . g:testfunc . ':') call extend(s:errors, v:errors) endif let v:errors = [] @@ -420,12 +419,12 @@ endif let s:may_fail_list = [] if $TEST_MAY_FAIL != '' - " Split the list at commas and add () to make it match s:test. + " Split the list at commas and add () to make it match g:testfunc. let s:may_fail_list = split($TEST_MAY_FAIL, ',')->map({i, v -> v .. '()'}) endif " Execute the tests in alphabetical order. -for s:test in sort(s:tests) +for g:testfunc in sort(s:tests) " Silence, please! set belloff=all let prev_error = '' @@ -435,7 +434,7 @@ for s:test in sort(s:tests) " A test can set g:test_is_flaky to retry running the test. let g:test_is_flaky = 0 - call RunTheTest(s:test) + call RunTheTest(g:testfunc) " Repeat a flaky test. Give up when: " - $TEST_NO_RETRY is not empty @@ -443,10 +442,10 @@ for s:test in sort(s:tests) " - it fails five times (with a different message) if len(v:errors) > 0 \ && $TEST_NO_RETRY == '' - \ && (index(s:flaky_tests, s:test) >= 0 + \ && (index(s:flaky_tests, g:testfunc) >= 0 \ || g:test_is_flaky) while 1 - call add(s:messages, 'Found errors in ' . s:test . ':') + call add(s:messages, 'Found errors in ' . g:testfunc . ':') call extend(s:messages, v:errors) call add(total_errors, 'Run ' . g:run_nr . ':') @@ -469,7 +468,7 @@ for s:test in sort(s:tests) let v:errors = [] let g:run_nr += 1 - call RunTheTest(s:test) + call RunTheTest(g:testfunc) if len(v:errors) == 0 " Test passed on rerun. @@ -478,7 +477,7 @@ for s:test in sort(s:tests) endwhile endif - call AfterTheTest(s:test) + call AfterTheTest(g:testfunc) endfor call FinishTesting() diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index 6fa191d928..199721b15e 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -3483,4 +3483,27 @@ func Test_autocmd_split_dummy() call delete('Xerr') endfunc +" This was crashing because there was only one window to execute autocommands +" in. +func Test_autocmd_nested_setbufvar() + CheckFeature python3 + + set hidden + edit Xaaa + edit Xbbb + call setline(1, 'bar') + enew + au BufWriteCmd Xbbb ++nested call setbufvar('Xaaa', '&ft', 'foo') | bw! Xaaa + au FileType foo call py3eval('vim.current.buffer.options["cindent"]') + wall + + au! BufWriteCmd + au! FileType foo + set nohidden + call delete('Xaaa') + call delete('Xbbb') + %bwipe! +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_escaped_glob.vim b/src/nvim/testdir/test_escaped_glob.vim index 1a4fd8bdab..9f53c76a2c 100644 --- a/src/nvim/testdir/test_escaped_glob.vim +++ b/src/nvim/testdir/test_escaped_glob.vim @@ -1,7 +1,7 @@ " Test whether glob()/globpath() return correct results with certain escaped " characters. -function SetUp() +func SetUp() " consistent sorting of file names set nofileignorecase endfunction diff --git a/src/nvim/testdir/test_getcwd.vim b/src/nvim/testdir/test_getcwd.vim index a75583cd2c..073f8303dc 100644 --- a/src/nvim/testdir/test_getcwd.vim +++ b/src/nvim/testdir/test_getcwd.vim @@ -24,7 +24,7 @@ endfunc " Do all test in a separate window to avoid E211 when we recursively " delete the Xtopdir directory during cleanup -function SetUp() +func SetUp() set visualbell set nocp viminfo+=nviminfo diff --git a/src/nvim/testdir/test_hide.vim b/src/nvim/testdir/test_hide.vim index 41b1a4ad7c..b3ce395523 100644 --- a/src/nvim/testdir/test_hide.vim +++ b/src/nvim/testdir/test_hide.vim @@ -1,6 +1,6 @@ " Tests for :hide command/modifier and 'hidden' option -function SetUp() +func SetUp() let s:save_hidden = &hidden let s:save_bufhidden = &bufhidden let s:save_autowrite = &autowrite diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim index d788841833..1811c82767 100644 --- a/src/nvim/testdir/test_ins_complete.vim +++ b/src/nvim/testdir/test_ins_complete.vim @@ -1300,13 +1300,13 @@ func Test_completefunc_callback() endfunc let lines =<< trim END - #" Test for using a function name + #" Test for using a global function name LET &completefunc = 'g:CompleteFunc2' new - call setline(1, 'zero') + call setline(1, 'global') LET g:CompleteFunc2Args = [] call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') - call assert_equal([[1, ''], [0, 'zero']], g:CompleteFunc2Args) + call assert_equal([[1, ''], [0, 'global']], g:CompleteFunc2Args) bw! #" Test for using a function() @@ -1442,6 +1442,29 @@ func Test_completefunc_callback() END call CheckLegacyAndVim9Success(lines) + " Test for using a script-local function name + func s:CompleteFunc3(findstart, base) + call add(g:CompleteFunc3Args, [a:findstart, a:base]) + return a:findstart ? 0 : [] + endfunc + set completefunc=s:CompleteFunc3 + new + call setline(1, 'script1') + let g:CompleteFunc3Args = [] + call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + call assert_equal([[1, ''], [0, 'script1']], g:CompleteFunc3Args) + bw! + + let &completefunc = 's:CompleteFunc3' + new + call setline(1, 'script2') + let g:CompleteFunc3Args = [] + call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + call assert_equal([[1, ''], [0, 'script2']], g:CompleteFunc3Args) + bw! + delfunc s:CompleteFunc3 + + " invalid return value let &completefunc = {a -> 'abc'} call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') @@ -1476,11 +1499,12 @@ func Test_completefunc_callback() let lines =<< trim END vim9script - # Test for using a def function with completefunc - def Vim9CompleteFunc(val: number, findstart: number, base: string): any - add(g:Vim9completeFuncArgs, [val, findstart, base]) + def Vim9CompleteFunc(callnr: number, findstart: number, base: string): any + add(g:Vim9completeFuncArgs, [callnr, findstart, base]) return findstart ? 0 : [] enddef + + # Test for using a def function with completefunc set completefunc=function('Vim9CompleteFunc',\ [60]) new | only setline(1, 'one') @@ -1488,6 +1512,28 @@ func Test_completefunc_callback() feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') assert_equal([[60, 1, ''], [60, 0, 'one']], g:Vim9completeFuncArgs) bw! + + # Test for using a global function name + &completefunc = g:CompleteFunc2 + new | only + setline(1, 'two') + g:CompleteFunc2Args = [] + feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + assert_equal([[1, ''], [0, 'two']], g:CompleteFunc2Args) + bw! + + # Test for using a script-local function name + def s:LocalCompleteFunc(findstart: number, base: string): any + add(g:LocalCompleteFuncArgs, [findstart, base]) + return findstart ? 0 : [] + enddef + &completefunc = s:LocalCompleteFunc + new | only + setline(1, 'three') + g:LocalCompleteFuncArgs = [] + feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + assert_equal([[1, ''], [0, 'three']], g:LocalCompleteFuncArgs) + bw! END " call CheckScriptSuccess(lines) @@ -1653,6 +1699,29 @@ func Test_omnifunc_callback() END call CheckLegacyAndVim9Success(lines) + " Test for using a script-local function name + func s:OmniFunc3(findstart, base) + call add(g:OmniFunc3Args, [a:findstart, a:base]) + return a:findstart ? 0 : [] + endfunc + set omnifunc=s:OmniFunc3 + new + call setline(1, 'script1') + let g:OmniFunc3Args = [] + call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + call assert_equal([[1, ''], [0, 'script1']], g:OmniFunc3Args) + bw! + + let &omnifunc = 's:OmniFunc3' + new + call setline(1, 'script2') + let g:OmniFunc3Args = [] + call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + call assert_equal([[1, ''], [0, 'script2']], g:OmniFunc3Args) + bw! + delfunc s:OmniFunc3 + + " invalid return value let &omnifunc = {a -> 'abc'} call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') @@ -1687,11 +1756,12 @@ func Test_omnifunc_callback() let lines =<< trim END vim9script - # Test for using a def function with omnifunc - def Vim9omniFunc(val: number, findstart: number, base: string): any - add(g:Vim9omniFunc_Args, [val, findstart, base]) + def Vim9omniFunc(callnr: number, findstart: number, base: string): any + add(g:Vim9omniFunc_Args, [callnr, findstart, base]) return findstart ? 0 : [] enddef + + # Test for using a def function with omnifunc set omnifunc=function('Vim9omniFunc',\ [60]) new | only setline(1, 'one') @@ -1699,6 +1769,28 @@ func Test_omnifunc_callback() feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') assert_equal([[60, 1, ''], [60, 0, 'one']], g:Vim9omniFunc_Args) bw! + + # Test for using a global function name + &omnifunc = g:OmniFunc2 + new | only + setline(1, 'two') + g:OmniFunc2Args = [] + feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + assert_equal([[1, ''], [0, 'two']], g:OmniFunc2Args) + bw! + + # Test for using a script-local function name + def s:LocalOmniFunc(findstart: number, base: string): any + add(g:LocalOmniFuncArgs, [findstart, base]) + return findstart ? 0 : [] + enddef + &omnifunc = s:LocalOmniFunc + new | only + setline(1, 'three') + g:LocalOmniFuncArgs = [] + feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + assert_equal([[1, ''], [0, 'three']], g:LocalOmniFuncArgs) + bw! END " call CheckScriptSuccess(lines) @@ -1887,6 +1979,29 @@ func Test_thesaurusfunc_callback() END call CheckLegacyAndVim9Success(lines) + " Test for using a script-local function name + func s:TsrFunc3(findstart, base) + call add(g:TsrFunc3Args, [a:findstart, a:base]) + return a:findstart ? 0 : [] + endfunc + set tsrfu=s:TsrFunc3 + new + call setline(1, 'script1') + let g:TsrFunc3Args = [] + call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + call assert_equal([[1, ''], [0, 'script1']], g:TsrFunc3Args) + bw! + + let &tsrfu = 's:TsrFunc3' + new + call setline(1, 'script2') + let g:TsrFunc3Args = [] + call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + call assert_equal([[1, ''], [0, 'script2']], g:TsrFunc3Args) + bw! + delfunc s:TsrFunc3 + + " invalid return value let &thesaurusfunc = {a -> 'abc'} call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') @@ -1934,11 +2049,12 @@ func Test_thesaurusfunc_callback() let lines =<< trim END vim9script - # Test for using a def function with thesaurusfunc - def Vim9tsrFunc(val: number, findstart: number, base: string): any - add(g:Vim9tsrFunc_Args, [val, findstart, base]) + def Vim9tsrFunc(callnr: number, findstart: number, base: string): any + add(g:Vim9tsrFunc_Args, [callnr, findstart, base]) return findstart ? 0 : [] enddef + + # Test for using a def function with thesaurusfunc set thesaurusfunc=function('Vim9tsrFunc',\ [60]) new | only setline(1, 'one') @@ -1946,6 +2062,28 @@ func Test_thesaurusfunc_callback() feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') assert_equal([[60, 1, ''], [60, 0, 'one']], g:Vim9tsrFunc_Args) bw! + + # Test for using a global function name + &thesaurusfunc = g:TsrFunc2 + new | only + setline(1, 'two') + g:TsrFunc2Args = [] + feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + assert_equal([[1, ''], [0, 'two']], g:TsrFunc2Args) + bw! + + # Test for using a script-local function name + def s:LocalTsrFunc(findstart: number, base: string): any + add(g:LocalTsrFuncArgs, [findstart, base]) + return findstart ? 0 : [] + enddef + &thesaurusfunc = s:LocalTsrFunc + new | only + setline(1, 'three') + g:LocalTsrFuncArgs = [] + feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + assert_equal([[1, ''], [0, 'three']], g:LocalTsrFuncArgs) + bw! END " call CheckScriptSuccess(lines) diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim index 83709420bf..d9b392992f 100644 --- a/src/nvim/testdir/test_normal.vim +++ b/src/nvim/testdir/test_normal.vim @@ -591,6 +591,21 @@ func Test_opfunc_callback() END call CheckTransLegacySuccess(lines) + " Test for using a script-local function name + func s:OpFunc3(type) + let g:OpFunc3Args = [a:type] + endfunc + set opfunc=s:OpFunc3 + let g:OpFunc3Args = [] + normal! g@l + call assert_equal(['char'], g:OpFunc3Args) + + let &opfunc = 's:OpFunc3' + let g:OpFunc3Args = [] + normal! g@l + call assert_equal(['char'], g:OpFunc3Args) + delfunc s:OpFunc3 + " Using Vim9 lambda expression in legacy context should fail " set opfunc=(a)\ =>\ OpFunc1(24,\ a) let g:OpFunc1Args = [] @@ -614,14 +629,32 @@ func Test_opfunc_callback() let lines =<< trim END vim9script - # Test for using a def function with opfunc def g:Vim9opFunc(val: number, type: string): void g:OpFunc1Args = [val, type] enddef + + # Test for using a def function with opfunc set opfunc=function('g:Vim9opFunc',\ [60]) g:OpFunc1Args = [] normal! g@l assert_equal([60, 'char'], g:OpFunc1Args) + + # Test for using a global function name + &opfunc = g:OpFunc2 + g:OpFunc2Args = [] + normal! g@l + assert_equal(['char'], g:OpFunc2Args) + bw! + + # Test for using a script-local function name + def s:LocalOpFunc(type: string): void + g:LocalOpFuncArgs = [type] + enddef + &opfunc = s:LocalOpFunc + g:LocalOpFuncArgs = [] + normal! g@l + assert_equal(['char'], g:LocalOpFuncArgs) + bw! END " call CheckScriptSuccess(lines) diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index bde6f5a4fc..02cee8a8dd 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -5789,6 +5789,23 @@ func Test_qftextfunc_callback() END call CheckLegacyAndVim9Success(lines) + " Test for using a script-local function name + func s:TqfFunc2(info) + let g:TqfFunc2Args = [a:info.start_idx, a:info.end_idx] + return '' + endfunc + let g:TqfFunc2Args = [] + set quickfixtextfunc=s:TqfFunc2 + cexpr "F10:10:10:L10" + cclose + call assert_equal([1, 1], g:TqfFunc2Args) + + let &quickfixtextfunc = 's:TqfFunc2' + cexpr "F11:11:11:L11" + cclose + call assert_equal([1, 1], g:TqfFunc2Args) + delfunc s:TqfFunc2 + " set 'quickfixtextfunc' to a partial with dict. This used to cause a crash. func SetQftfFunc() let params = {'qftf': function('g:DictQftfFunc')} diff --git a/src/nvim/testdir/test_retab.vim b/src/nvim/testdir/test_retab.vim index 1650a03876..a4f95053c0 100644 --- a/src/nvim/testdir/test_retab.vim +++ b/src/nvim/testdir/test_retab.vim @@ -1,4 +1,7 @@ " Test :retab + +source check.vim + func SetUp() new call setline(1, "\ta \t b c ") @@ -81,19 +84,32 @@ func Test_retab_error() call assert_fails('ret 80000000000000000000', 'E475:') endfunc +func RetabLoop() + while 1 + set ts=4000 + retab 4 + endwhile +endfunc + func Test_retab_endless() - new + " inside try/catch we can catch the error message call setline(1, "\t0\t") let caught = 'no' try - while 1 - set ts=4000 - retab 4 - endwhile - catch /E1240/ - let caught = 'yes' + call RetabLoop() + catch /E1240:/ + let caught = v:exception endtry - bwipe! + call assert_match('E1240:', caught) + + set tabstop& +endfunc + +func Test_nocatch_retab_endless() + " when not inside try/catch an interrupt is generated to get out of loops + call setline(1, "\t0\t") + call assert_fails('call RetabLoop()', ['E1240:', 'Interrupted']) + set tabstop& endfunc diff --git a/src/nvim/testdir/test_tagfunc.vim b/src/nvim/testdir/test_tagfunc.vim index 7a88723bc0..93b9c67b25 100644 --- a/src/nvim/testdir/test_tagfunc.vim +++ b/src/nvim/testdir/test_tagfunc.vim @@ -1,6 +1,8 @@ " Test 'tagfunc' source vim9.vim +source check.vim +source screendump.vim func TagFunc(pat, flag, info) let g:tagfunc_args = [a:pat, a:flag, a:info] @@ -265,38 +267,62 @@ func Test_tagfunc_callback() END call CheckLegacyAndVim9Success(lines) + " Test for using a script-local function name + func s:TagFunc3(pat, flags, info) + let g:TagFunc3Args = [a:pat, a:flags, a:info] + return v:null + endfunc + set tagfunc=s:TagFunc3 + new + let g:TagFunc3Args = [] + call assert_fails('tag a21', 'E433:') + call assert_equal(['a21', '', {}], g:TagFunc3Args) + bw! + let &tagfunc = 's:TagFunc3' + new + let g:TagFunc3Args = [] + call assert_fails('tag a22', 'E433:') + call assert_equal(['a22', '', {}], g:TagFunc3Args) + bw! + delfunc s:TagFunc3 + + " invalid return value let &tagfunc = "{a -> 'abc'}" call assert_fails("echo taglist('a')", "E987:") " Using Vim9 lambda expression in legacy context should fail " set tagfunc=(a,\ b,\ c)\ =>\ g:TagFunc1(21,\ a,\ b,\ c) - new | only + new let g:TagFunc1Args = [] " call assert_fails("tag a17", "E117:") call assert_equal([], g:TagFunc1Args) + bw! " Test for using a script local function set tagfunc=<SID>ScriptLocalTagFunc - new | only + new let g:ScriptLocalFuncArgs = [] call assert_fails('tag a15', 'E433:') call assert_equal(['a15', '', {}], g:ScriptLocalFuncArgs) + bw! " Test for using a script local funcref variable let Fn = function("s:ScriptLocalTagFunc") let &tagfunc= Fn - new | only + new let g:ScriptLocalFuncArgs = [] call assert_fails('tag a16', 'E433:') call assert_equal(['a16', '', {}], g:ScriptLocalFuncArgs) + bw! " Test for using a string(script local funcref variable) let Fn = function("s:ScriptLocalTagFunc") let &tagfunc= string(Fn) - new | only + new let g:ScriptLocalFuncArgs = [] call assert_fails('tag a16', 'E433:') call assert_equal(['a16', '', {}], g:ScriptLocalFuncArgs) + bw! " set 'tagfunc' to a partial with dict. This used to cause a crash. func SetTagFunc() @@ -322,16 +348,37 @@ func Test_tagfunc_callback() let lines =<< trim END vim9script - # Test for using function() - def Vim9tagFunc(val: number, pat: string, flags: string, info: dict<any>): any - g:Vim9tagFuncArgs = [val, pat, flags, info] + def Vim9tagFunc(callnr: number, pat: string, flags: string, info: dict<any>): any + g:Vim9tagFuncArgs = [callnr, pat, flags, info] return null enddef + + # Test for using a def function with completefunc set tagfunc=function('Vim9tagFunc',\ [60]) - new | only + new g:Vim9tagFuncArgs = [] assert_fails('tag a10', 'E433:') assert_equal([60, 'a10', '', {}], g:Vim9tagFuncArgs) + + # Test for using a global function name + &tagfunc = g:TagFunc2 + new + g:TagFunc2Args = [] + assert_fails('tag a11', 'E433:') + assert_equal(['a11', '', {}], g:TagFunc2Args) + bw! + + # Test for using a script-local function name + def s:LocalTagFunc(pat: string, flags: string, info: dict<any> ): any + g:LocalTagFuncArgs = [pat, flags, info] + return null + enddef + &tagfunc = s:LocalTagFunc + new + g:LocalTagFuncArgs = [] + assert_fails('tag a12', 'E433:') + assert_equal(['a12', '', {}], g:LocalTagFuncArgs) + bw! END " call CheckScriptSuccess(lines) diff --git a/src/nvim/testdir/test_winbuf_close.vim b/src/nvim/testdir/test_winbuf_close.vim index 643c1068bd..26b4ba8778 100644 --- a/src/nvim/testdir/test_winbuf_close.vim +++ b/src/nvim/testdir/test_winbuf_close.vim @@ -192,7 +192,23 @@ func Test_tabwin_close() call win_execute(l:wid, 'close') " Should not crash. call assert_true(v:true) - %bwipe! + + " This tests closing a window in another tab, while leaving the tab open + " i.e. two windows in another tab. + tabedit + let w:this_win = 42 + new + let othertab_wid = win_getid() + tabprevious + call win_execute(othertab_wid, 'q') + " drawing the tabline helps check that the other tab's windows and buffers + " are still valid + redrawtabline + " but to be certain, ensure we can focus the other tab too + tabnext + call assert_equal(42, w:this_win) + + bwipe! endfunc " Test when closing a split window (above/below) restores space to the window diff --git a/src/nvim/testing.c b/src/nvim/testing.c index 3617670da9..8c54d9ad32 100644 --- a/src/nvim/testing.c +++ b/src/nvim/testing.c @@ -606,6 +606,7 @@ theend: suppress_errthrow = false; in_assert_fails = false; did_emsg = false; + got_int = false; msg_col = 0; no_wait_return--; need_wait_return = false; diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c index 32bdb7d273..26a17ef747 100644 --- a/src/nvim/ui_compositor.c +++ b/src/nvim/ui_compositor.c @@ -706,15 +706,18 @@ static void ui_comp_grid_resize(UI *ui, Integer grid, Integer width, Integer hei static void ui_comp_event(UI *ui, char *name, Array args) { - Error err = ERROR_INIT; UIEventCallback *event_cb; bool handled = false; - map_foreach_value(&ui_event_cbs, event_cb, { + Error err = ERROR_INIT; Object res = nlua_call_ref(event_cb->cb, name, args, false, &err); if (res.type == kObjectTypeBoolean && res.data.boolean == true) { handled = true; } + if (ERROR_SET(&err)) { + ELOG("Error while executing ui_comp_event callback: %s", err.msg); + } + api_clear_error(&err); }) if (!handled) { diff --git a/src/nvim/window.c b/src/nvim/window.c index 5dd35537fa..1f80f14f26 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -1046,8 +1046,8 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) { win_T *wp = new_wp; - // aucmd_win should always remain floating - if (new_wp != NULL && new_wp == aucmd_win) { + // aucmd_win[] should always remain floating + if (new_wp != NULL && is_aucmd_win(new_wp)) { return FAIL; } @@ -1505,7 +1505,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) // equalize the window sizes. if (do_equal || dir != 0) { win_equal(wp, true, (flags & WSP_VERT) ? (dir == 'v' ? 'b' : 'h') : (dir == 'h' ? 'b' : 'v')); - } else if (*p_spk != 'c' && wp != aucmd_win) { + } else if (*p_spk != 'c' && !is_aucmd_win(wp)) { win_fix_scroll(false); } @@ -1644,11 +1644,20 @@ bool win_valid_floating(const win_T *win) /// @param win window to check bool win_valid(const win_T *win) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { + return tabpage_win_valid(curtab, win); +} + +/// Check if "win" is a pointer to an existing window in tabpage "tp". +/// +/// @param win window to check +static bool tabpage_win_valid(const tabpage_T *tp, const win_T *win) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ if (win == NULL) { return false; } - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + FOR_ALL_WINDOWS_IN_TAB(wp, tp) { if (wp == win) { return true; } @@ -1940,7 +1949,7 @@ static void win_totop(int size, int flags) beep_flush(); return; } - if (curwin == aucmd_win) { + if (is_aucmd_win(curwin)) { return; } if (check_split_disallowed() == FAIL) { @@ -2086,7 +2095,7 @@ void win_equal(win_T *next_curwin, bool current, int dir) win_equal_rec(next_curwin == NULL ? curwin : next_curwin, current, topframe, dir, 0, tabline_height(), Columns, topframe->fr_height); - if (*p_spk != 'c' && next_curwin != aucmd_win) { + if (*p_spk != 'c' && !is_aucmd_win(next_curwin)) { win_fix_scroll(true); } } @@ -2459,7 +2468,7 @@ void close_windows(buf_T *buf, bool keep_curwin) // Start from lastwin to close floating windows with the same buffer first. // When the autocommand window is involved win_close() may need to print an error message. - for (win_T *wp = lastwin; wp != NULL && (lastwin == aucmd_win || !one_window(wp));) { + for (win_T *wp = lastwin; wp != NULL && (is_aucmd_win(lastwin) || !one_window(wp));) { if (wp->w_buffer == buf && (!keep_curwin || wp != curwin) && !(wp->w_closing || wp->w_buffer->b_locked > 0)) { if (win_close(wp, false, false) == FAIL) { @@ -2508,14 +2517,14 @@ bool last_window(win_T *win) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT return one_window(win) && first_tabpage->tp_next == NULL; } -/// Check that current tab page contains no more then one window other than `aucmd_win`. -/// @param counted_float counted even if floating, but not if it is `aucmd_win` +/// Check if current tab page contains no more than one window other than `aucmd_win[]`. +/// @param counted_float counted even if floating, but not if it is `aucmd_win[]` bool one_window(win_T *counted_float) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { bool seen_one = false; FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp != aucmd_win && (!wp->w_floating || wp == counted_float)) { + if (!is_aucmd_win(wp) && (!wp->w_floating || wp == counted_float)) { if (seen_one) { return false; } @@ -2545,7 +2554,7 @@ bool last_nonfloat(win_T *wp) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT /// @return true if all floating windows can be closed static bool can_close_floating_windows(void) { - assert(lastwin != aucmd_win); + assert(!is_aucmd_win(lastwin)); for (win_T *wp = lastwin; wp->w_floating; wp = wp->w_prev) { buf_T *buf = wp->w_buffer; int need_hide = (bufIsChanged(buf) && buf->b_nwindows <= 1); @@ -2663,12 +2672,12 @@ int win_close(win_T *win, bool free_buf, bool force) || (win->w_buffer != NULL && win->w_buffer->b_locked > 0)) { return FAIL; // window is already being closed } - if (win == aucmd_win) { + if (is_aucmd_win(win)) { emsg(_(e_autocmd_close)); return FAIL; } if (lastwin->w_floating && one_window(win)) { - if (lastwin == aucmd_win) { + if (is_aucmd_win(lastwin)) { emsg(_("E814: Cannot close window, only autocmd window would remain")); return FAIL; } @@ -2760,6 +2769,7 @@ int win_close(win_T *win, bool free_buf, bool force) if (win->w_floating) { ui_comp_remove_grid(&win->w_grid_alloc); + assert(first_tabpage != NULL); // suppress clang "Dereference of NULL pointer" if (win->w_float_config.external) { for (tabpage_T *tp = first_tabpage; tp != NULL; tp = tp->tp_next) { if (tp == curtab) { @@ -3048,6 +3058,7 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) static win_T *win_free_mem(win_T *win, int *dirp, tabpage_T *tp) { win_T *wp; + tabpage_T *win_tp = tp == NULL ? curtab : tp; if (!win->w_floating) { // Remove the window and its frame from the tree of frames. @@ -3056,22 +3067,26 @@ static win_T *win_free_mem(win_T *win, int *dirp, tabpage_T *tp) xfree(frp); } else { *dirp = 'h'; // Dummy value. - if (win_valid(prevwin) && prevwin != win) { - wp = prevwin; + if (tp == NULL) { + if (win_valid(prevwin) && prevwin != win) { + wp = prevwin; + } else { + wp = firstwin; + } } else { - wp = firstwin; + if (tabpage_win_valid(tp, tp->tp_prevwin) && tp->tp_prevwin != win) { + wp = tp->tp_prevwin; + } else { + wp = tp->tp_firstwin; + } } } win_free(win, tp); - // When deleting the current window of another tab page select a new - // current window. - if (tp != NULL && win == tp->tp_curwin) { - if (win_valid(tp->tp_prevwin) && tp->tp_prevwin != win) { - tp->tp_curwin = tp->tp_prevwin; - } else { - tp->tp_curwin = tp->tp_firstwin; - } + // When deleting the current window in the tab, select a new current + // window. + if (win == win_tp->tp_curwin) { + win_tp->tp_curwin = wp; } return wp; @@ -3092,17 +3107,23 @@ void win_free_all(void) win_remove(lastwin, NULL); int dummy; (void)win_free_mem(wp, &dummy, NULL); - if (wp == aucmd_win) { - aucmd_win = NULL; + for (int i = 0; i < AUCMD_WIN_COUNT; i++) { + if (aucmd_win[i].auc_win == wp) { + aucmd_win[i].auc_win = NULL; + } } } - if (aucmd_win != NULL) { - int dummy; - (void)win_free_mem(aucmd_win, &dummy, NULL); - aucmd_win = NULL; + for (int i = 0; i < AUCMD_WIN_COUNT; i++) { + if (aucmd_win[i].auc_win != NULL) { + int dummy; + (void)win_free_mem(aucmd_win[i].auc_win, &dummy, NULL); + aucmd_win[i].auc_win = NULL; + } } + kv_destroy(aucmd_win_vec); + while (firstwin != NULL) { int dummy; (void)win_free_mem(firstwin, &dummy, NULL); @@ -3904,18 +3925,18 @@ void win_alloc_first(void) unuse_tabpage(first_tabpage); } -// Init `aucmd_win`. This can only be done after the first window +// Init `aucmd_win[idx]`. This can only be done after the first window // is fully initialized, thus it can't be in win_alloc_first(). -void win_alloc_aucmd_win(void) +void win_alloc_aucmd_win(int idx) { Error err = ERROR_INIT; FloatConfig fconfig = FLOAT_CONFIG_INIT; fconfig.width = Columns; fconfig.height = 5; fconfig.focusable = false; - aucmd_win = win_new_float(NULL, true, fconfig, &err); - aucmd_win->w_buffer->b_nwindows--; - RESET_BINDING(aucmd_win); + aucmd_win[idx].auc_win = win_new_float(NULL, true, fconfig, &err); + aucmd_win[idx].auc_win->w_buffer->b_nwindows--; + RESET_BINDING(aucmd_win[idx].auc_win); } // Allocate the first window or the first window in a new tab page. @@ -5108,7 +5129,7 @@ static void win_free(win_T *wp, tabpage_T *tp) win_free_grid(wp, false); - if (wp != aucmd_win) { + if (win_valid_any_tab(wp)) { win_remove(wp, tp); } if (autocmd_busy) { @@ -7059,7 +7080,7 @@ bool only_one_window(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_buffer != NULL && (!((bt_help(wp->w_buffer) && !bt_help(curbuf)) || wp->w_floating - || wp->w_p_pvw) || wp == curwin) && wp != aucmd_win) { + || wp->w_p_pvw) || wp == curwin) && !is_aucmd_win(wp)) { count++; } } diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index 48431ccfc7..774ec406a8 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -490,6 +490,8 @@ describe('API/win', function() it('closing current (float) window of another tabpage #15313', function() command('tabedit') + command('botright split') + local prevwin = curwin().id eq(2, eval('tabpagenr()')) local win = meths.open_win(0, true, { relative='editor', row=10, col=10, width=50, height=10 @@ -499,7 +501,7 @@ describe('API/win', function() eq(1, eval('tabpagenr()')) meths.win_close(win, false) - eq(1001, meths.tabpage_get_win(tab).id) + eq(prevwin, meths.tabpage_get_win(tab).id) assert_alive() end) end) diff --git a/test/functional/lua/fs_spec.lua b/test/functional/lua/fs_spec.lua index fc228e54bc..88ad6ba24a 100644 --- a/test/functional/lua/fs_spec.lua +++ b/test/functional/lua/fs_spec.lua @@ -12,6 +12,39 @@ local is_os = helpers.is_os local nvim_prog_basename = is_os('win') and 'nvim.exe' or 'nvim' +local test_basename_dirname_eq = { + '~/foo/', + '~/foo', + '~/foo/bar.lua', + 'foo.lua', + ' ', + '', + '.', + '..', + '../', + '~', + '/usr/bin', + '/usr/bin/gcc', + '/', + '/usr/', + '/usr', + 'c:/usr', + 'c:/', + 'c:', + 'c:/users/foo', + 'c:/users/foo/bar.lua', + 'c:/users/foo/bar/../', +} + +local tests_windows_paths = { + 'c:\\usr', + 'c:\\', + 'c:', + 'c:\\users\\foo', + 'c:\\users\\foo\\bar.lua', + 'c:\\users\\foo\\bar\\..\\', +} + before_each(clear) describe('vim.fs', function() @@ -41,15 +74,58 @@ describe('vim.fs', function() local nvim_dir = ... return vim.fs.dirname(nvim_dir) ]], nvim_dir)) + + local function test_paths(paths) + for _, path in ipairs(paths) do + eq( + exec_lua([[ + local path = ... + return vim.fn.fnamemodify(path,':h'):gsub('\\', '/') + ]], path), + exec_lua([[ + local path = ... + return vim.fs.dirname(path) + ]], path), + path + ) + end + end + + test_paths(test_basename_dirname_eq) + if is_os('win') then + test_paths(tests_windows_paths) + end end) end) describe('basename()', function() it('works', function() + eq(nvim_prog_basename, exec_lua([[ local nvim_prog = ... return vim.fs.basename(nvim_prog) ]], nvim_prog)) + + local function test_paths(paths) + for _, path in ipairs(paths) do + eq( + exec_lua([[ + local path = ... + return vim.fn.fnamemodify(path,':t'):gsub('\\', '/') + ]], path), + exec_lua([[ + local path = ... + return vim.fs.basename(path) + ]], path), + path + ) + end + end + + test_paths(test_basename_dirname_eq) + if is_os('win') then + test_paths(tests_windows_paths) + end end) end) diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index 4266d30e73..c74e88f55d 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -2679,6 +2679,46 @@ describe('lua stdlib', function() a.nvim_buf_call(a.nvim_create_buf(false, true), function() vim.cmd "redraw" end) ]] end) + + it('can be nested crazily with hidden buffers', function() + eq(true, exec_lua([[ + local function scratch_buf_call(fn) + local buf = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_option(buf, 'cindent', true) + return vim.api.nvim_buf_call(buf, function() + return vim.api.nvim_get_current_buf() == buf + and vim.api.nvim_buf_get_option(buf, 'cindent') + and fn() + end) and vim.api.nvim_buf_delete(buf, {}) == nil + end + + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return true + end) + end) + end) + end) + end) + end) + end) + end) + end) + end) + end) + end) + ]])) + end) end) describe('vim.api.nvim_win_call', function() diff --git a/test/helpers.lua b/test/helpers.lua index eef47563a0..3fe4322501 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -269,29 +269,10 @@ function module.check_logs() table.concat(runtime_errors, ', '))) end --- Gets (lowercase) OS name from CMake, uname, or manually check if on Windows -do - local platform = nil - function module.uname() - if platform then - return platform - end - - if os.getenv("SYSTEM_NAME") then -- From CMAKE_HOST_SYSTEM_NAME. - platform = string.lower(os.getenv("SYSTEM_NAME")) - return platform - end - - local status, f = pcall(module.popen_r, 'uname', '-s') - if status then - platform = string.lower(f:read("*l")) - f:close() - elseif package.config:sub(1,1) == '\\' then - platform = 'windows' - else - error('unknown platform') - end - return platform +function module.sysname() + local platform = luv.os_uname() + if platform and platform.sysname then + return platform.sysname:lower() end end @@ -303,11 +284,11 @@ function module.is_os(s) or s == 'bsd') then error('unknown platform: '..tostring(s)) end - return ((s == 'win' and module.uname() == 'windows') - or (s == 'mac' and module.uname() == 'darwin') - or (s == 'freebsd' and module.uname() == 'freebsd') - or (s == 'openbsd' and module.uname() == 'openbsd') - or (s == 'bsd' and string.find(module.uname(), 'bsd'))) + return ((s == 'win' and module.sysname():find('windows')) + or (s == 'mac' and module.sysname() == 'darwin') + or (s == 'freebsd' and module.sysname() == 'freebsd') + or (s == 'openbsd' and module.sysname() == 'openbsd') + or (s == 'bsd' and module.sysname():find('bsd'))) end local function tmpdir_get() |