diff options
27 files changed, 239 insertions, 172 deletions
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index bbca6b3339..737a9a249e 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -36,3 +36,16 @@ d90fb1c0bfc1e64c783c385a79e7de87013dadba 9c268263b1792d00b3ffdfd7495af2575862656e 8c74c895b300bcee5fa937a2329d1d4756567b42 40be47e0faef7aa015eb4ba44ceb1ee1a03e97cf +4472c56d54f447040f6e8610b261b7efa0d04eb6 +a68faed02dc8e37b8f10da14dc02e33e6ed93947 +725cbe7d414f609e769081276f2a034e32a4337b +7e3bdc75e44b9139d8afaea4381b53ae78b15746 +4ba12b3dda34472c193c9fa8ffd7d3bd5b6c04d6 +849f104c2789c884428fd45501912c6591a78e12 +38dd53c525054daf83dba27d7d46e90e8b41fa50 +6059784770c4c88fb6fe528b9f7634192fa1164e +ee031eb5256bb83e0d6add2bae6fd943a4186ffe + +# typos +d238b8f6003d34cae7f65ff7585b48a2cd9449fb +4547137aaff32b20172870a549d3a28a3c7adf1c diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index d50e0c1f92..7845ae92a3 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -30,6 +30,8 @@ jobs: run: gh pr edit "$PR_NUMBER" --add-label "$(echo "$PR_TITLE" | sed -E 's|([[:alpha:]]+)(\(.*\))?!?:.*|\1|')" || true - name: "Extract commit scope and add as label" run: gh pr edit "$PR_NUMBER" --add-label "$(echo "$PR_TITLE" | sed -E 's|[[:alpha:]]+\((.+)\)!?:.*|\1|')" || true + - name: "Extract if the PR is a breaking change and add it as label" + run: gh pr edit "$PR_NUMBER" --add-label "$(echo "$PR_TITLE" | sed -E 's|[[:alpha:]]+(\(.*\))?!:.*|breaking-change|')" || true request-reviewer: if: github.event.pull_request.state == 'open' && github.event.pull_request.draft == false @@ -1,4 +1,6 @@ -[](https://neovim.io) +<h1 align="center"> + <img src="https://raw.githubusercontent.com/neovim/neovim.github.io/master/logos/neovim-logo-300x87.png" alt="Neovim"> +</h1> [Documentation](https://neovim.io/doc/general/) | [Chat](https://app.element.io/#/room/#neovim:matrix.org) | diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 02a27e15f8..0a8a3e9a2c 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -3282,7 +3282,7 @@ nvim_del_autocmd({id}) *nvim_del_autocmd()* See also: ~ |nvim_create_autocmd()| -nvim_do_autocmd({event}, {*opts}) *nvim_do_autocmd()* +nvim_exec_autocmd({event}, {*opts}) *nvim_exec_autocmd()* Execute an autocommand |autocmd-execute|. Parameters: ~ diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index ab0b0cd07c..54fb4c14a0 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -500,9 +500,9 @@ CursorMoved After the cursor was moved in Normal or Visual of the cursor line has been changed, e.g. with "x", "rx" or "p". Not triggered when there is typeahead, while - executing a script file, when an operator is - pending, or when moving to another window while - remaining at the same cursor position. + executing a script file, or when an operator + is pending. Always triggered when moving to + another window. For an example see |match-parens|. Note: Cannot be skipped with |:noautocmd|. Careful: This is triggered very often, don't diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 8323ec3e9d..c0a7b73438 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -3670,8 +3670,7 @@ A jump table for the options with a short description can be found at |Q_op|. 0: never 1: only if there are at least two windows 2: always - 3: have a global statusline at the bottom instead of one for - each window + 3: always and ONLY the last window The screen looks nicer with a status line if you have several windows, but it takes another screen line. |status-line| diff --git a/runtime/doc/repeat.txt b/runtime/doc/repeat.txt index 994f97bba0..508565dea4 100644 --- a/runtime/doc/repeat.txt +++ b/runtime/doc/repeat.txt @@ -182,8 +182,7 @@ For writing a Vim script, see chapter 41 of the user manual |usr_41.txt|. *:so* *:source* *load-vim-script* :[range]so[urce] [file] Runs |Ex| commands or Lua code (".lua" files) from - [file], or from the current buffer if no [file] is - given. + [file], or current buffer if no [file]. Triggers the |SourcePre| autocommand. *:source!* :[range]so[urce]! {file} diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 9ca5faf112..59f085977b 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -180,6 +180,7 @@ Commands: |:Man| is available by default, with many improvements such as completion |:sign-define| accepts a `numhl` argument, to highlight the line number |:match| can be invoked before highlight group is defined + |:source| works with Lua and anonymous (no file) scripts Events: |RecordingEnter| diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index fcb1e61764..1ec66d7c55 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -1224,6 +1224,18 @@ function M.open_float(opts, ...) opts = opts or {} bufnr = get_bufnr(bufnr or opts.bufnr) + + do + -- Resolve options with user settings from vim.diagnostic.config + -- Unlike the other decoration functions (e.g. set_virtual_text, set_signs, etc.) `open_float` + -- does not have a dedicated table for configuration options; instead, the options are mixed in + -- with its `opts` table which also includes "keyword" parameters. So we create a dedicated + -- options table that inherits missing keys from the global configuration before resolving. + local t = global_diagnostic_options.float + local float_opts = vim.tbl_extend("keep", opts, type(t) == "table" and t or {}) + opts = get_resolved_options({ float = float_opts }, nil, bufnr).float + end + local scope = ({l = "line", c = "cursor", b = "buffer"})[opts.scope] or opts.scope or "line" local lnum, col if scope == "line" or scope == "cursor" then @@ -1242,17 +1254,6 @@ function M.open_float(opts, ...) error("Invalid value for option 'scope'") end - do - -- Resolve options with user settings from vim.diagnostic.config - -- Unlike the other decoration functions (e.g. set_virtual_text, set_signs, etc.) `open_float` - -- does not have a dedicated table for configuration options; instead, the options are mixed in - -- with its `opts` table which also includes "keyword" parameters. So we create a dedicated - -- options table that inherits missing keys from the global configuration before resolving. - local t = global_diagnostic_options.float - local float_opts = vim.tbl_extend("keep", opts, type(t) == "table" and t or {}) - opts = get_resolved_options({ float = float_opts }, nil, bufnr).float - end - local diagnostics = get_diagnostics(bufnr, opts, true) if scope == "line" then diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index 9aaa0418dc..49f6f98ba4 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -695,7 +695,7 @@ void nvim_del_augroup_by_name(String name) /// - modeline (bool) optional: defaults to true. Process the /// modeline after the autocommands |<nomodeline>|. /// @see |:doautocmd| -void nvim_do_autocmd(Object event, Dict(do_autocmd) *opts, Error *err) +void nvim_exec_autocmd(Object event, Dict(exec_autocmd) *opts, Error *err) FUNC_API_SINCE(9) { int au_group = AUGROUP_ALL; diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua index b6264cdfab..6a29b0fb59 100644 --- a/src/nvim/api/keysets.lua +++ b/src/nvim/api/keysets.lua @@ -133,7 +133,7 @@ return { "nested"; "pattern"; }; - do_autocmd = { + exec_autocmd = { "buffer"; "group"; "modeline"; diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 4ca752e747..bf592a626d 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -590,6 +590,10 @@ bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last) // Remove the buffer from the list. if (wipe_buf) { + // Do not wipe out the buffer if it is used in a window. + if (buf->b_nwindows > 0) { + return false; + } if (buf->b_sfname != buf->b_ffname) { XFREE_CLEAR(buf->b_sfname); } else { diff --git a/src/nvim/diff.c b/src/nvim/diff.c index 1b8a9f41e9..a6bbe40999 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -1536,7 +1536,7 @@ static void diff_read(int idx_orig, int idx_new, diffio_T *dio) long off; int i; int notset = true; // block "*dp" not set yet - diffhunk_T *hunk; + diffhunk_T *hunk = NULL; // init to avoid gcc warning enum { DIFF_ED, DIFF_UNIFIED, diff --git a/src/nvim/eval.c b/src/nvim/eval.c index fbbc543893..6c72c2866e 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -9217,6 +9217,8 @@ dictitem_T *find_var_in_ht(hashtab_T *const ht, int htname, const char *const va /// Finds the dict (g:, l:, s:, …) and hashtable used for a variable. /// +/// Assigns SID if s: scope is accessed from Lua or anonymous Vimscript. #15994 +/// /// @param[in] name Variable name, possibly with scope prefix. /// @param[in] name_len Variable name length. /// @param[out] varname Will be set to the start of the name without scope @@ -9304,6 +9306,7 @@ static hashtab_T *find_var_ht_dict(const char *name, const size_t name_len, cons } } if (current_sctx.sc_sid == SID_STR || current_sctx.sc_sid == SID_LUA) { + // Create SID if s: scope is accessed from Lua or anon Vimscript. #15994 new_script_item(NULL, ¤t_sctx.sc_sid); } *d = &SCRIPT_SV(current_sctx.sc_sid)->sv_dict; diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 0dbc9d6b14..20325509c4 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -6839,7 +6839,7 @@ void tabpage_close_other(tabpage_T *tp, int forceit) // Autocommands may delete the tab page under our fingers and we may // fail to close a window with a modified buffer. - if (!valid_tabpage(tp) || tp->tp_firstwin == wp) { + if (!valid_tabpage(tp) || tp->tp_lastwin == wp) { break; } } diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index d4005e41e8..76c69ad10b 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -2611,4 +2611,21 @@ func Test_autocmd_closing_cmdwin() only endfunc +func Test_bufwipeout_changes_window() + " This should not crash, but we don't have any expectations about what + " happens, changing window in BufWipeout has unpredictable results. + tabedit + let g:window_id = win_getid() + topleft new + setlocal bufhidden=wipe + autocmd BufWipeout <buffer> call win_gotoid(g:window_id) + tabprevious + +tabclose + + unlet g:window_id + au! BufWipeout + %bwipe! +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/window.c b/src/nvim/window.c index fa71a08b94..6bd2ef3ef6 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -971,7 +971,6 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) int before; int minheight; int wmh1; - int hsep_height; bool did_set_fraction = false; // aucmd_win should always remain floating @@ -1084,7 +1083,6 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) } } } else { - hsep_height = STATUS_HEIGHT; layout = FR_COL; /* @@ -1093,7 +1091,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) */ // Current window requires at least 1 space. wmh1 = p_wmh == 0 ? 1 : p_wmh; - needed = wmh1 + hsep_height; + needed = wmh1 + STATUS_HEIGHT; if (flags & WSP_ROOM) { needed += p_wh - wmh1; } @@ -1135,15 +1133,15 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) new_size = oldwin_height / 2; } - if (new_size > available - minheight - hsep_height) { - new_size = available - minheight - hsep_height; + if (new_size > available - minheight - STATUS_HEIGHT) { + new_size = available - minheight - STATUS_HEIGHT; } if (new_size < wmh1) { new_size = wmh1; } // if it doesn't fit in the current window, need win_equal() - if (oldwin_height - new_size - hsep_height < p_wmh) { + if (oldwin_height - new_size - STATUS_HEIGHT < p_wmh) { do_equal = true; } @@ -1156,7 +1154,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) set_fraction(oldwin); did_set_fraction = true; - win_setheight_win(oldwin->w_height + new_size + hsep_height, + win_setheight_win(oldwin->w_height + new_size + STATUS_HEIGHT, oldwin); oldwin_height = oldwin->w_height; if (need_status) { @@ -1173,7 +1171,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) while (frp != NULL) { if (frp->fr_win != oldwin && frp->fr_win != NULL && (frp->fr_win->w_height > new_size - || frp->fr_win->w_height > oldwin_height - new_size - hsep_height)) { + || frp->fr_win->w_height > oldwin_height - new_size - STATUS_HEIGHT)) { do_equal = true; break; } @@ -2021,7 +2019,6 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int int room = 0; int new_size; int has_next_curwin = 0; - int hsep_height; bool hnc; if (topfr->fr_layout == FR_LEAF) { @@ -2167,7 +2164,6 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int totwincount -= wincount; } } else { // topfr->fr_layout == FR_COL - hsep_height = STATUS_HEIGHT; topfr->fr_width = width; topfr->fr_height = height; @@ -2182,7 +2178,7 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int } else { extra_sep = 0; } - totwincount = (n + extra_sep) / (p_wmh + hsep_height); + totwincount = (n + extra_sep) / (p_wmh + STATUS_HEIGHT); has_next_curwin = frame_has_win(topfr, next_curwin); /* @@ -2217,7 +2213,7 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int } else { // These windows don't use up room. totwincount -= (n + (fr->fr_next == NULL - ? extra_sep : 0)) / (p_wmh + hsep_height); + ? extra_sep : 0)) / (p_wmh + STATUS_HEIGHT); } room -= new_size - n; if (room < 0) { @@ -2263,7 +2259,7 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int // Compute the maximum number of windows vert. in "fr". n = frame_minheight(fr, NOWIN); wincount = (n + (fr->fr_next == NULL ? extra_sep : 0)) - / (p_wmh + hsep_height); + / (p_wmh + STATUS_HEIGHT); m = frame_minheight(fr, next_curwin); if (has_next_curwin) { hnc = frame_has_win(fr, next_curwin); @@ -2350,6 +2346,30 @@ void entering_window(win_T *const win) } } +void win_init_empty(win_T *wp) +{ + redraw_later(wp, NOT_VALID); + wp->w_lines_valid = 0; + wp->w_cursor.lnum = 1; + wp->w_curswant = wp->w_cursor.col = 0; + wp->w_cursor.coladd = 0; + wp->w_pcmark.lnum = 1; // pcmark not cleared but set to line 1 + wp->w_pcmark.col = 0; + wp->w_prev_pcmark.lnum = 0; + wp->w_prev_pcmark.col = 0; + wp->w_topline = 1; + wp->w_topfill = 0; + wp->w_botline = 2; + wp->w_s = &wp->w_buffer->b_s; +} + +/// Init the current window "curwin". +/// Called when a new file is being edited. +void curwin_init(void) +{ + win_init_empty(curwin); +} + /// Closes all windows for buffer `buf` unless there is only one non-floating window. /// /// @param keep_curwin don't close `curwin` @@ -2867,6 +2887,13 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) for (ptp = first_tabpage; ptp != NULL && ptp != tp; ptp = ptp->tp_next) { } if (ptp == NULL || tp == curtab) { + // If the buffer was removed from the window we have to give it any + // buffer. + if (win_valid_any_tab(win) && win->w_buffer == NULL) { + win->w_buffer = firstbuf; + firstbuf->b_nwindows++; + win_init_empty(win); + } return; } @@ -3793,33 +3820,6 @@ void close_others(int message, int forceit) } } - -/* - * Init the current window "curwin". - * Called when a new file is being edited. - */ -void curwin_init(void) -{ - win_init_empty(curwin); -} - -void win_init_empty(win_T *wp) -{ - redraw_later(wp, NOT_VALID); - wp->w_lines_valid = 0; - wp->w_cursor.lnum = 1; - wp->w_curswant = wp->w_cursor.col = 0; - wp->w_cursor.coladd = 0; - wp->w_pcmark.lnum = 1; // pcmark not cleared but set to line 1 - wp->w_pcmark.col = 0; - wp->w_prev_pcmark.lnum = 0; - wp->w_prev_pcmark.col = 0; - wp->w_topline = 1; - wp->w_topfill = 0; - wp->w_botline = 2; - wp->w_s = &wp->w_buffer->b_s; -} - /* * Allocate the first window and put an empty buffer in it. * Called from main(). @@ -6510,38 +6510,32 @@ void last_status(bool morewin) } // Look for resizable frames and take lines from them to make room for the statusline -static void resize_frame_for_status(frame_T *fr, int resize_amount) +static void resize_frame_for_status(frame_T *fr) { // Find a frame to take a line from. frame_T *fp = fr; win_T *wp = fr->fr_win; - int n; - while (resize_amount > 0) { - while (fp->fr_height <= frame_minheight(fp, NULL)) { - if (fp == topframe) { - emsg(_(e_noroom)); - return; - } - // In a column of frames: go to frame above. If already at - // the top or in a row of frames: go to parent. - if (fp->fr_parent->fr_layout == FR_COL && fp->fr_prev != NULL) { - fp = fp->fr_prev; - } else { - fp = fp->fr_parent; - } + while (fp->fr_height <= frame_minheight(fp, NULL)) { + if (fp == topframe) { + emsg(_(e_noroom)); + return; } - n = MIN(fp->fr_height - frame_minheight(fp, NULL), resize_amount); - resize_amount -= n; - - if (fp != fr) { - frame_new_height(fp, fp->fr_height - n, false, false); - frame_fix_height(wp); - (void)win_comp_pos(); + // In a column of frames: go to frame above. If already at + // the top or in a row of frames: go to parent. + if (fp->fr_parent->fr_layout == FR_COL && fp->fr_prev != NULL) { + fp = fp->fr_prev; } else { - win_new_height(wp, wp->w_height - n); + fp = fp->fr_parent; } } + if (fp != fr) { + frame_new_height(fp, fp->fr_height - 1, false, false); + frame_fix_height(wp); + (void)win_comp_pos(); + } else { + win_new_height(wp, wp->w_height - 1); + } } static void last_status_rec(frame_T *fr, bool statusline, bool is_stl_global) @@ -6562,15 +6556,12 @@ static void last_status_rec(frame_T *fr, bool statusline, bool is_stl_global) } else if (wp->w_status_height == 0 && !is_stl_global && statusline) { // Add statusline to window if needed wp->w_status_height = STATUS_HEIGHT; - resize_frame_for_status(fr, STATUS_HEIGHT); + resize_frame_for_status(fr); comp_col(); } } else if (wp->w_status_height != 0 && is_stl_global) { // If statusline is global and the window has a statusline, replace it with a horizontal // separator - if (STATUS_HEIGHT - 1 != 0) { - win_new_height(wp, wp->w_height + STATUS_HEIGHT - 1); - } wp->w_status_height = 0; wp->w_hsep_height = 1; comp_col(); @@ -6578,7 +6569,6 @@ static void last_status_rec(frame_T *fr, bool statusline, bool is_stl_global) // If statusline isn't global and the window doesn't have a statusline, re-add it wp->w_status_height = STATUS_HEIGHT; wp->w_hsep_height = 0; - resize_frame_for_status(fr, STATUS_HEIGHT - 1); comp_col(); } redraw_all_later(SOME_VALID); diff --git a/test/functional/api/autocmd_spec.lua b/test/functional/api/autocmd_spec.lua index a58ca00a75..3b14ae9bf7 100644 --- a/test/functional/api/autocmd_spec.lua +++ b/test/functional/api/autocmd_spec.lua @@ -168,7 +168,7 @@ describe('autocmd api', function() }) ]] - meths.do_autocmd("User", {pattern = "Test"}) + meths.exec_autocmd("User", {pattern = "Test"}) eq({{ buflocal = false, command = 'A test autocommand', @@ -179,7 +179,7 @@ describe('autocmd api', function() pattern = 'Test', }}, meths.get_autocmds({event = "User", pattern = "Test"})) meths.set_var("some_condition", true) - meths.do_autocmd("User", {pattern = "Test"}) + meths.exec_autocmd("User", {pattern = "Test"}) eq({}, meths.get_autocmds({event = "User", pattern = "Test"})) end) end) @@ -490,7 +490,7 @@ describe('autocmd api', function() end) end) - describe('nvim_do_autocmd', function() + describe('nvim_exec_autocmd', function() it("can trigger builtin autocmds", function() meths.set_var("autocmd_executed", false) @@ -500,7 +500,7 @@ describe('autocmd api', function() }) eq(false, meths.get_var("autocmd_executed")) - meths.do_autocmd("BufReadPost", {}) + meths.exec_autocmd("BufReadPost", {}) eq(true, meths.get_var("autocmd_executed")) end) @@ -514,10 +514,10 @@ describe('autocmd api', function() }) -- Doesn't execute for other non-matching events - meths.do_autocmd("CursorHold", { buffer = 1 }) + meths.exec_autocmd("CursorHold", { buffer = 1 }) eq(-1, meths.get_var("buffer_executed")) - meths.do_autocmd("BufLeave", { buffer = 1 }) + meths.exec_autocmd("BufLeave", { buffer = 1 }) eq(1, meths.get_var("buffer_executed")) end) @@ -531,7 +531,7 @@ describe('autocmd api', function() }) -- Doesn't execute for other non-matching events - meths.do_autocmd("CursorHold", { buffer = 1 }) + meths.exec_autocmd("CursorHold", { buffer = 1 }) eq('none', meths.get_var("filename_executed")) meths.command('edit __init__.py') @@ -539,7 +539,7 @@ describe('autocmd api', function() end) it('cannot pass buf and fname', function() - local ok = pcall(meths.do_autocmd, "BufReadPre", { pattern = "literally_cannot_error.rs", buffer = 1 }) + local ok = pcall(meths.exec_autocmd, "BufReadPre", { pattern = "literally_cannot_error.rs", buffer = 1 }) eq(false, ok) end) @@ -557,16 +557,16 @@ describe('autocmd api', function() }) -- Doesn't execute for other non-matching events - meths.do_autocmd("CursorHoldI", { buffer = 1 }) + meths.exec_autocmd("CursorHoldI", { buffer = 1 }) eq('none', meths.get_var("filename_executed")) - meths.do_autocmd("CursorHoldI", { buffer = tonumber(meths.get_current_buf()) }) + meths.exec_autocmd("CursorHoldI", { buffer = tonumber(meths.get_current_buf()) }) eq('__init__.py', meths.get_var("filename_executed")) -- Reset filename meths.set_var("filename_executed", 'none') - meths.do_autocmd("CursorHoldI", { pattern = '__init__.py' }) + meths.exec_autocmd("CursorHoldI", { pattern = '__init__.py' }) eq('__init__.py', meths.get_var("filename_executed")) end) @@ -578,9 +578,9 @@ describe('autocmd api', function() command = 'let g:matched = "matched"' }) - meths.do_autocmd("User", { pattern = "OtherCommand" }) + meths.exec_autocmd("User", { pattern = "OtherCommand" }) eq('none', meths.get_var('matched')) - meths.do_autocmd("User", { pattern = "TestCommand" }) + meths.exec_autocmd("User", { pattern = "TestCommand" }) eq('matched', meths.get_var('matched')) end) @@ -594,7 +594,7 @@ describe('autocmd api', function() }) eq(false, meths.get_var("group_executed")) - meths.do_autocmd("FileType", { group = auid }) + meths.exec_autocmd("FileType", { group = auid }) eq(true, meths.get_var("group_executed")) end) @@ -609,7 +609,7 @@ describe('autocmd api', function() }) eq(false, meths.get_var("group_executed")) - meths.do_autocmd("FileType", { group = auname }) + meths.exec_autocmd("FileType", { group = auname }) eq(true, meths.get_var("group_executed")) end) end) diff --git a/test/functional/api/command_spec.lua b/test/functional/api/command_spec.lua index b80004f67c..94176fc2ce 100644 --- a/test/functional/api/command_spec.lua +++ b/test/functional/api/command_spec.lua @@ -65,6 +65,7 @@ describe('nvim_get_commands', function() local cmd3 = { addr=NIL, bang=false, bar=true, complete=NIL, complete_arg=NIL, count=NIL, definition='call \128\253R3_ohyeah()', name='Cmd3', nargs='0', range=NIL, register=false, keepscript=false, script_id=3, } local cmd4 = { addr=NIL, bang=false, bar=false, complete=NIL, complete_arg=NIL, count=NIL, definition='call \128\253R4_just_great()', name='Cmd4', nargs='0', range=NIL, register=true, keepscript=false, script_id=4, } source([[ + let s:foo = 1 command -complete=custom,ListUsers -nargs=+ Finger !finger <args> ]]) eq({Finger=cmd1}, meths.get_commands({builtin=false})) @@ -72,12 +73,18 @@ describe('nvim_get_commands', function() eq({Finger=cmd1, TestCmd=cmd0}, meths.get_commands({builtin=false})) source([[ + function! s:foo() abort + endfunction command -bang -nargs=* Cmd2 call <SID>foo(<q-args>) ]]) source([[ + function! s:ohyeah() abort + endfunction command -bar -nargs=0 Cmd3 call <SID>ohyeah() ]]) source([[ + function! s:just_great() abort + endfunction command -register Cmd4 call <SID>just_great() ]]) -- TODO(justinmk): Order is stable but undefined. Sort before return? diff --git a/test/functional/editor/tabpage_spec.lua b/test/functional/editor/tabpage_spec.lua index d1d6854b07..2494daf99b 100644 --- a/test/functional/editor/tabpage_spec.lua +++ b/test/functional/editor/tabpage_spec.lua @@ -3,8 +3,10 @@ local helpers = require('test.functional.helpers')(after_each) local clear = helpers.clear local command = helpers.command local eq = helpers.eq +local neq = helpers.neq local feed = helpers.feed local eval = helpers.eval +local exec = helpers.exec describe('tabpage', function() before_each(clear) @@ -34,5 +36,20 @@ describe('tabpage', function() eq(3, eval('tabpagenr()')) end) + + it('does not crash or loop 999 times if BufWipeout autocommand switches window #17868', function() + exec([[ + tabedit + let s:window_id = win_getid() + botright new + setlocal bufhidden=wipe + let g:win_closed = 0 + autocmd WinClosed * let g:win_closed += 1 + autocmd BufWipeout <buffer> call win_gotoid(s:window_id) + tabprevious + +tabclose + ]]) + neq(999, eval('g:win_closed')) + end) end) diff --git a/test/functional/ex_cmds/cmd_map_spec.lua b/test/functional/ex_cmds/cmd_map_spec.lua index 0b2190bbcf..64cf53dfa9 100644 --- a/test/functional/ex_cmds/cmd_map_spec.lua +++ b/test/functional/ex_cmds/cmd_map_spec.lua @@ -1,21 +1,23 @@ local helpers = require('test.functional.helpers')(after_each) local clear = helpers.clear -local feed_command = helpers.feed_command local feed = helpers.feed local eq = helpers.eq local expect = helpers.expect local eval = helpers.eval local funcs = helpers.funcs local insert = helpers.insert +local write_file = helpers.write_file local exc_exec = helpers.exc_exec -local source = helpers.source +local command = helpers.command local Screen = require('test.functional.ui.screen') describe('mappings with <Cmd>', function() local screen + local tmpfile = 'X_ex_cmds_cmd_map' + local function cmdmap(lhs, rhs) - feed_command('noremap '..lhs..' <Cmd>'..rhs..'<cr>') - feed_command('noremap! '..lhs..' <Cmd>'..rhs..'<cr>') + command('noremap '..lhs..' <Cmd>'..rhs..'<cr>') + command('noremap! '..lhs..' <Cmd>'..rhs..'<cr>') end before_each(function() @@ -39,7 +41,7 @@ describe('mappings with <Cmd>', function() cmdmap('<F4>', 'normal! ww') cmdmap('<F5>', 'normal! "ay') cmdmap('<F6>', 'throw "very error"') - feed_command([[ + command([[ function! TextObj() if mode() !=# "v" normal! v @@ -55,11 +57,15 @@ describe('mappings with <Cmd>', function() feed('gg') cmdmap('<F8>', 'startinsert') cmdmap('<F9>', 'stopinsert') - feed_command("abbr foo <Cmd>let g:y = 17<cr>bar") + command("abbr foo <Cmd>let g:y = 17<cr>bar") + end) + + after_each(function() + os.remove(tmpfile) end) it('can be displayed', function() - feed_command('map <F3>') + command('map <F3>') screen:expect([[ ^some short lines | of test text | @@ -73,8 +79,8 @@ describe('mappings with <Cmd>', function() end) it('handles invalid mappings', function() - feed_command('let x = 0') - feed_command('noremap <F3> <Cmd><Cmd>let x = 1<cr>') + command('let x = 0') + command('noremap <F3> <Cmd><Cmd>let x = 1<cr>') feed('<F3>') screen:expect([[ ^some short lines | @@ -87,7 +93,7 @@ describe('mappings with <Cmd>', function() {2:E5521: <Cmd> mapping must end with <CR> before second <Cmd>} | ]]) - feed_command('noremap <F3> <Cmd><F3>let x = 2<cr>') + command('noremap <F3> <Cmd><F3>let x = 2<cr>') feed('<F3>') screen:expect([[ ^some short lines | @@ -100,7 +106,7 @@ describe('mappings with <Cmd>', function() {2:E5522: <Cmd> mapping must not include <F3> key} | ]]) - feed_command('noremap <F3> <Cmd>let x = 3') + command('noremap <F3> <Cmd>let x = 3') feed('<F3>') screen:expect([[ ^some short lines | @@ -137,7 +143,7 @@ describe('mappings with <Cmd>', function() eq('n', eval('mode(1)')) -- select mode mapping - feed_command('snoremap <F3> <Cmd>let m = mode(1)<cr>') + command('snoremap <F3> <Cmd>let m = mode(1)<cr>') feed('gh<F3>') eq('s', eval('m')) -- didn't leave select mode @@ -184,8 +190,8 @@ describe('mappings with <Cmd>', function() eq('n', eval('mode(1)')) -- terminal mode - feed_command('tnoremap <F3> <Cmd>let m = mode(1)<cr>') - feed_command('split | terminal') + command('tnoremap <F3> <Cmd>let m = mode(1)<cr>') + command('split | terminal') feed('i') eq('t', eval('mode(1)')) feed('<F3>') @@ -264,11 +270,11 @@ describe('mappings with <Cmd>', function() end) it('works in :normal command', function() - feed_command('noremap ,x <Cmd>call append(1, "xx")\\| call append(1, "aa")<cr>') - feed_command('noremap ,f <Cmd>nosuchcommand<cr>') - feed_command('noremap ,e <Cmd>throw "very error"\\| call append(1, "yy")<cr>') - feed_command('noremap ,m <Cmd>echoerr "The message."\\| call append(1, "zz")<cr>') - feed_command('noremap ,w <Cmd>for i in range(5)\\|if i==1\\|echoerr "Err"\\|endif\\|call append(1, i)\\|endfor<cr>') + command('noremap ,x <Cmd>call append(1, "xx")\\| call append(1, "aa")<cr>') + command('noremap ,f <Cmd>nosuchcommand<cr>') + command('noremap ,e <Cmd>throw "very error"\\| call append(1, "yy")<cr>') + command('noremap ,m <Cmd>echoerr "The message."\\| call append(1, "zz")<cr>') + command('noremap ,w <Cmd>for i in range(5)\\|if i==1\\|echoerr "Err"\\|endif\\|call append(1, i)\\|endfor<cr>') feed(":normal ,x<cr>") screen:expect([[ @@ -297,7 +303,7 @@ describe('mappings with <Cmd>', function() :normal ,x | ]]) - feed_command(':%d') + command(':%d') eq('Vim(echoerr):Err', exc_exec("normal ,w")) screen:expect([[ ^ | @@ -310,8 +316,8 @@ describe('mappings with <Cmd>', function() --No lines in buffer-- | ]]) - feed_command(':%d') - feed_command(':normal ,w') + command(':%d') + feed(':normal ,w<cr>') screen:expect([[ ^ | 4 | @@ -401,8 +407,8 @@ describe('mappings with <Cmd>', function() end) it('works in select mode', function() - feed_command('snoremap <F1> <cmd>throw "very error"<cr>') - feed_command('snoremap <F2> <cmd>normal! <c-g>"by<cr>') + command('snoremap <F1> <cmd>throw "very error"<cr>') + command('snoremap <F2> <cmd>normal! <c-g>"by<cr>') -- can extend select mode feed('gh<F4>') screen:expect([[ @@ -830,12 +836,14 @@ describe('mappings with <Cmd>', function() end) it("works with <SID> mappings", function() - source([[ + command('new!') + write_file(tmpfile, [[ map <f2> <Cmd>call <SID>do_it()<Cr> function! s:do_it() let g:x = 10 endfunction ]]) + command('source '..tmpfile) feed('<f2>') eq('', eval('v:errmsg')) eq(10, eval('g:x')) diff --git a/test/functional/ex_cmds/echo_spec.lua b/test/functional/ex_cmds/echo_spec.lua index d320425de1..a6be04138b 100644 --- a/test/functional/ex_cmds/echo_spec.lua +++ b/test/functional/ex_cmds/echo_spec.lua @@ -181,9 +181,9 @@ describe(':echo :echon :echomsg :echoerr', function() end) it('dumps references to script functions', function() - eq('<SNR>2_Test2', eval('String(Test2_f)')) - eq("function('<SNR>2_Test2')", eval('StringMsg(Test2_f)')) - eq("function('<SNR>2_Test2')", eval('StringErr(Test2_f)')) + eq('<SNR>1_Test2', eval('String(Test2_f)')) + eq("function('<SNR>1_Test2')", eval('StringMsg(Test2_f)')) + eq("function('<SNR>1_Test2')", eval('StringErr(Test2_f)')) end) it('dump references to lambdas', function() @@ -205,11 +205,11 @@ describe(':echo :echon :echomsg :echoerr', function() it('dumps automatically created partials', function() assert_same_echo_dump( - "function('<SNR>2_Test2', {'f': function('<SNR>2_Test2')})", + "function('<SNR>1_Test2', {'f': function('<SNR>1_Test2')})", '{"f": Test2_f}.f', true) assert_same_echo_dump( - "function('<SNR>2_Test2', [1], {'f': function('<SNR>2_Test2', [1])})", + "function('<SNR>1_Test2', [1], {'f': function('<SNR>1_Test2', [1])})", '{"f": function(Test2_f, [1])}.f', true) end) @@ -227,7 +227,7 @@ describe(':echo :echon :echomsg :echoerr', function() function() meths.set_var('d', {v=true}) eq(dedent([[ - {'p': function('<SNR>2_Test2', {...@0}), 'f': function('<SNR>2_Test2'), 'v': v:true}]]), + {'p': function('<SNR>1_Test2', {...@0}), 'f': function('<SNR>1_Test2'), 'v': v:true}]]), exec_capture('echo String(extend(extend(g:d, {"f": g:Test2_f}), {"p": g:d.f}))')) end) @@ -264,7 +264,7 @@ describe(':echo :echon :echomsg :echoerr', function() eval('add(l, function("Test1", l))') eval('add(l, function("Test1", d))') eq(dedent([=[ - {'p': function('<SNR>2_Test2', [[[...@3], function('Test1', [[...@3]]), function('Test1', {...@0})], function('Test1', [[[...@5], function('Test1', [[...@5]]), function('Test1', {...@0})]]), function('Test1', {...@0})], {...@0}), 'f': function('<SNR>2_Test2'), 'v': v:true}]=]), + {'p': function('<SNR>1_Test2', [[[...@3], function('Test1', [[...@3]]), function('Test1', {...@0})], function('Test1', [[[...@5], function('Test1', [[...@5]]), function('Test1', {...@0})]]), function('Test1', {...@0})], {...@0}), 'f': function('<SNR>1_Test2'), 'v': v:true}]=]), exec_capture('echo String(extend(extend(g:d, {"f": g:Test2_f}), {"p": function(g:d.f, l)}))')) end) end) diff --git a/test/functional/ex_cmds/script_spec.lua b/test/functional/ex_cmds/script_spec.lua index f249b9ba7c..bf69ada820 100644 --- a/test/functional/ex_cmds/script_spec.lua +++ b/test/functional/ex_cmds/script_spec.lua @@ -2,18 +2,30 @@ local helpers = require('test.functional.helpers')(after_each) local eq = helpers.eq local neq = helpers.neq +local command = helpers.command +local write_file = helpers.write_file local meths = helpers.meths local clear = helpers.clear local dedent = helpers.dedent -local source = helpers.source local exc_exec = helpers.exc_exec local missing_provider = helpers.missing_provider +local tmpfile = 'X_ex_cmds_script' + before_each(clear) +local function source(code) + write_file(tmpfile, code) + command('source '..tmpfile) +end + describe('script_get-based command', function() local garbage = ')}{+*({}]*[;(+}{&[]}{*])(' + after_each(function() + os.remove(tmpfile) + end) + local function test_garbage_exec(cmd, check_neq) describe(cmd, function() it('works correctly when skipping oneline variant', function() diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index 1845786c4b..173fc54af5 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -21,7 +21,6 @@ local map = global_helpers.tbl_map local ok = global_helpers.ok local sleep = global_helpers.sleep local tbl_contains = global_helpers.tbl_contains -local write_file = global_helpers.write_file local fail = global_helpers.fail local module = { @@ -54,7 +53,6 @@ if module.nvim_dir == module.nvim_prog then module.nvim_dir = "." end -local tmpname = global_helpers.tmpname local iswin = global_helpers.iswin local prepend_argv @@ -494,24 +492,9 @@ function module.feed_command(...) end end -local sourced_fnames = {} +-- @deprecated use nvim_exec() function module.source(code) - local fname = tmpname() - write_file(fname, code) - module.command('source '..fname) - -- DO NOT REMOVE FILE HERE. - -- do_source() has a habit of checking whether files are “same” by using inode - -- and device IDs. If you run two source() calls in quick succession there is - -- a good chance that underlying filesystem will reuse the inode, making files - -- appear as “symlinks” to do_source when it checks FileIDs. With current - -- setup linux machines (both QB, travis and mine(ZyX-I) with XFS) do reuse - -- inodes, Mac OS machines (again, both QB and travis) do not. - -- - -- Files appearing as “symlinks” mean that both the first and the second - -- source() calls will use same SID, which may fail some tests which check for - -- exact numbers after `<SNR>` in e.g. function names. - sourced_fnames[#sourced_fnames + 1] = fname - return fname + module.exec(dedent(code)) end function module.has_powershell() @@ -525,7 +508,7 @@ function module.set_shell_powershell() local cmd = set_encoding..'Remove-Item -Force '..table.concat(iswin() and {'alias:cat', 'alias:echo', 'alias:sleep'} or {'alias:echo'}, ',')..';' - module.source([[ + module.exec([[ let &shell = ']]..shell..[[' set shellquote= shellxquote= let &shellpipe = '2>&1 | Out-File -Encoding UTF8 %s; exit $LastExitCode' @@ -887,9 +870,6 @@ module = global_helpers.tbl_extend('error', module, global_helpers) return function(after_each) if after_each then after_each(function() - for _, fname in ipairs(sourced_fnames) do - os.remove(fname) - end check_logs() check_cores('build/bin/nvim') if session then diff --git a/test/functional/legacy/assert_spec.lua b/test/functional/legacy/assert_spec.lua index dd6a9d0bde..4829a0bbe1 100644 --- a/test/functional/legacy/assert_spec.lua +++ b/test/functional/legacy/assert_spec.lua @@ -136,19 +136,19 @@ describe('assert function:', function() end) it('should have file names and passed messages', function() - local tmpname_one = source([[ + source([[ call assert_equal(1, 100, 'equal assertion failed') call assert_false('true', 'true assertion failed') call assert_true('false', 'false assertion failed') ]]) - local tmpname_two = source([[ + source([[ call assert_true('', 'file two') ]]) expected_errors({ - tmpname_one .. " line 1: equal assertion failed: Expected 1 but got 100", - tmpname_one .. " line 2: true assertion failed: Expected False but got 'true'", - tmpname_one .. " line 3: false assertion failed: Expected True but got 'false'", - tmpname_two .. " line 1: file two: Expected True but got ''", + "nvim_exec(): equal assertion failed: Expected 1 but got 100", + "nvim_exec(): true assertion failed: Expected False but got 'true'", + "nvim_exec(): false assertion failed: Expected True but got 'false'", + "nvim_exec(): file two: Expected True but got ''", }) end) end) diff --git a/test/functional/legacy/memory_usage_spec.lua b/test/functional/legacy/memory_usage_spec.lua index 8d25b9d927..eec89aa919 100644 --- a/test/functional/legacy/memory_usage_spec.lua +++ b/test/functional/legacy/memory_usage_spec.lua @@ -10,6 +10,7 @@ local source = helpers.source local poke_eventloop = helpers.poke_eventloop local uname = helpers.uname local load_adjust = helpers.load_adjust +local write_file = helpers.write_file local isCI = helpers.isCI local function isasan() @@ -84,6 +85,12 @@ setmetatable(monitor_memory_usage, end}) describe('memory usage', function() + local tmpfile = 'X_memory_usage' + + after_each(function() + os.remove(tmpfile) + end) + local function check_result(tbl, status, result) if not status then print('') @@ -103,7 +110,7 @@ describe('memory usage', function() it('function capture vargs', function() local pid = eval('getpid()') local before = monitor_memory_usage(pid) - source([[ + write_file(tmpfile, [[ func s:f(...) let x = a:000 endfunc @@ -111,6 +118,8 @@ describe('memory usage', function() call s:f(0) endfor ]]) + -- TODO: check_result fails if command() is used here. Why? #16064 + feed_command('source '..tmpfile) poke_eventloop() local after = monitor_memory_usage(pid) -- Estimate the limit of max usage as 2x initial usage. @@ -136,7 +145,7 @@ describe('memory usage', function() it('function capture lvars', function() local pid = eval('getpid()') local before = monitor_memory_usage(pid) - local fname = source([[ + write_file(tmpfile, [[ if !exists('s:defined_func') func s:f() let x = l: @@ -147,10 +156,12 @@ describe('memory usage', function() call s:f() endfor ]]) + feed_command('source '..tmpfile) poke_eventloop() local after = monitor_memory_usage(pid) for _ = 1, 3 do - feed_command('so '..fname) + -- TODO: check_result fails if command() is used here. Why? #16064 + feed_command('source '..tmpfile) poke_eventloop() end local last = monitor_memory_usage(pid) diff --git a/test/functional/vimscript/lang_spec.lua b/test/functional/vimscript/lang_spec.lua index d5254986ab..90437f2ee1 100644 --- a/test/functional/vimscript/lang_spec.lua +++ b/test/functional/vimscript/lang_spec.lua @@ -11,6 +11,7 @@ describe('vimscript', function() return end source([[ + let s:foo = 1 func! <sid>_dummy_function() echo 1 endfunc |