From a5ade3c63d88e93244c43ff0f0635f4774f890ce Mon Sep 17 00:00:00 2001 From: glepnir Date: Thu, 29 Feb 2024 18:50:40 +0800 Subject: fix(snippet): correct indent with newline Problem: snippet newline use before line indent after expand. Solution: it should level + 1. --- runtime/lua/vim/snippet.lua | 10 +++++++--- test/functional/lua/snippet_spec.lua | 27 +++++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/runtime/lua/vim/snippet.lua b/runtime/lua/vim/snippet.lua index 09b7576d97..a660d6f301 100644 --- a/runtime/lua/vim/snippet.lua +++ b/runtime/lua/vim/snippet.lua @@ -446,14 +446,18 @@ function M.expand(input) base_indent = base_indent .. (snippet_lines[#snippet_lines]:match('(^%s*)%S') or '') --- @type string end + local shiftwidth = vim.fn.shiftwidth() + local curbuf = vim.api.nvim_get_current_buf() + local expandtab = vim.bo[curbuf].expandtab local lines = vim.iter.map(function(i, line) -- Replace tabs by spaces. - if vim.o.expandtab then - line = line:gsub('\t', (' '):rep(vim.fn.shiftwidth())) --- @type string + if expandtab then + line = line:gsub('\t', (' '):rep(shiftwidth)) --- @type string end -- Add the base indentation. if i > 1 then - line = base_indent .. line + line = #line ~= 0 and base_indent .. line + or (expandtab and (' '):rep(shiftwidth) or '\t'):rep(vim.fn.indent('.') / shiftwidth + 1) end return line end, ipairs(text_to_lines(text))) diff --git a/test/functional/lua/snippet_spec.lua b/test/functional/lua/snippet_spec.lua index e981bc6261..d31b8cc7d5 100644 --- a/test/functional/lua/snippet_spec.lua +++ b/test/functional/lua/snippet_spec.lua @@ -5,6 +5,7 @@ local clear = helpers.clear local eq = helpers.eq local exec_lua = helpers.exec_lua local feed = helpers.feed +local api = helpers.api local fn = helpers.fn local matches = helpers.matches local pcall_err = helpers.pcall_err @@ -230,7 +231,7 @@ describe('vim.snippet', function() end) it('updates snippet state when built-in completion menu is visible', function() - test_expand_success({ '$1 = function($2)\n$3\nend' }, { ' = function()', '', 'end' }) + test_expand_success({ '$1 = function($2)\nend' }, { ' = function()', 'end' }) -- Show the completion menu. feed('') -- Make sure no item is selected. @@ -238,6 +239,28 @@ describe('vim.snippet', function() -- Jump forward (the 2nd tabstop). exec_lua('vim.snippet.jump(1)') feed('foo') - eq({ ' = function(foo)', '', 'end' }, buf_lines(0)) + eq({ ' = function(foo)', 'end' }, buf_lines(0)) + end) + + it('correctly indents with newlines', function() + local curbuf = api.nvim_get_current_buf() + test_expand_success( + { 'function($2)\n$3\nend' }, + { 'function()', ' ', 'end' }, + [[ + vim.opt.sw = 2 + vim.opt.expandtab = true + ]] + ) + api.nvim_buf_set_lines(curbuf, 0, -1, false, {}) + test_expand_success( + { 'func main() {\n$1\n}' }, + { 'func main() {', '\t', '}' }, + [[ + vim.opt.sw = 4 + vim.opt.ts = 4 + vim.opt.expandtab = false + ]] + ) end) end) -- cgit From bcb70eeac48040fd6d6bfc20cf7fb6f41374a67c Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Sun, 4 Feb 2024 00:42:36 +0000 Subject: fix(api): win_set_config autocmds crash when moving win to other tabpage Problem: win_enter autocommands can close new_curwin, crashing if it was the last window in its tabpage after removing win, or can close parent, crashing when attempting to split it later. Solution: remove win first, check that parent is valid after win_enter. NOTE: This isn't actually quite right, as this means win is not in the window list or even has a frame when triggering enter autocommands (so it's not considered valid in the tabpage). This is addressed in later commits. --- src/nvim/api/win_config.c | 7 ++++++- test/functional/api/window_spec.lua | 21 +++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 3cc520dc78..bb1117b3fe 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -478,12 +478,17 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) int dir; new_curwin = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp); } + win_remove(win, win_tp == curtab ? NULL : win_tp); // move to neighboring window if we're moving the current window to a new tabpage if (curwin == win && parent != NULL && new_curwin != NULL && win_tp != win_find_tabpage(parent)) { win_enter(new_curwin, true); + if (!win_valid_any_tab(parent)) { + // win_enter autocommands closed the `parent` to split from. + api_set_error(err, kErrorTypeException, "Window to split was closed"); + return; + } } - win_remove(win, win_tp == curtab ? NULL : win_tp); } else { win_remove(win, win_tp == curtab ? NULL : win_tp); ui_comp_remove_grid(&win->w_grid_alloc); diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index 097a546ef2..a812d502eb 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -1663,6 +1663,27 @@ describe('API/win', function() }, }, fn.winlayout()) end) + + it('closing new curwin when moving window to other tabpage works', function() + command('split | tabnew') + local w = api.nvim_get_current_win() + local t = api.nvim_get_current_tabpage() + command('tabfirst | autocmd WinEnter * ++once quit') + api.nvim_win_set_config(0, { win = w, split = 'left' }) + -- New tabpage is now the only one, as WinEnter closed the new curwin in the original. + eq(t, api.nvim_get_current_tabpage()) + eq({ t }, api.nvim_list_tabpages()) + end) + + it('closing split parent when moving window to other tabpage aborts', function() + command('split | tabnew') + local w = api.nvim_get_current_win() + command('tabfirst | autocmd WinEnter * call nvim_win_close(' .. w .. ', 1)') + eq( + 'Window to split was closed', + pcall_err(api.nvim_win_set_config, 0, { win = w, split = 'left' }) + ) + end) end) describe('get_config', function() -- cgit From 233649bc757743f7677b2ae414779192a94aa2ae Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Sun, 4 Feb 2024 01:50:49 +0000 Subject: fix(api): win_set_config fires unnecessary autocmds Problem: win_set_config should have the observable effect of moving an existing window to another place, but instead fires autocommands as if a new window was created and entered (and does not fire autocommands reflecting a "return" to the original window). Solution: do not fire win_enter-related autocommands when splitting the window, but continue to fire them when entering the window that fills the new space when moving a window to a different tabpage, as the new curwin changes. Also, remove "++once" from the WinEnter autocmd in the other test, as omitting it also crashed Nvim before this fix. --- src/nvim/api/win_config.c | 28 ++++++++-------------------- test/functional/api/window_spec.lua | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 21 deletions(-) diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index bb1117b3fe..e53e13e2a3 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -505,7 +505,7 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) win->w_pos_changed = true; } - int flags = win_split_flags(fconfig.split, parent == NULL); + int flags = win_split_flags(fconfig.split, parent == NULL) | WSP_NOENTER; if (parent == NULL) { if (!win_split_ins(0, flags, win, 0)) { @@ -514,24 +514,13 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) return; } } else { - win_execute_T args; - - tabpage_T *tp = win_find_tabpage(parent); - if (!win_execute_before(&args, parent, tp)) { - // TODO(willothy): how should we handle this / what should the message be? - api_set_error(err, kErrorTypeException, "Failed to switch to tabpage %d", tp->handle); - win_execute_after(&args); - return; - } - // This should return the same ptr to `win`, but we check for - // NULL to detect errors. - win_T *res = win_split_ins(0, flags, win, 0); - win_execute_after(&args); - if (!res) { - // TODO(willothy): What should this error message say? - api_set_error(err, kErrorTypeException, "Failed to split window"); - return; - } + switchwin_T switchwin; + // `parent` is valid in its tabpage, so switch_win should not fail. + const int result = switch_win(&switchwin, parent, win_find_tabpage(parent), true); + (void)result; + assert(result == OK); + win_split_ins(0, flags, win, 0); + restore_win(&switchwin, true); } if (HAS_KEY_X(config, width)) { win_setwidth_win(fconfig.width, win); @@ -539,7 +528,6 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) if (HAS_KEY_X(config, height)) { win_setheight_win(fconfig.height, win); } - redraw_later(win, UPD_NOT_VALID); return; } else { win_config_float(win, fconfig); diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index a812d502eb..3914090814 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -1668,7 +1668,7 @@ describe('API/win', function() command('split | tabnew') local w = api.nvim_get_current_win() local t = api.nvim_get_current_tabpage() - command('tabfirst | autocmd WinEnter * ++once quit') + command('tabfirst | autocmd WinEnter * quit') api.nvim_win_set_config(0, { win = w, split = 'left' }) -- New tabpage is now the only one, as WinEnter closed the new curwin in the original. eq(t, api.nvim_get_current_tabpage()) @@ -1684,6 +1684,38 @@ describe('API/win', function() pcall_err(api.nvim_win_set_config, 0, { win = w, split = 'left' }) ) end) + + it('expected autocmds when moving window to other tabpage', function() + local new_curwin = api.nvim_get_current_win() + command('split') + local win = api.nvim_get_current_win() + command('tabnew') + local parent = api.nvim_get_current_win() + exec([[ + tabfirst + let result = [] + autocmd WinEnter * let result += ["Enter", win_getid()] + autocmd WinLeave * let result += ["Leave", win_getid()] + autocmd WinNew * let result += ["New", win_getid()] + ]]) + api.nvim_win_set_config(0, { win = parent, split = 'left' }) + -- Shouldn't see WinNew, as we're not creating any new windows, just moving existing ones. + eq({ 'Leave', win, 'Enter', new_curwin }, eval('result')) + end) + + it('no autocmds when moving window within same tabpage', function() + local parent = api.nvim_get_current_win() + exec([[ + split + let result = [] + autocmd WinEnter * let result += ["Enter", win_getid()] + autocmd WinLeave * let result += ["Leave", win_getid()] + autocmd WinNew * let result += ["New", win_getid()] + ]]) + api.nvim_win_set_config(0, { win = parent, split = 'left' }) + -- Shouldn't see any of those events, as we remain in the same window. + eq({}, eval('result')) + end) end) describe('get_config', function() -- cgit From a873f33993ef84e3f954127038e559e1ac1cac43 Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Sun, 4 Feb 2024 02:59:26 +0000 Subject: fix(api): open_win fire BufWinEnter for other buffer when !enter && !noautocmd Problem: BufWinEnter is not fired when not entering a new window, even when a different buffer is specified and buffer-related autocommands are unblocked (!noautocmd). Solution: fire it in the context of the new window and buffer. Do not do it if the buffer is unchanged, like :{s}buffer. Be wary of autocommands! For example, it's possible for nvim_win_set_config to be used in an autocommand to move a window to a different tabpage (in contrast, things like wincmd T actually create a *new* window, so it may not have been possible before, meaning other parts of Nvim could assume windows can't do this... I'd be especially cautious of logic that restores curwin and curtab without checking if curwin is still valid in curtab, if any such logic exists). Also, bail early from win_set_buf if setting the temp curwin fails; this shouldn't be possible, as the callers check that wp is valid, but in case that's not true, win_set_buf will no longer continue setting a buffer for the wrong window. Note that pum_create_float_preview also uses win_set_buf, but from a glance, doesn't look like it properly checks for autocmds screwing things up (win_enter, nvim_create_buf...). I haven't addressed that here. Also adds some test coverage for nvim_open_win autocommands. Closes #27121. --- src/nvim/api/win_config.c | 39 +++++-- src/nvim/window.c | 6 +- test/functional/api/window_spec.lua | 207 ++++++++++++++++++++++++++++++++++++ 3 files changed, 242 insertions(+), 10 deletions(-) diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index e53e13e2a3..9d63a1997c 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -261,8 +261,8 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Err switchwin_T switchwin; // `parent` is valid in `tp`, so switch_win should not fail. const int result = switch_win(&switchwin, parent, tp, true); - (void)result; assert(result == OK); + (void)result; wp = win_split_ins(0, flags, NULL, 0); restore_win(&switchwin, true); } @@ -276,18 +276,41 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Err api_set_error(err, kErrorTypeException, "Failed to create window"); return 0; } + + // Autocommands may close `wp` or move it to another tabpage, so update and check `tp` after each + // event. In each case, `wp` should already be valid in `tp`, so switch_win should not fail. switchwin_T switchwin; - if (switch_win_noblock(&switchwin, wp, tp, true) == OK) { - apply_autocmds(EVENT_WINNEW, NULL, NULL, false, curbuf); + { + const int result = switch_win_noblock(&switchwin, wp, tp, true); + assert(result == OK); + (void)result; + if (apply_autocmds(EVENT_WINNEW, NULL, NULL, false, curbuf)) { + tp = win_find_tabpage(wp); + } + restore_win_noblock(&switchwin, true); } - restore_win_noblock(&switchwin, true); - if (enter) { + if (tp && enter) { goto_tabpage_win(tp, wp); + tp = win_find_tabpage(wp); } - if (win_valid_any_tab(wp) && buf != wp->w_buffer) { - win_set_buf(wp, buf, !enter || fconfig.noautocmd, err); + if (tp && buf != wp->w_buffer) { + const bool noautocmd = !enter || fconfig.noautocmd; + win_set_buf(wp, buf, noautocmd, err); + if (!noautocmd) { + tp = win_find_tabpage(wp); + } + // win_set_buf autocommands were blocked if we didn't enter, but we still want BufWinEnter. + if (noautocmd && !fconfig.noautocmd && wp->w_buffer == buf) { + const int result = switch_win_noblock(&switchwin, wp, tp, true); + assert(result == OK); + (void)result; + if (apply_autocmds(EVENT_BUFWINENTER, NULL, NULL, false, buf)) { + tp = win_find_tabpage(wp); + } + restore_win_noblock(&switchwin, true); + } } - if (!win_valid_any_tab(wp)) { + if (!tp) { api_set_error(err, kErrorTypeException, "Window was closed immediately"); return 0; } diff --git a/src/nvim/window.c b/src/nvim/window.c index e2c4524eaa..d5a6e347e7 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -718,6 +718,7 @@ void win_set_buf(win_T *win, buf_T *buf, bool noautocmd, Error *err) kErrorTypeException, "Failed to switch to window %d", win->handle); + goto cleanup; } try_start(); @@ -729,10 +730,11 @@ void win_set_buf(win_T *win, buf_T *buf, bool noautocmd, Error *err) buf->handle); } - // If window is not current, state logic will not validate its cursor. - // So do it now. + // If window is not current, state logic will not validate its cursor. So do it now. + // Still needed if do_buffer returns FAIL (e.g: autocmds abort script after buffer was set). validate_cursor(); +cleanup: restore_win_noblock(&switchwin, true); if (noautocmd) { unblock_autocmds(); diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index 3914090814..8966c3b086 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -1364,6 +1364,213 @@ describe('API/win', function() }, }, layout) end) + + local function setup_tabbed_autocmd_test() + local info = {} + info.orig_buf = api.nvim_get_current_buf() + info.other_buf = api.nvim_create_buf(true, true) + info.tab1_curwin = api.nvim_get_current_win() + info.tab1 = api.nvim_get_current_tabpage() + command('tab split | split') + info.tab2_curwin = api.nvim_get_current_win() + info.tab2 = api.nvim_get_current_tabpage() + exec([=[ + tabfirst + let result = [] + autocmd TabEnter * let result += [["TabEnter", nvim_get_current_tabpage()]] + autocmd TabLeave * let result += [["TabLeave", nvim_get_current_tabpage()]] + autocmd WinEnter * let result += [["WinEnter", win_getid()]] + autocmd WinLeave * let result += [["WinLeave", win_getid()]] + autocmd WinNew * let result += [["WinNew", win_getid()]] + autocmd WinClosed * let result += [["WinClosed", str2nr(expand(""))]] + autocmd BufEnter * let result += [["BufEnter", win_getid(), bufnr()]] + autocmd BufLeave * let result += [["BufLeave", win_getid(), bufnr()]] + autocmd BufWinEnter * let result += [["BufWinEnter", win_getid(), bufnr()]] + autocmd BufWinLeave * let result += [["BufWinLeave", win_getid(), bufnr()]] + ]=]) + return info + end + + it('fires expected autocmds when creating splits without entering', function() + local info = setup_tabbed_autocmd_test() + + -- For these, don't want BufWinEnter if visiting the same buffer, like :{s}buffer. + -- Same tabpage, same buffer. + local new_win = api.nvim_open_win(0, false, { split = 'left', win = info.tab1_curwin }) + eq({ + { 'WinNew', new_win }, + }, eval('result')) + eq(info.tab1_curwin, api.nvim_get_current_win()) + + -- Other tabpage, same buffer. + command('let result = []') + new_win = api.nvim_open_win(0, false, { split = 'left', win = info.tab2_curwin }) + eq({ + { 'WinNew', new_win }, + }, eval('result')) + eq(info.tab1_curwin, api.nvim_get_current_win()) + + -- Same tabpage, other buffer. + command('let result = []') + new_win = api.nvim_open_win(info.other_buf, false, { split = 'left', win = info.tab1_curwin }) + eq({ + { 'WinNew', new_win }, + { 'BufWinEnter', new_win, info.other_buf }, + }, eval('result')) + eq(info.tab1_curwin, api.nvim_get_current_win()) + + -- Other tabpage, other buffer. + command('let result = []') + new_win = api.nvim_open_win(info.other_buf, false, { split = 'left', win = info.tab2_curwin }) + eq({ + { 'WinNew', new_win }, + { 'BufWinEnter', new_win, info.other_buf }, + }, eval('result')) + eq(info.tab1_curwin, api.nvim_get_current_win()) + end) + + it('fires expected autocmds when creating and entering splits', function() + local info = setup_tabbed_autocmd_test() + + -- Same tabpage, same buffer. + local new_win = api.nvim_open_win(0, true, { split = 'left', win = info.tab1_curwin }) + eq({ + { 'WinNew', new_win }, + { 'WinLeave', info.tab1_curwin }, + { 'WinEnter', new_win }, + }, eval('result')) + + -- Same tabpage, other buffer. + api.nvim_set_current_win(info.tab1_curwin) + command('let result = []') + new_win = api.nvim_open_win(info.other_buf, true, { split = 'left', win = info.tab1_curwin }) + eq({ + { 'WinNew', new_win }, + { 'WinLeave', info.tab1_curwin }, + { 'WinEnter', new_win }, + { 'BufLeave', new_win, info.orig_buf }, + { 'BufEnter', new_win, info.other_buf }, + { 'BufWinEnter', new_win, info.other_buf }, + }, eval('result')) + + -- For these, the other tabpage's prevwin and curwin will change like we switched from its old + -- curwin to the new window, so the extra events near TabEnter reflect that. + -- Other tabpage, same buffer. + api.nvim_set_current_win(info.tab1_curwin) + command('let result = []') + new_win = api.nvim_open_win(0, true, { split = 'left', win = info.tab2_curwin }) + eq({ + { 'WinNew', new_win }, + { 'WinLeave', info.tab1_curwin }, + { 'TabLeave', info.tab1 }, + + { 'WinEnter', info.tab2_curwin }, + { 'TabEnter', info.tab2 }, + { 'WinLeave', info.tab2_curwin }, + { 'WinEnter', new_win }, + }, eval('result')) + + -- Other tabpage, other buffer. + api.nvim_set_current_win(info.tab2_curwin) + api.nvim_set_current_win(info.tab1_curwin) + command('let result = []') + new_win = api.nvim_open_win(info.other_buf, true, { split = 'left', win = info.tab2_curwin }) + eq({ + { 'WinNew', new_win }, + { 'WinLeave', info.tab1_curwin }, + { 'TabLeave', info.tab1 }, + + { 'WinEnter', info.tab2_curwin }, + { 'TabEnter', info.tab2 }, + { 'WinLeave', info.tab2_curwin }, + { 'WinEnter', new_win }, + + { 'BufLeave', new_win, info.orig_buf }, + { 'BufEnter', new_win, info.other_buf }, + { 'BufWinEnter', new_win, info.other_buf }, + }, eval('result')) + + -- Other tabpage, other buffer; but other tabpage's curwin has a new buffer active. + api.nvim_set_current_win(info.tab2_curwin) + local new_buf = api.nvim_create_buf(true, true) + api.nvim_set_current_buf(new_buf) + api.nvim_set_current_win(info.tab1_curwin) + command('let result = []') + new_win = api.nvim_open_win(info.other_buf, true, { split = 'left', win = info.tab2_curwin }) + eq({ + { 'WinNew', new_win }, + { 'BufLeave', info.tab1_curwin, info.orig_buf }, + { 'WinLeave', info.tab1_curwin }, + { 'TabLeave', info.tab1 }, + + { 'WinEnter', info.tab2_curwin }, + { 'TabEnter', info.tab2 }, + { 'BufEnter', info.tab2_curwin, new_buf }, + { 'WinLeave', info.tab2_curwin }, + { 'WinEnter', new_win }, + { 'BufLeave', new_win, new_buf }, + { 'BufEnter', new_win, info.other_buf }, + { 'BufWinEnter', new_win, info.other_buf }, + }, eval('result')) + end) + + it('OK when new window is moved to other tabpage by autocommands', function() + -- Use nvim_win_set_config in the autocommands, as other methods of moving a window to a + -- different tabpage (e.g: wincmd T) actually creates a new window. + local tab0 = api.nvim_get_current_tabpage() + local tab0_win = api.nvim_get_current_win() + command('tabnew') + local new_buf = api.nvim_create_buf(true, true) + local tab1 = api.nvim_get_current_tabpage() + local tab1_parent = api.nvim_get_current_win() + command( + 'tabfirst | autocmd WinNew * ++once call nvim_win_set_config(0, #{split: "left", win: ' + .. tab1_parent + .. '})' + ) + local new_win = api.nvim_open_win(new_buf, true, { split = 'left' }) + eq(tab1, api.nvim_get_current_tabpage()) + eq(new_win, api.nvim_get_current_win()) + eq(new_buf, api.nvim_get_current_buf()) + + -- nvim_win_set_config called after entering. It doesn't follow a curwin that is moved to a + -- different tabpage, but instead moves to the win filling the space, which is tab0_win. + command( + 'tabfirst | autocmd WinEnter * ++once call nvim_win_set_config(0, #{split: "left", win: ' + .. tab1_parent + .. '})' + ) + new_win = api.nvim_open_win(new_buf, true, { split = 'left' }) + eq(tab0, api.nvim_get_current_tabpage()) + eq(tab0_win, api.nvim_get_current_win()) + eq(tab1, api.nvim_win_get_tabpage(new_win)) + eq(new_buf, api.nvim_win_get_buf(new_win)) + + command( + 'tabfirst | autocmd BufEnter * ++once call nvim_win_set_config(0, #{split: "left", win: ' + .. tab1_parent + .. '})' + ) + new_win = api.nvim_open_win(new_buf, true, { split = 'left' }) + eq(tab0, api.nvim_get_current_tabpage()) + eq(tab0_win, api.nvim_get_current_win()) + eq(tab1, api.nvim_win_get_tabpage(new_win)) + eq(new_buf, api.nvim_win_get_buf(new_win)) + end) + + it('does not fire BufWinEnter if win_set_buf fails', function() + exec([[ + set nohidden modified + autocmd WinNew * ++once only! + let fired = v:false + autocmd BufWinEnter * ++once let fired = v:true + ]]) + eq( + 'Failed to set buffer 2', + pcall_err(api.nvim_open_win, api.nvim_create_buf(true, true), false, { split = 'left' }) + ) + eq(false, eval('fired')) + end) end) describe('set_config', function() -- cgit From e55a502ed413d2bc8954b5227acfb34c8689f979 Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Sun, 11 Feb 2024 18:45:56 +0000 Subject: fix(api): open_win fire Buf* events when !enter && !noautocmd if entered early Problem: if switch_win{_noblock} fails to restore the old curwin after WinNew (e.g: it was closed), wp will become the new curwin, but win_set_buf enter events would still be blocked if !enter && !noautocmd. Solution: fire them, as we've actually entered the new window. Note: there's a problem of switch_win{_noblock} failing to restore the old curwin, leaving us in wp without triggering WinEnter/WinLeave, but this affects all callers of switch_win{_noblock} anyways. (It's also not clear how WinLeave can be called if the old curwin was closed already). --- src/nvim/api/win_config.c | 2 +- test/functional/api/window_spec.lua | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 9d63a1997c..238ec5df1e 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -294,7 +294,7 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Err tp = win_find_tabpage(wp); } if (tp && buf != wp->w_buffer) { - const bool noautocmd = !enter || fconfig.noautocmd; + const bool noautocmd = curwin != wp || fconfig.noautocmd; win_set_buf(wp, buf, noautocmd, err); if (!noautocmd) { tp = win_find_tabpage(wp); diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index 8966c3b086..e0cb66de41 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -1571,6 +1571,16 @@ describe('API/win', function() ) eq(false, eval('fired')) end) + + it('fires Buf* autocommands when `!enter` if window is entered via autocommands', function() + exec([[ + autocmd WinNew * ++once only! + let fired = v:false + autocmd BufEnter * ++once let fired = v:true + ]]) + api.nvim_open_win(api.nvim_create_buf(true, true), false, { split = 'left' }) + eq(true, eval('fired')) + end) end) describe('set_config', function() -- cgit From b1e24f240baeea80dcf4a3d8453fed0230fb88fd Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Sun, 11 Feb 2024 20:15:47 +0000 Subject: fix(api): avoid open_win UAF if target buf deleted by autocmds Problem: WinNew and win_enter autocommands can delete the target buffer to switch to, causing a heap-use-after-free. Solution: store a bufref to the buffer, check it before attempting to switch. --- src/nvim/api/win_config.c | 6 +++++- test/functional/api/window_spec.lua | 8 ++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 238ec5df1e..3959e74af9 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -12,6 +12,7 @@ #include "nvim/ascii_defs.h" #include "nvim/autocmd.h" #include "nvim/autocmd_defs.h" +#include "nvim/buffer.h" #include "nvim/buffer_defs.h" #include "nvim/decoration.h" #include "nvim/decoration_defs.h" @@ -279,6 +280,9 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Err // Autocommands may close `wp` or move it to another tabpage, so update and check `tp` after each // event. In each case, `wp` should already be valid in `tp`, so switch_win should not fail. + // Also, autocommands may free the `buf` to switch to, so store a bufref to check. + bufref_T bufref; + set_bufref(&bufref, buf); switchwin_T switchwin; { const int result = switch_win_noblock(&switchwin, wp, tp, true); @@ -293,7 +297,7 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Err goto_tabpage_win(tp, wp); tp = win_find_tabpage(wp); } - if (tp && buf != wp->w_buffer) { + if (tp && bufref_valid(&bufref) && buf != wp->w_buffer) { const bool noautocmd = curwin != wp || fconfig.noautocmd; win_set_buf(wp, buf, noautocmd, err); if (!noautocmd) { diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index e0cb66de41..2f6a02b5d5 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -1581,6 +1581,14 @@ describe('API/win', function() api.nvim_open_win(api.nvim_create_buf(true, true), false, { split = 'left' }) eq(true, eval('fired')) end) + + it('no heap-use-after-free if target buffer deleted by autocommands', function() + local cur_buf = api.nvim_get_current_buf() + local new_buf = api.nvim_create_buf(true, true) + command('autocmd WinNew * ++once call nvim_buf_delete(' .. new_buf .. ', #{force: 1})') + api.nvim_open_win(new_buf, true, { split = 'left' }) + eq(cur_buf, api.nvim_get_current_buf()) + end) end) describe('set_config', function() -- cgit From 5d58136cccc760f6d95eb45b46f2ad60f06b103b Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Wed, 7 Feb 2024 17:17:44 +0000 Subject: fix(api): make open_win/win_set_config check if splitting allowed Problem: splitting is disallowed in some cases to prevent the window layout changes while a window is closing, but it's not checked for. Solution: check for this, and set the API error message directly. (Also sneak in a change to tui.c that got lost from #27352; it's a char* buf, and the memset is assuming one byte each anyway) --- src/nvim/api/win_config.c | 8 +++++++ src/nvim/tui/tui.c | 2 +- src/nvim/window.c | 36 ++++++++++++++++++++-------- test/functional/api/window_spec.lua | 47 +++++++++++++++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 11 deletions(-) diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 3959e74af9..557c2f37f9 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -246,6 +246,10 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Err } } + if (!check_split_disallowed_err(parent ? parent : curwin, err)) { + return 0; // error already set + } + if (HAS_KEY_X(config, vertical) && !HAS_KEY_X(config, split)) { if (config->vertical) { fconfig.split = p_spr ? kWinSplitRight : kWinSplitLeft; @@ -440,6 +444,10 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) return; } + if (!check_split_disallowed_err(win, err)) { + return; // error already set + } + if (was_split) { win_T *new_curwin = NULL; diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 7fae34d33f..c332c17e43 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -1643,7 +1643,7 @@ static void invalidate(TUIData *tui, int top, int bot, int left, int right) static void ensure_space_buf_size(TUIData *tui, size_t len) { if (len > tui->space_buf_len) { - tui->space_buf = xrealloc(tui->space_buf, len * sizeof *tui->space_buf); + tui->space_buf = xrealloc(tui->space_buf, len); memset(tui->space_buf + tui->space_buf_len, ' ', len - tui->space_buf_len); tui->space_buf_len = len; } diff --git a/src/nvim/window.c b/src/nvim/window.c index d5a6e347e7..81f8304b22 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -905,19 +905,35 @@ void ui_ext_win_viewport(win_T *wp) } } -/// If "split_disallowed" is set give an error and return FAIL. +/// If "split_disallowed" is set or "wp"s buffer is closing, give an error and return FAIL. /// Otherwise return OK. -static int check_split_disallowed(void) +static int check_split_disallowed(const win_T *wp) + FUNC_ATTR_NONNULL_ALL +{ + Error err = ERROR_INIT; + const bool ok = check_split_disallowed_err(wp, &err); + if (ERROR_SET(&err)) { + emsg(_(err.msg)); + api_clear_error(&err); + } + return ok ? OK : FAIL; +} + +/// Like `check_split_disallowed`, but set `err` to the (untranslated) error message on failure and +/// return false. Otherwise return true. +/// @see check_split_disallowed +bool check_split_disallowed_err(const win_T *wp, Error *err) + FUNC_ATTR_NONNULL_ALL { if (split_disallowed > 0) { - emsg(_("E242: Can't split a window while closing another")); - return FAIL; + api_set_error(err, kErrorTypeException, "E242: Can't split a window while closing another"); + return false; } - if (curwin->w_buffer->b_locked_split) { - emsg(_(e_cannot_split_window_when_closing_buffer)); - return FAIL; + if (wp->w_buffer->b_locked_split) { + api_set_error(err, kErrorTypeException, "%s", e_cannot_split_window_when_closing_buffer); + return false; } - return OK; + return true; } // split the current window, implements CTRL-W s and :split @@ -936,7 +952,7 @@ static int check_split_disallowed(void) // return FAIL for failure, OK otherwise int win_split(int size, int flags) { - if (check_split_disallowed() == FAIL) { + if (check_split_disallowed(curwin) == FAIL) { return FAIL; } @@ -1871,7 +1887,7 @@ static void win_totop(int size, int flags) if (is_aucmd_win(curwin)) { return; } - if (check_split_disallowed() == FAIL) { + if (check_split_disallowed(curwin) == FAIL) { return; } diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index 2f6a02b5d5..30235d6b84 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -1589,6 +1589,37 @@ describe('API/win', function() api.nvim_open_win(new_buf, true, { split = 'left' }) eq(cur_buf, api.nvim_get_current_buf()) end) + + it('checks if splitting disallowed', function() + command('split | autocmd WinEnter * ++once call nvim_open_win(0, 0, #{split: "right"})') + matches("E242: Can't split a window while closing another$", pcall_err(command, 'quit')) + + command('only | autocmd BufHidden * ++once call nvim_open_win(0, 0, #{split: "left"})') + matches( + 'E1159: Cannot split a window when closing the buffer$', + pcall_err(command, 'new | quit') + ) + + local w = api.nvim_get_current_win() + command( + 'only | new | autocmd BufHidden * ++once call nvim_open_win(0, 0, #{split: "left", win: ' + .. w + .. '})' + ) + matches( + 'E1159: Cannot split a window when closing the buffer$', + pcall_err(api.nvim_win_close, w, true) + ) + + -- OK when using window to different buffer than `win`s. + w = api.nvim_get_current_win() + command( + 'only | autocmd BufHidden * ++once call nvim_open_win(0, 0, #{split: "left", win: ' + .. w + .. '})' + ) + command('new | quit') + end) end) describe('set_config', function() @@ -1941,6 +1972,22 @@ describe('API/win', function() -- Shouldn't see any of those events, as we remain in the same window. eq({}, eval('result')) end) + + it('checks if splitting disallowed', function() + command('split | autocmd WinEnter * ++once call nvim_win_set_config(0, #{split: "right"})') + matches("E242: Can't split a window while closing another$", pcall_err(command, 'quit')) + + command('autocmd BufHidden * ++once call nvim_win_set_config(0, #{split: "left"})') + matches( + 'E1159: Cannot split a window when closing the buffer$', + pcall_err(command, 'new | quit') + ) + + -- OK when using window to different buffer. + local w = api.nvim_get_current_win() + command('autocmd BufHidden * ++once call nvim_win_set_config(' .. w .. ', #{split: "left"})') + command('new | quit') + end) end) describe('get_config', function() -- cgit From b1577d371a6db43222de9e3a525def82320ebdb1 Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Wed, 7 Feb 2024 21:44:42 +0000 Subject: fix(api): make win_set_config with "win" for splits need "split/vertical" Problem: currently, for splits, nvim_win_set_config accepts win without any of split or vertical set, which has little effect and seems error-prone. Solution: require at least one of split or vertical to also be set for splits. Also, update nvim_win_set_config docs, as it's no longer limited to just floating and external windows. --- runtime/doc/api.txt | 8 ++++---- runtime/lua/vim/_meta/api.lua | 8 ++++---- src/nvim/api/win_config.c | 16 ++++++++++------ test/functional/api/window_spec.lua | 9 +++++++++ 4 files changed, 27 insertions(+), 14 deletions(-) diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 1b00777532..125976342b 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -3291,11 +3291,11 @@ nvim_win_get_config({window}) *nvim_win_get_config()* Map defining the window configuration, see |nvim_open_win()| nvim_win_set_config({window}, {config}) *nvim_win_set_config()* - Configures window layout. Currently only for floating and external windows - (including changing a split window to those layouts). + Configures window layout. Cannot be used to move the last window in a + tabpage to a different one. - When reconfiguring a floating window, absent option keys will not be - changed. `row`/`col` and `relative` must be reconfigured together. + When reconfiguring a window, absent option keys will not be changed. + `row`/`col` and `relative` must be reconfigured together. Parameters: ~ • {window} Window handle, or 0 for current window diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index 94eab72291..4a179d49f3 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -2207,11 +2207,11 @@ function vim.api.nvim_win_remove_ns(window, ns_id) end --- @param buffer integer Buffer handle function vim.api.nvim_win_set_buf(window, buffer) end ---- Configures window layout. Currently only for floating and external windows ---- (including changing a split window to those layouts). +--- Configures window layout. Cannot be used to move the last window in a +--- tabpage to a different one. --- ---- When reconfiguring a floating window, absent option keys will not be ---- changed. `row`/`col` and `relative` must be reconfigured together. +--- When reconfiguring a window, absent option keys will not be changed. +--- `row`/`col` and `relative` must be reconfigured together. --- --- @param window integer Window handle, or 0 for current window --- @param config vim.api.keyset.win_config Map defining the window configuration, see `nvim_open_win()` diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 557c2f37f9..21d6d59b1e 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -361,11 +361,11 @@ static int win_split_flags(WinSplit split, bool toplevel) return flags; } -/// Configures window layout. Currently only for floating and external windows -/// (including changing a split window to those layouts). +/// Configures window layout. Cannot be used to move the last window in a +/// tabpage to a different one. /// -/// When reconfiguring a floating window, absent option keys will not be -/// changed. `row`/`col` and `relative` must be reconfigured together. +/// When reconfiguring a window, absent option keys will not be changed. +/// `row`/`col` and `relative` must be reconfigured together. /// /// @see |nvim_open_win()| /// @@ -1099,11 +1099,15 @@ static bool parse_float_config(Dict(win_config) *config, WinConfig *fconfig, boo fconfig->window = config->win; } } - } else if (has_relative) { - if (HAS_KEY_X(config, win)) { + } else if (HAS_KEY_X(config, win)) { + if (has_relative) { api_set_error(err, kErrorTypeValidation, "'win' key is only valid with relative='win' and relative=''"); return false; + } else if (!is_split) { + api_set_error(err, kErrorTypeValidation, + "non-float with 'win' requires at least 'split' or 'vertical'"); + return false; } } diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index 30235d6b84..246ff50ddc 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -1727,6 +1727,15 @@ describe('API/win', function() config = api.nvim_win_get_config(win) eq('', config.relative) eq('below', config.split) + + eq( + "non-float with 'win' requires at least 'split' or 'vertical'", + pcall_err(api.nvim_win_set_config, 0, { win = 0 }) + ) + eq( + "non-float with 'win' requires at least 'split' or 'vertical'", + pcall_err(api.nvim_win_set_config, 0, { win = 0, relative = '' }) + ) end) it('creates top-level splits', function() -- cgit From a70eae57bd44208a77b5ac29839e8a39ab3c9cd8 Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Sun, 11 Feb 2024 22:53:37 +0000 Subject: fix(api): make open_win block only enter/leave events if !enter && !noautocmd Problem: nvim_open_win blocking all win_set_buf autocommands when !enter && !noautocmd is too aggressive. Solution: temporarily block WinEnter/Leave and BufEnter/Leave events when setting the buffer. Delegate the firing of BufWinEnter back to win_set_buf, which also has the advantage of keeping the timing consistent (e.g: before the epilogue in enter_buffer, which also handles restoring the cursor position if autocommands didn't change it, among other things). Reword the documentation for noautocmd a bit. I pondered modifying do_buffer and callees to allow for BufEnter/Leave being conditionally disabled, but it seems too invasive (and potentially error-prone, especially if new code paths to BufEnter/Leave are added in the future). Unfortunately, doing this has the drawback of blocking ALL such events for the duration, which also means blocking unrelated such events; like if window switching occurs in a ++nested autocmd fired by win_set_buf. If this turns out to be a problem in practice, a different solution specialized for nvim_open_win could be considered. :-) --- runtime/doc/api.txt | 6 +++--- runtime/lua/vim/_meta/api.lua | 6 +++--- src/nvim/api/win_config.c | 30 +++++++++++++++--------------- test/functional/api/window_spec.lua | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 55 insertions(+), 21 deletions(-) diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 125976342b..adaf66c2d9 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -3265,9 +3265,9 @@ nvim_open_win({buffer}, {enter}, {config}) *nvim_open_win()* • footer_pos: Footer position. Must be set with `footer` option. Value can be one of "left", "center", or "right". Default is `"left"`. - • noautocmd: If true then no buffer-related autocommand - events such as |BufEnter|, |BufLeave| or |BufWinEnter| may - fire from calling this function. + • noautocmd: If true then autocommands triggered from + setting the `buffer` to display are blocked (e.g: + |BufEnter|, |BufLeave|, |BufWinEnter|). • fixed: If true when anchor is NW or SW, the float window would be kept fixed even if the window would be truncated. • hide: If true the floating window will be hidden. diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index 4a179d49f3..f2f5f43c26 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -1719,9 +1719,9 @@ function vim.api.nvim_open_term(buffer, opts) end --- • footer_pos: Footer position. Must be set with `footer` --- option. Value can be one of "left", "center", or "right". --- Default is `"left"`. ---- • noautocmd: If true then no buffer-related autocommand ---- events such as `BufEnter`, `BufLeave` or `BufWinEnter` may ---- fire from calling this function. +--- • noautocmd: If true then autocommands triggered from +--- setting the `buffer` to display are blocked (e.g: +--- `BufEnter`, `BufLeave`, `BufWinEnter`). --- • fixed: If true when anchor is NW or SW, the float window --- would be kept fixed even if the window would be truncated. --- • hide: If true the floating window will be hidden. diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 21d6d59b1e..8608b0dde9 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -199,9 +199,9 @@ /// - footer_pos: Footer position. Must be set with `footer` option. /// Value can be one of "left", "center", or "right". /// Default is `"left"`. -/// - noautocmd: If true then no buffer-related autocommand events such as -/// |BufEnter|, |BufLeave| or |BufWinEnter| may fire from -/// calling this function. +/// - noautocmd: If true then autocommands triggered from setting the +/// `buffer` to display are blocked (e.g: |BufEnter|, |BufLeave|, +/// |BufWinEnter|). /// - fixed: If true when anchor is NW or SW, the float window /// would be kept fixed even if the window would be truncated. /// - hide: If true the floating window will be hidden. @@ -302,20 +302,20 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Err tp = win_find_tabpage(wp); } if (tp && bufref_valid(&bufref) && buf != wp->w_buffer) { - const bool noautocmd = curwin != wp || fconfig.noautocmd; - win_set_buf(wp, buf, noautocmd, err); - if (!noautocmd) { + // win_set_buf temporarily makes `wp` the curwin to set the buffer. + // If not entering `wp`, block Enter and Leave events. (cringe) + const bool au_no_enter_leave = curwin != wp && !fconfig.noautocmd; + if (au_no_enter_leave) { + autocmd_no_enter++; + autocmd_no_leave++; + } + win_set_buf(wp, buf, fconfig.noautocmd, err); + if (!fconfig.noautocmd) { tp = win_find_tabpage(wp); } - // win_set_buf autocommands were blocked if we didn't enter, but we still want BufWinEnter. - if (noautocmd && !fconfig.noautocmd && wp->w_buffer == buf) { - const int result = switch_win_noblock(&switchwin, wp, tp, true); - assert(result == OK); - (void)result; - if (apply_autocmds(EVENT_BUFWINENTER, NULL, NULL, false, buf)) { - tp = win_find_tabpage(wp); - } - restore_win_noblock(&switchwin, true); + if (au_no_enter_leave) { + autocmd_no_enter--; + autocmd_no_leave--; } } if (!tp) { diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index 246ff50ddc..6e20c81fc2 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -1620,6 +1620,40 @@ describe('API/win', function() ) command('new | quit') end) + + it('restores last known cursor position if BufWinEnter did not move it', function() + -- This test mostly exists to ensure BufWinEnter is executed before enter_buffer's epilogue. + local buf = api.nvim_get_current_buf() + insert([[ + foo + bar baz .etc + i love autocommand bugs! + supercalifragilisticexpialidocious + marvim is actually a human + llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch + ]]) + api.nvim_win_set_cursor(0, { 5, 2 }) + command('set nostartofline | enew') + local new_win = api.nvim_open_win(buf, false, { split = 'left' }) + eq({ 5, 2 }, api.nvim_win_get_cursor(new_win)) + + exec([[ + only! + autocmd BufWinEnter * ++once normal! j6l + ]]) + new_win = api.nvim_open_win(buf, false, { split = 'left' }) + eq({ 2, 6 }, api.nvim_win_get_cursor(new_win)) + end) + + it('does not block all win_set_buf autocommands if !enter and !noautocmd', function() + local new_buf = fn.bufadd('foobarbaz') + exec([[ + let triggered = "" + autocmd BufReadCmd * ++once let triggered = bufname() + ]]) + api.nvim_open_win(new_buf, false, { split = 'left' }) + eq('foobarbaz', eval('triggered')) + end) end) describe('set_config', function() -- cgit From 66f331fef7ad3df480bd02f1705e176d1a07c785 Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Fri, 23 Feb 2024 09:49:28 +0000 Subject: vim-patch:9.1.0116: win_split_ins may not check available room Problem: win_split_ins has no check for E36 when moving an existing window Solution: check for room and fix the issues in f_win_splitmove() (Sean Dewar) https://github.com/vim/vim/commit/0fd44a5ad81ade342cb54d8984965bdedd2272c8 Omit WSP_FORCE_ROOM, as it's not needed for Nvim's autocmd window, which is floating. Shouldn't be difficult to port later if it's used for anything else. Make win_splitmove continue working for turning floating windows into splits. Move the logic for "unfloating" a float to win_split_ins; unlike splits, no changes to the window layout are needed before calling it, as floats take no room in the window layout and cannot affect the e_noroom check. Add missing tp_curwin-fixing logic for turning external windows into splits, and add a test. NOTE: there are other issues with the way "tabpage independence" is implemented for external windows; namely, some things assume that tp_curwin is indeed a window within that tabpage, and as such, functions like tabpage_winnr and nvim_tabpage_get_win currently don't always work for external windows (with the latter aborting!) Use last_status over frame_add_statusline, as Nvim's last_status already does this for all windows in the current tabpage. Adjust restore_full_snapshot_rec to handle this. This "restore everything" approach is changed in a future commit anyway, so only ensure it's robust enough to just pass tests. Keep check_split_disallowed's current doc comment, as it's actually a bit more accurate here. (I should probably PR Vim to use this one) Allow f_win_splitmove to move a floating "wp" into a split; Nvim supports this. Continue to disallow it from moving the autocommand window into a split (funnily enough, the check wasn't reachable before, as moving a float was disallowed), but now return -1 in that case (win_splitmove also returns FAIL for this, but handling it in f_win_splitmove avoids us needing to switch windows first). Cherry-pick Test_window_split_no_room fix from v9.1.0121. Update nvim_win_set_config to handle win_split_ins failure in later commits. --- src/nvim/api/win_config.c | 12 -- src/nvim/buffer.c | 1 - src/nvim/eval/window.c | 66 ++++------- src/nvim/globals.h | 1 + src/nvim/window.c | 213 +++++++++++++++++++++++++++-------- test/functional/ui/float_spec.lua | 53 +++++++++ test/old/testdir/test_window_cmd.vim | 161 +++++++++++++++++++++++--- 7 files changed, 389 insertions(+), 118 deletions(-) diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 8608b0dde9..c308cadb7c 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -526,18 +526,6 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) } } else { win_remove(win, win_tp == curtab ? NULL : win_tp); - ui_comp_remove_grid(&win->w_grid_alloc); - if (win->w_config.external) { - for (tabpage_T *tp = first_tabpage; tp != NULL; tp = tp->tp_next) { - if (tp == curtab) { - continue; - } - if (tp->tp_curwin == win) { - tp->tp_curwin = tp->tp_firstwin; - } - } - } - win->w_pos_changed = true; } int flags = win_split_flags(fconfig.split, parent == NULL) | WSP_NOENTER; diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index f6c7229485..b013f43ceb 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -117,7 +117,6 @@ # include "buffer.c.generated.h" #endif -static const char *e_auabort = N_("E855: Autocommands caused command to abort"); static const char e_attempt_to_delete_buffer_that_is_in_use_str[] = N_("E937: Attempt to delete a buffer that is in use: %s"); diff --git a/src/nvim/eval/window.c b/src/nvim/eval/window.c index b8aa0c9641..17b8b01963 100644 --- a/src/nvim/eval/window.c +++ b/src/nvim/eval/window.c @@ -659,55 +659,19 @@ void f_win_screenpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) tv_list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_wincol + 1); } -/// Move the window wp into a new split of targetwin in a given direction -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 || is_aucmd_win(wp)) { - return; - } - - // Jump to the target window - if (curwin != targetwin) { - win_goto(targetwin); - } - - // Remove the old window and frame from the tree of frames - int dir; - winframe_remove(wp, &dir, NULL); - win_remove(wp, NULL); - last_status(false); // may need to remove last status line - win_comp_pos(); // recompute window positions - - // Split a window on the desired side and put the old window there - win_split_ins(size, flags, wp, dir); - - // If splitting horizontally, try to preserve height - if (size == 0 && !(flags & WSP_VERT)) { - win_setheight_win(height, wp); - if (p_ea) { - win_equal(wp, true, 'v'); - } - } - - if (oldwin != curwin) { - win_goto(oldwin); - } -} - /// "win_splitmove()" function void f_win_splitmove(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { win_T *wp = find_win_by_nr_or_id(&argvars[0]); win_T *targetwin = find_win_by_nr_or_id(&argvars[1]); + win_T *oldwin = curwin; + + rettv->vval.v_number = -1; if (wp == NULL || targetwin == NULL || wp == targetwin || !win_valid(wp) || !win_valid(targetwin) - || win_float_valid(wp) || win_float_valid(targetwin)) { + || targetwin->w_floating) { emsg(_(e_invalwindow)); - rettv->vval.v_number = -1; return; } @@ -732,7 +696,27 @@ void f_win_splitmove(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) size = (int)tv_dict_get_number(d, "size"); } - win_move_into_split(wp, targetwin, size, flags); + // Check if we can split the target before we bother switching windows. + if (is_aucmd_win(wp) || check_split_disallowed(targetwin) == FAIL) { + return; + } + + if (curwin != targetwin) { + win_goto(targetwin); + } + + // Autocommands may have sent us elsewhere or closed "wp" or "oldwin". + if (curwin == targetwin && win_valid(wp)) { + if (win_splitmove(wp, size, flags) == OK) { + rettv->vval.v_number = 0; + } + } else { + emsg(_(e_auabort)); + } + + if (oldwin != curwin && win_valid(oldwin)) { + win_goto(oldwin); + } } /// "win_gettype(nr)" function diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 22f7daa823..c1c9ae456c 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -939,6 +939,7 @@ EXTERN const char e_using_float_as_string[] INIT(= N_("E806: Using a Float as a EXTERN const char e_cannot_edit_other_buf[] INIT(= N_("E788: Not allowed to edit another buffer now")); EXTERN const char e_using_number_as_bool_nr[] INIT(= N_("E1023: Using a Number as a Bool: %d")); EXTERN const char e_not_callable_type_str[] INIT(= N_("E1085: Not a callable type: %s")); +EXTERN const char e_auabort[] INIT(= N_("E855: Autocommands caused command to abort")); EXTERN const char e_api_error[] INIT(= N_("E5555: API call: %s")); diff --git a/src/nvim/window.c b/src/nvim/window.c index 81f8304b22..cfa28bbc1f 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -455,9 +455,14 @@ newwindow: case 'H': case 'L': CHECK_CMDWIN; - win_totop(Prenum, - ((nchar == 'H' || nchar == 'L') ? WSP_VERT : 0) - | ((nchar == 'H' || nchar == 'K') ? WSP_TOP : WSP_BOT)); + if (firstwin == curwin && lastwin_nofloating() == curwin) { + beep_flush(); + } else { + const int dir = ((nchar == 'H' || nchar == 'L') ? WSP_VERT : 0) + | ((nchar == 'H' || nchar == 'K') ? WSP_TOP : WSP_BOT); + + win_splitmove(curwin, Prenum, dir); + } break; // make all windows the same width and/or height @@ -907,7 +912,7 @@ void ui_ext_win_viewport(win_T *wp) /// If "split_disallowed" is set or "wp"s buffer is closing, give an error and return FAIL. /// Otherwise return OK. -static int check_split_disallowed(const win_T *wp) +int check_split_disallowed(const win_T *wp) FUNC_ATTR_NONNULL_ALL { Error err = ERROR_INIT; @@ -1004,13 +1009,12 @@ win_T *win_split_ins(int size, int flags, win_T *new_wp, int dir) int need_status = 0; int new_size = size; - bool new_in_layout = (new_wp == NULL || new_wp->w_floating); bool vertical = flags & WSP_VERT; bool toplevel = flags & (WSP_TOP | WSP_BOT); // add a status line when p_ls == 1 and splitting the first window if (one_nonfloat() && p_ls == 1 && oldwin->w_status_height == 0) { - if (oldwin->w_height <= p_wmh && new_in_layout) { + if (oldwin->w_height <= p_wmh) { emsg(_(e_noroom)); return NULL; } @@ -1059,7 +1063,7 @@ win_T *win_split_ins(int size, int flags, win_T *new_wp, int dir) available = oldwin->w_frame->fr_width; needed += minwidth; } - if (available < needed && new_in_layout) { + if (available < needed) { emsg(_(e_noroom)); return NULL; } @@ -1139,7 +1143,7 @@ win_T *win_split_ins(int size, int flags, win_T *new_wp, int dir) available = oldwin->w_frame->fr_height; needed += minheight; } - if (available < needed && new_in_layout) { + if (available < needed) { emsg(_(e_noroom)); return NULL; } @@ -1229,8 +1233,27 @@ win_T *win_split_ins(int size, int flags, win_T *new_wp, int dir) // make the contents of the new window the same as the current one win_init(wp, curwin, flags); } else if (wp->w_floating) { - new_frame(wp); + ui_comp_remove_grid(&wp->w_grid_alloc); + if (ui_has(kUIMultigrid)) { + wp->w_pos_changed = true; + } else { + // No longer a float, a non-multigrid UI shouldn't draw it as such + ui_call_win_hide(wp->w_grid_alloc.handle); + win_free_grid(wp, true); + } + + // External windows are independent of tabpages, and may have been the curwin of others. + if (wp->w_config.external) { + FOR_ALL_TABS(tp) { + if (tp != curtab && tp->tp_curwin == wp) { + tp->tp_curwin = tp->tp_firstwin; + } + } + } + wp->w_floating = false; + new_frame(wp); + // non-floating window doesn't store float config or have a border. wp->w_config = WIN_CONFIG_INIT; CLEAR_FIELD(wp->w_border_adj); @@ -1874,48 +1897,67 @@ static void win_rotate(bool upwards, int count) redraw_all_later(UPD_NOT_VALID); } -// Move the current window to the very top/bottom/left/right of the screen. -static void win_totop(int size, int flags) +/// Move "wp" into a new split in a given direction, possibly relative to the +/// current window. +/// "wp" must be valid in the current tabpage. +/// Returns FAIL for failure, OK otherwise. +int win_splitmove(win_T *wp, int size, int flags) { int dir = 0; - int height = curwin->w_height; + int height = wp->w_height; - if (firstwin == curwin && lastwin_nofloating() == curwin) { - beep_flush(); - return; - } - if (is_aucmd_win(curwin)) { - return; + if (firstwin == wp && lastwin_nofloating() == wp) { + return OK; // nothing to do } - if (check_split_disallowed(curwin) == FAIL) { - return; + if (is_aucmd_win(wp) || check_split_disallowed(wp) == FAIL) { + return FAIL; } - if (curwin->w_floating) { - ui_comp_remove_grid(&curwin->w_grid_alloc); - if (ui_has(kUIMultigrid)) { - curwin->w_pos_changed = true; - } else { - // No longer a float, a non-multigrid UI shouldn't draw it as such - ui_call_win_hide(curwin->w_grid_alloc.handle); - win_free_grid(curwin, true); - } + frame_T *frp = NULL; + if (wp->w_floating) { + win_remove(wp, NULL); } else { + // Undoing changes to frames if splitting fails is complicated. + // Save a full snapshot to restore instead. + frp = make_full_snapshot(); + // Remove the window and frame from the tree of frames. - winframe_remove(curwin, &dir, NULL); + winframe_remove(wp, &dir, NULL); + win_remove(wp, NULL); + last_status(false); // may need to remove last status line + win_comp_pos(); // recompute window positions + } + + // Split a window on the desired side and put "wp" there. + if (win_split_ins(size, flags, wp, dir) == NULL) { + win_append(wp->w_prev, wp); + if (!wp->w_floating) { + // Restore the previous layout from the snapshot. + xfree(wp->w_frame); + restore_full_snapshot(frp); + + // Vertical separators to the left may have been lost. Restore them. + frp = wp->w_frame; + if (frp->fr_parent->fr_layout == FR_ROW && frp->fr_prev != NULL) { + frame_add_vsep(frp->fr_prev); + } + } + return FAIL; } - win_remove(curwin, NULL); - last_status(false); // may need to remove last status line - win_comp_pos(); // recompute window positions + clear_snapshot_rec(frp); - // Split a window on the desired side and put the window there. - win_split_ins(size, flags, curwin, dir); - if (!(flags & WSP_VERT)) { - win_setheight(height); + // If splitting horizontally, try to preserve height. + // Note that win_split_ins autocommands may have immediately made "wp" floating! + if (size == 0 && !(flags & WSP_VERT) && !wp->w_floating) { + win_setheight_win(height, wp); if (p_ea) { - win_equal(curwin, true, 'v'); + // Equalize windows. Note that win_split_ins autocommands may have + // made a window other than "wp" current. + win_equal(curwin, curwin == wp, 'v'); } } + + return OK; } // Move window "win1" to below/right of "win2" and make "win1" the current @@ -2777,13 +2819,10 @@ int win_close(win_T *win, bool free_buf, bool force) ui_comp_remove_grid(&win->w_grid_alloc); assert(first_tabpage != NULL); // suppress clang "Dereference of NULL pointer" if (win->w_config.external) { - for (tabpage_T *tp = first_tabpage; tp != NULL; tp = tp->tp_next) { - if (tp == curtab) { - continue; - } - if (tp->tp_curwin == win) { + FOR_ALL_TABS(tp) { + if (tp != curtab && tp->tp_curwin == win) { // NB: an autocmd can still abort the closing of this window, - // bur carring out this change anyway shouldn't be a catastrophe. + // but carrying out this change anyway shouldn't be a catastrophe. tp->tp_curwin = tp->tp_firstwin; } } @@ -7207,23 +7246,23 @@ void reset_lnums(void) void make_snapshot(int idx) { clear_snapshot(curtab, idx); - make_snapshot_rec(topframe, &curtab->tp_snapshot[idx]); + make_snapshot_rec(topframe, &curtab->tp_snapshot[idx], false); } -static void make_snapshot_rec(frame_T *fr, frame_T **frp) +static void make_snapshot_rec(frame_T *fr, frame_T **frp, bool snap_wins) { *frp = xcalloc(1, sizeof(frame_T)); (*frp)->fr_layout = fr->fr_layout; (*frp)->fr_width = fr->fr_width; (*frp)->fr_height = fr->fr_height; if (fr->fr_next != NULL) { - make_snapshot_rec(fr->fr_next, &((*frp)->fr_next)); + make_snapshot_rec(fr->fr_next, &((*frp)->fr_next), snap_wins); } if (fr->fr_child != NULL) { - make_snapshot_rec(fr->fr_child, &((*frp)->fr_child)); + make_snapshot_rec(fr->fr_child, &((*frp)->fr_child), snap_wins); } - if (fr->fr_layout == FR_LEAF && fr->fr_win == curwin) { - (*frp)->fr_win = curwin; + if (fr->fr_layout == FR_LEAF && (snap_wins || fr->fr_win == curwin)) { + (*frp)->fr_win = fr->fr_win; } } @@ -7340,6 +7379,80 @@ static win_T *restore_snapshot_rec(frame_T *sn, frame_T *fr) return wp; } +/// Return a snapshot of all frames in the current tabpage and which windows are +/// in them. +/// Use clear_snapshot_rec to free the snapshot. +static frame_T *make_full_snapshot(void) +{ + frame_T *frp; + make_snapshot_rec(topframe, &frp, true); + return frp; +} + +/// Restore all frames in the full snapshot "sn" for the current tabpage. +/// Caller must ensure that the screen size didn't change, no windows with frames +/// in the snapshot were freed, and windows with frames not in the snapshot are +/// removed from their frames! +/// Doesn't restore changed window vertical separators. +/// Frees the old frames. Don't call clear_snapshot_rec on "sn" afterwards! +static void restore_full_snapshot(frame_T *sn) +{ + if (sn == NULL) { + return; + } + + clear_snapshot_rec(topframe); + restore_full_snapshot_rec(sn); + curtab->tp_topframe = topframe = sn; + last_status(false); + + // If the amount of space available changed, first try setting the sizes of + // windows with 'winfix{width,height}'. If that doesn't result in the right + // size, forget about that option. + if (topframe->fr_width != Columns) { + frame_new_width(topframe, Columns, false, true); + if (!frame_check_width(topframe, Columns)) { + frame_new_width(topframe, Columns, false, false); + } + } + if (topframe->fr_height != ROWS_AVAIL) { + frame_new_height(topframe, (int)ROWS_AVAIL, false, true); + if (!frame_check_height(topframe, (int)ROWS_AVAIL)) { + frame_new_height(topframe, (int)ROWS_AVAIL, false, false); + } + } + + win_comp_pos(); +} + +static void restore_full_snapshot_rec(frame_T *sn) +{ + if (sn == NULL) { + return; + } + + if (sn->fr_child != NULL) { + sn->fr_child->fr_parent = sn; + } + if (sn->fr_next != NULL) { + sn->fr_next->fr_parent = sn->fr_parent; + sn->fr_next->fr_prev = sn; + } + if (sn->fr_win != NULL) { + sn->fr_win->w_frame = sn; + // Assume for now that all windows have statuslines, so last_status in restore_full_snapshot + // doesn't resize frames to fit any missing statuslines. + sn->fr_win->w_status_height = STATUS_HEIGHT; + sn->fr_win->w_hsep_height = 0; + + // Resize window to fit the frame. + frame_new_height(sn, sn->fr_height, false, false); + frame_new_width(sn, sn->fr_width, false, false); + } + restore_full_snapshot_rec(sn->fr_child); + restore_full_snapshot_rec(sn->fr_next); +} + /// Check that "topfrp" and its children are at the right height. /// /// @param topfrp top frame pointer diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index cdb3b79963..e324d03b30 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -550,6 +550,43 @@ describe('float window', function() eq({ w0 }, api.nvim_list_wins()) end) + it('win_splitmove() can move float into a split', function() + command('split') + eq({'col', {{'leaf', 1001}, {'leaf', 1000}}}, fn.winlayout()) + + local win1 = api.nvim_open_win(0, true, {relative = 'editor', row = 1, col = 1, width = 5, height = 5}) + fn.win_splitmove(win1, 1001, {vertical = true}) + eq({'col', {{'row', {{'leaf', win1}, {'leaf', 1001}}}, {'leaf', 1000}}}, fn.winlayout()) + eq('', api.nvim_win_get_config(win1).relative) + + -- Should be unable to create a split relative to a float, though. + local win2 = api.nvim_open_win(0, true, {relative = 'editor', row = 1, col = 1, width = 5, height = 5}) + eq('Vim:E957: Invalid window number', pcall_err(fn.win_splitmove, win1, win2, {vertical = true})) + end) + + it('tp_curwin updated if external window is moved into split', function() + local screen = Screen.new(20, 7) + screen:attach { ext_multigrid = true } + + command('tabnew') + local external_win = api.nvim_open_win(0, true, {external = true, width = 5, height = 5}) + eq(external_win, api.nvim_get_current_win()) + eq(2, fn.tabpagenr()) + command('tabfirst') + api.nvim_set_current_win(external_win) + eq(external_win, api.nvim_get_current_win()) + eq(1, fn.tabpagenr()) + + command('wincmd J') + eq(external_win, api.nvim_get_current_win()) + eq(false, api.nvim_win_get_config(external_win).external) + command('tabnext') + eq(2, fn.tabpagenr()) + neq(external_win, api.nvim_get_current_win()) + + screen:detach() + end) + describe('with only one tabpage,', function() local float_opts = {relative = 'editor', row = 1, col = 1, width = 1, height = 1} local old_buf, old_win @@ -9101,6 +9138,22 @@ describe('float window', function() ]]} end end) + + it('attempt to turn into split with no room', function() + eq('Vim(split):E36: Not enough room', pcall_err(command, 'execute "split |"->repeat(&lines)')) + command('vsplit | wincmd | | wincmd p') + api.nvim_open_win(0, true, {relative = "editor", row = 0, col = 0, width = 5, height = 5}) + local config = api.nvim_win_get_config(0) + eq('editor', config.relative) + + local layout = fn.winlayout() + local restcmd = fn.winrestcmd() + eq('Vim(wincmd):E36: Not enough room', pcall_err(command, 'wincmd K')) + eq('Vim(wincmd):E36: Not enough room', pcall_err(command, 'wincmd J')) + eq(layout, fn.winlayout()) + eq(restcmd, fn.winrestcmd()) + eq(config, api.nvim_win_get_config(0)) + end) end describe('with ext_multigrid', function() diff --git a/test/old/testdir/test_window_cmd.vim b/test/old/testdir/test_window_cmd.vim index da1711a0a1..99a713d2ed 100644 --- a/test/old/testdir/test_window_cmd.vim +++ b/test/old/testdir/test_window_cmd.vim @@ -272,6 +272,16 @@ func Test_window_split_no_room() for s in range(1, hor_split_count) | split | endfor call assert_fails('split', 'E36:') + botright vsplit + wincmd | + let layout = winlayout() + let restcmd = winrestcmd() + call assert_fails('wincmd J', 'E36:') + call assert_fails('wincmd K', 'E36:') + call assert_equal(layout, winlayout()) + call assert_equal(restcmd, winrestcmd()) + only + " N vertical windows need >= 2*(N - 1) + 1 columns: " - 1 column + 1 separator for each window (except last window) " - 1 column for the last window which does not have separator @@ -284,7 +294,39 @@ func Test_window_split_no_room() for s in range(1, ver_split_count) | vsplit | endfor call assert_fails('vsplit', 'E36:') + split + wincmd | + let layout = winlayout() + let restcmd = winrestcmd() + call assert_fails('wincmd H', 'E36:') + call assert_fails('wincmd L', 'E36:') + call assert_equal(layout, winlayout()) + call assert_equal(restcmd, winrestcmd()) + + " Check that the last statusline isn't lost. + " Set its window's width to 2 for the test. + wincmd j + set laststatus=0 winminwidth=0 + vertical resize 2 + set winminwidth& + call setwinvar(winnr('k'), '&statusline', '@#') + let last_stl_row = win_screenpos(0)[0] - 1 + redraw + call assert_equal('@#|', GetScreenStr(last_stl_row)) + call assert_equal('~ |', GetScreenStr(&lines - &cmdheight)) + + let restcmd = winrestcmd() + call assert_fails('wincmd H', 'E36:') + call assert_fails('wincmd L', 'E36:') + call assert_equal(layout, winlayout()) + call assert_equal(restcmd, winrestcmd()) + call setwinvar(winnr('k'), '&statusline', '=-') + redraw + call assert_equal('=-|', GetScreenStr(last_stl_row)) + call assert_equal('~ |', GetScreenStr(&lines - &cmdheight)) + %bw! + set laststatus& endfunc func Test_window_exchange() @@ -1055,6 +1097,44 @@ func Test_win_splitmove() tabnew call assert_fails('call win_splitmove(1, win_getid(1, 1))', 'E957:') tabclose + + split + augroup WinSplitMove + au! + au WinEnter * ++once call win_gotoid(win_getid(winnr('#'))) + augroup END + call assert_fails('call win_splitmove(winnr(), winnr("#"))', 'E855:') + + augroup WinSplitMove + au! + au WinLeave * ++once quit + augroup END + call assert_fails('call win_splitmove(winnr(), winnr("#"))', 'E855:') + + split + split + augroup WinSplitMove + au! + au WinEnter * ++once let s:triggered = v:true + \| call assert_fails('call win_splitmove(winnr("$"), winnr())', 'E242:') + augroup END + quit + call assert_equal(v:true, s:triggered) + unlet! s:triggered + + new + augroup WinSplitMove + au! + au BufHidden * ++once let s:triggered = v:true + \| call assert_fails('call win_splitmove(winnr("#"), winnr())', 'E1159:') + augroup END + hide + call assert_equal(v:true, s:triggered) + unlet! s:triggered + + au! WinSplitMove + augroup! WinSplitMove + %bw! endfunc func Test_floatwin_splitmove() @@ -1062,7 +1142,8 @@ func Test_floatwin_splitmove() let win2 = win_getid() let popup_winid = nvim_open_win(0, 0, {'relative': 'win', \ 'row': 3, 'col': 3, 'width': 12, 'height': 3}) - call assert_fails('call win_splitmove(popup_winid, win2)', 'E957:') + " Nvim: floating windows are supported for the first argument. + " call assert_fails('call win_splitmove(popup_winid, win2)', 'E957:') call assert_fails('call win_splitmove(win2, popup_winid)', 'E957:') call nvim_win_close(popup_winid, 1) @@ -2007,23 +2088,75 @@ func Test_new_help_window_on_error() endfunc func Test_smoothscroll_in_zero_width_window() - let save_lines = &lines - let save_columns = &columns + set cpo+=n number smoothscroll + set winwidth=99999 winminwidth=0 - winsize 0 24 - set cpo+=n - exe "noremap 0 \n\L" - norm 000000 - set number smoothscroll - exe "norm \" + vsplit + call assert_equal(0, winwidth(winnr('#'))) + call win_execute(win_getid(winnr('#')), "norm! \") only! - let &lines = save_lines - let &columns = save_columns - set cpo-=n - unmap 0 - set nonumber nosmoothscroll + set winwidth& winminwidth& + set cpo-=n nonumber nosmoothscroll endfunc +func Test_splitmove_flatten_frame() + split + vsplit + + wincmd L + let layout = winlayout() + wincmd K + wincmd L + call assert_equal(winlayout(), layout) + + only! +endfunc + +func Test_splitmove_autocmd_window_no_room() + " Open as many windows as possible + while v:true + try + split + catch /E36:/ + break + endtry + endwhile + while v:true + try + vsplit + catch /E36:/ + break + endtry + endwhile + + wincmd j + vsplit + call assert_fails('wincmd H', 'E36:') + call assert_fails('wincmd J', 'E36:') + call assert_fails('wincmd K', 'E36:') + call assert_fails('wincmd L', 'E36:') + + edit unload me + enew + bunload! unload\ me + augroup SplitMoveAucmdWin + au! + au BufEnter * ++once let s:triggered = v:true + \| call assert_equal('autocmd', win_gettype()) + augroup END + let layout = winlayout() + let restcmd = winrestcmd() + " bufload opening the autocommand window shouldn't give E36. + call bufload('unload me') + call assert_equal(v:true, s:triggered) + call assert_equal(winlayout(), layout) + call assert_equal(winrestcmd(), restcmd) + + unlet! s:triggered + au! SplitMoveAucmdWin + augroup! SplitMoveAucmdWin + %bw! +endfunc " vim: shiftwidth=2 sts=2 expandtab -- cgit From 24dfa47e4f4ca41d0c5f8c1c0f851602362c81d3 Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Sat, 24 Feb 2024 23:18:50 +0000 Subject: vim-patch:partial:9.1.0117: Stop split-moving from firing WinNew and WinNewPre autocommands Problem: win_splitmove fires WinNewPre and possibly WinNew when moving windows, even though no new windows are created. Solution: don't fire WinNew and WinNewPre when inserting an existing window, even if it isn't the current window. Improve the accuracy of related documentation. (Sean Dewar) https://github.com/vim/vim/commit/96cc4aef3d47d0fd70e68908af3d48a0dce8ea70 Partial as WinNewPre has not been ported yet (it currently has problems anyway). --- runtime/doc/builtin.txt | 8 ++++---- runtime/doc/windows.txt | 30 ++++++++++++++---------------- runtime/lua/vim/_meta/vimfn.lua | 8 ++++---- src/nvim/eval.lua | 8 ++++---- src/nvim/window.c | 4 +++- test/old/testdir/test_window_cmd.vim | 18 ++++++++++++++++++ 6 files changed, 47 insertions(+), 29 deletions(-) diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index c5f3946871..4b1ccc0c5c 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -8901,10 +8901,10 @@ win_screenpos({nr}) *win_screenpos()* tabpage. win_splitmove({nr}, {target} [, {options}]) *win_splitmove()* - Move the window {nr} to a new split of the window {target}. - This is similar to moving to {target}, creating a new window - using |:split| but having the same contents as window {nr}, and - then closing {nr}. + Temporarily switch to window {target}, then move window {nr} + to a new split adjacent to {target}. + Unlike commands such as |:split|, no new windows are created + (the |window-ID| of window {nr} is unchanged after the move). Both {nr} and {target} can be window numbers or |window-ID|s. Both must be in the current tab page. diff --git a/runtime/doc/windows.txt b/runtime/doc/windows.txt index b71e7c80ab..4791e73929 100644 --- a/runtime/doc/windows.txt +++ b/runtime/doc/windows.txt @@ -502,35 +502,33 @@ horizontally split windows. CTRL-W H does it the other way around. *CTRL-W_K* CTRL-W K Move the current window to be at the very top, using the full - width of the screen. This works like closing the current - window and then creating another one with ":topleft split", - except that the current window contents is used for the new - window. + width of the screen. This works like `:topleft split`, except + it is applied to the current window and no new window is + created. *CTRL-W_J* CTRL-W J Move the current window to be at the very bottom, using the - full width of the screen. This works like closing the current - window and then creating another one with ":botright split", - except that the current window contents is used for the new - window. + full width of the screen. This works like `:botright split`, + except it is applied to the current window and no new window + is created. *CTRL-W_H* CTRL-W H Move the current window to be at the far left, using the - full height of the screen. This works like closing the - current window and then creating another one with - `:vert topleft split`, except that the current window contents - is used for the new window. + full height of the screen. This works like + `:vert topleft split`, except it is applied to the current + window and no new window is created. *CTRL-W_L* CTRL-W L Move the current window to be at the far right, using the full - height of the screen. This works like closing the - current window and then creating another one with - `:vert botright split`, except that the current window - contents is used for the new window. + height of the screen. This works like `:vert botright split`, + except it is applied to the current window and no new window + is created. *CTRL-W_T* CTRL-W T Move the current window to a new tab page. This fails if there is only one window in the current tab page. + This works like `:tab split`, except the previous window is + closed. When a count is specified the new tab page will be opened before the tab page with this index. Otherwise it comes after the current tab page. diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index ac25547212..ee68f669f8 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -10598,10 +10598,10 @@ function vim.fn.win_move_statusline(nr, offset) end --- @return any function vim.fn.win_screenpos(nr) end ---- Move the window {nr} to a new split of the window {target}. ---- This is similar to moving to {target}, creating a new window ---- using |:split| but having the same contents as window {nr}, and ---- then closing {nr}. +--- Temporarily switch to window {target}, then move window {nr} +--- to a new split adjacent to {target}. +--- Unlike commands such as |:split|, no new windows are created +--- (the |window-ID| of window {nr} is unchanged after the move). --- --- Both {nr} and {target} can be window numbers or |window-ID|s. --- Both must be in the current tab page. diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index b7120d5dd5..febd022254 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -12699,10 +12699,10 @@ M.funcs = { args = { 2, 3 }, base = 1, desc = [=[ - Move the window {nr} to a new split of the window {target}. - This is similar to moving to {target}, creating a new window - using |:split| but having the same contents as window {nr}, and - then closing {nr}. + Temporarily switch to window {target}, then move window {nr} + to a new split adjacent to {target}. + Unlike commands such as |:split|, no new windows are created + (the |window-ID| of window {nr} is unchanged after the move). Both {nr} and {target} can be window numbers or |window-ID|s. Both must be in the current tab page. diff --git a/src/nvim/window.c b/src/nvim/window.c index cfa28bbc1f..b1135d59fc 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -987,6 +987,8 @@ int win_split(int size, int flags) /// When "new_wp" is NULL: split the current window in two. /// When "new_wp" is not NULL: insert this window at the far /// top/left/right/bottom. +/// On failure, if "new_wp" was not NULL, no changes will have been made to the +/// window layout or sizes. /// @return NULL for failure, or pointer to new window win_T *win_split_ins(int size, int flags, win_T *new_wp, int dir) { @@ -1494,7 +1496,7 @@ win_T *win_split_ins(int size, int flags, win_T *new_wp, int dir) if (!(flags & WSP_NOENTER)) { // make the new window the current window - win_enter_ext(wp, WEE_TRIGGER_NEW_AUTOCMDS | WEE_TRIGGER_ENTER_AUTOCMDS + win_enter_ext(wp, (new_wp == NULL ? WEE_TRIGGER_NEW_AUTOCMDS : 0) | WEE_TRIGGER_ENTER_AUTOCMDS | WEE_TRIGGER_LEAVE_AUTOCMDS); } if (vertical) { diff --git a/test/old/testdir/test_window_cmd.vim b/test/old/testdir/test_window_cmd.vim index 99a713d2ed..fc379c198d 100644 --- a/test/old/testdir/test_window_cmd.vim +++ b/test/old/testdir/test_window_cmd.vim @@ -1066,6 +1066,19 @@ func Test_win_splitmove() leftabove split b leftabove vsplit c leftabove split d + + " win_splitmove doesn't actually create or close any windows, so expect an + " unchanged winid and no WinNew/WinClosed events, like :wincmd H/J/K/L. + let s:triggered = [] + augroup WinSplitMove + au! + " Nvim: WinNewPre not ported yet. Also needs full port of v9.1.0117 to pass. + " au WinNewPre * let s:triggered += ['WinNewPre'] + au WinNew * let s:triggered += ['WinNew', win_getid()] + au WinClosed * let s:triggered += ['WinClosed', str2nr(expand(''))] + augroup END + let winid = win_getid() + call assert_equal(0, win_splitmove(winnr(), winnr('l'))) call assert_equal(bufname(winbufnr(1)), 'c') call assert_equal(bufname(winbufnr(2)), 'd') @@ -1088,6 +1101,11 @@ func Test_win_splitmove() call assert_equal(bufname(winbufnr(3)), 'a') call assert_equal(bufname(winbufnr(4)), 'd') call assert_fails('call win_splitmove(winnr(), winnr("k"), v:_null_dict)', 'E1297:') + call assert_equal([], s:triggered) + call assert_equal(winid, win_getid()) + + unlet! s:triggered + au! WinSplitMove only | bd call assert_fails('call win_splitmove(winnr(), 123)', 'E957:') -- cgit From 1c6b693ec1592f9d193fc9cc1bb03e738fb2bef6 Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Sun, 25 Feb 2024 01:03:26 +0000 Subject: vim-patch:9.1.0118: Use different restoration strategy in win_splitmove Problem: saving and restoring all frames to split-move is overkill now that WinNewPre is not fired when split-moving. Solution: defer the flattening of frames until win_split_ins begins reorganising them, and attempt to restore the layout by undoing our changes. (Sean Dewar) https://github.com/vim/vim/commit/704966c2545897dfcf426dd9ef946aeb6fa80c38 Adjust winframe_restore to account for Nvim's horizontal separators when the global statusline is in use. Add a test. --- src/nvim/api/win_config.c | 14 +- src/nvim/window.c | 267 +++++++++++++++------------------ src/nvim/winfloat.c | 2 +- test/functional/ui/statusline_spec.lua | 20 +++ 4 files changed, 152 insertions(+), 151 deletions(-) diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index c308cadb7c..978d8515c8 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -260,7 +260,7 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Err int flags = win_split_flags(fconfig.split, parent == NULL) | WSP_NOENTER; if (parent == NULL) { - wp = win_split_ins(0, flags, NULL, 0); + wp = win_split_ins(0, flags, NULL, 0, NULL); } else { tp = win_find_tabpage(parent); switchwin_T switchwin; @@ -268,7 +268,7 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Err const int result = switch_win(&switchwin, parent, tp, true); assert(result == OK); (void)result; - wp = win_split_ins(0, flags, NULL, 0); + wp = win_split_ins(0, flags, NULL, 0, NULL); restore_win(&switchwin, true); } if (wp) { @@ -495,11 +495,11 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) // If the frame doesn't have a parent, the old frame // was the root frame and we need to create a top-level split. int dir; - new_curwin = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp); + new_curwin = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp, NULL); } else if (n_frames == 2) { // There are two windows in the frame, we can just rotate it. int dir; - neighbor = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp); + neighbor = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp, NULL); new_curwin = neighbor; } else { // There is only one window in the frame, we can't split it. @@ -511,7 +511,7 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) parent = neighbor; } else { int dir; - new_curwin = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp); + new_curwin = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp, NULL); } win_remove(win, win_tp == curtab ? NULL : win_tp); // move to neighboring window if we're moving the current window to a new tabpage @@ -531,7 +531,7 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) int flags = win_split_flags(fconfig.split, parent == NULL) | WSP_NOENTER; if (parent == NULL) { - if (!win_split_ins(0, flags, win, 0)) { + if (!win_split_ins(0, flags, win, 0, NULL)) { // TODO(willothy): What should this error message say? api_set_error(err, kErrorTypeException, "Failed to split window"); return; @@ -542,7 +542,7 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) const int result = switch_win(&switchwin, parent, win_find_tabpage(parent), true); (void)result; assert(result == OK); - win_split_ins(0, flags, win, 0); + win_split_ins(0, flags, win, 0, NULL); restore_win(&switchwin, true); } if (HAS_KEY_X(config, width)) { diff --git a/src/nvim/window.c b/src/nvim/window.c index b1135d59fc..2d0010ad6c 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -981,16 +981,19 @@ int win_split(int size, int flags) clear_snapshot(curtab, SNAP_HELP_IDX); } - return win_split_ins(size, flags, NULL, 0) == NULL ? FAIL : OK; + return win_split_ins(size, flags, NULL, 0, NULL) == NULL ? FAIL : OK; } /// When "new_wp" is NULL: split the current window in two. /// When "new_wp" is not NULL: insert this window at the far /// top/left/right/bottom. +/// When "to_flatten" is not NULL: flatten this frame before reorganising frames; +/// remains unflattened on failure. +/// /// On failure, if "new_wp" was not NULL, no changes will have been made to the /// window layout or sizes. /// @return NULL for failure, or pointer to new window -win_T *win_split_ins(int size, int flags, win_T *new_wp, int dir) +win_T *win_split_ins(int size, int flags, win_T *new_wp, int dir, frame_T *to_flatten) { win_T *wp = new_wp; @@ -1261,6 +1264,11 @@ win_T *win_split_ins(int size, int flags, win_T *new_wp, int dir) CLEAR_FIELD(wp->w_border_adj); } + // Going to reorganize frames now, make sure they're flat. + if (to_flatten != NULL) { + frame_flatten(to_flatten); + } + bool before; frame_T *curfrp; @@ -1915,38 +1923,29 @@ int win_splitmove(win_T *wp, int size, int flags) return FAIL; } - frame_T *frp = NULL; + frame_T *unflat_altfr = NULL; if (wp->w_floating) { win_remove(wp, NULL); } else { - // Undoing changes to frames if splitting fails is complicated. - // Save a full snapshot to restore instead. - frp = make_full_snapshot(); - - // Remove the window and frame from the tree of frames. - winframe_remove(wp, &dir, NULL); + // Remove the window and frame from the tree of frames. Don't flatten any + // frames yet so we can restore things if win_split_ins fails. + winframe_remove(wp, &dir, NULL, &unflat_altfr); win_remove(wp, NULL); last_status(false); // may need to remove last status line win_comp_pos(); // recompute window positions } // Split a window on the desired side and put "wp" there. - if (win_split_ins(size, flags, wp, dir) == NULL) { + if (win_split_ins(size, flags, wp, dir, unflat_altfr) == NULL) { win_append(wp->w_prev, wp); if (!wp->w_floating) { - // Restore the previous layout from the snapshot. - xfree(wp->w_frame); - restore_full_snapshot(frp); - - // Vertical separators to the left may have been lost. Restore them. - frp = wp->w_frame; - if (frp->fr_parent->fr_layout == FR_ROW && frp->fr_prev != NULL) { - frame_add_vsep(frp->fr_prev); - } + // win_split_ins doesn't change sizes or layout if it fails to insert an + // existing window, so just undo winframe_remove. + winframe_restore(wp, dir, unflat_altfr); + win_comp_pos(); // recompute window positions } return FAIL; } - clear_snapshot_rec(frp); // If splitting horizontally, try to preserve height. // Note that win_split_ins autocommands may have immediately made "wp" floating! @@ -3065,7 +3064,7 @@ static win_T *win_free_mem(win_T *win, int *dirp, tabpage_T *tp) if (!win->w_floating) { // Remove the window and its frame from the tree of frames. frame_T *frp = win->w_frame; - wp = winframe_remove(win, dirp, tp); + wp = winframe_remove(win, dirp, tp, NULL); xfree(frp); } else { *dirp = 'h'; // Dummy value. @@ -3146,9 +3145,11 @@ void win_free_all(void) /// /// @param dirp set to 'v' or 'h' for direction if 'ea' /// @param tp tab page "win" is in, NULL for current +/// @param unflat_altfr if not NULL, set to pointer of frame that got +/// the space, and it is not flattened /// /// @return a pointer to the window that got the freed up space. -win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp) +win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp, frame_T **unflat_altfr) FUNC_ATTR_NONNULL_ARG(1, 2) { assert(tp == NULL || tp != curtab); @@ -3236,56 +3237,110 @@ win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp) frame_comp_pos(frp2, &row, &col); } - if (frp2->fr_next == NULL && frp2->fr_prev == NULL) { - // There is no other frame in this list, move its info to the parent - // and remove it. - frp2->fr_parent->fr_layout = frp2->fr_layout; - frp2->fr_parent->fr_child = frp2->fr_child; - frame_T *frp; - FOR_ALL_FRAMES(frp, frp2->fr_child) { - frp->fr_parent = frp2->fr_parent; - } - frp2->fr_parent->fr_win = frp2->fr_win; - if (frp2->fr_win != NULL) { - frp2->fr_win->w_frame = frp2->fr_parent; + if (unflat_altfr == NULL) { + frame_flatten(frp2); + } else { + *unflat_altfr = frp2; + } + + return wp; +} + +/// Flatten "frp" into its parent frame if it's the only child, also merging its +/// list with the grandparent if they share the same layout. +/// Frees "frp" if flattened; also "frp->fr_parent" if it has the same layout. +/// "frp" must be valid in the current tabpage. +static void frame_flatten(frame_T *frp) + FUNC_ATTR_NONNULL_ALL +{ + if (frp->fr_next != NULL || frp->fr_prev != NULL) { + return; + } + + // There is no other frame in this list, move its info to the parent + // and remove it. + frp->fr_parent->fr_layout = frp->fr_layout; + frp->fr_parent->fr_child = frp->fr_child; + frame_T *frp2; + FOR_ALL_FRAMES(frp2, frp->fr_child) { + frp2->fr_parent = frp->fr_parent; + } + frp->fr_parent->fr_win = frp->fr_win; + if (frp->fr_win != NULL) { + frp->fr_win->w_frame = frp->fr_parent; + } + frp2 = frp->fr_parent; + if (topframe->fr_child == frp) { + topframe->fr_child = frp2; + } + xfree(frp); + + frp = frp2->fr_parent; + if (frp != NULL && frp->fr_layout == frp2->fr_layout) { + // The frame above the parent has the same layout, have to merge + // the frames into this list. + if (frp->fr_child == frp2) { + frp->fr_child = frp2->fr_child; + } + assert(frp2->fr_child); + frp2->fr_child->fr_prev = frp2->fr_prev; + if (frp2->fr_prev != NULL) { + frp2->fr_prev->fr_next = frp2->fr_child; + } + for (frame_T *frp3 = frp2->fr_child;; frp3 = frp3->fr_next) { + frp3->fr_parent = frp; + if (frp3->fr_next == NULL) { + frp3->fr_next = frp2->fr_next; + if (frp2->fr_next != NULL) { + frp2->fr_next->fr_prev = frp3; + } + break; + } } - frp = frp2->fr_parent; if (topframe->fr_child == frp2) { topframe->fr_child = frp; } xfree(frp2); + } +} - frp2 = frp->fr_parent; - if (frp2 != NULL && frp2->fr_layout == frp->fr_layout) { - // The frame above the parent has the same layout, have to merge - // the frames into this list. - if (frp2->fr_child == frp) { - frp2->fr_child = frp->fr_child; - } - assert(frp->fr_child); - frp->fr_child->fr_prev = frp->fr_prev; - if (frp->fr_prev != NULL) { - frp->fr_prev->fr_next = frp->fr_child; - } - frame_T *frp3; - for (frp3 = frp->fr_child;; frp3 = frp3->fr_next) { - frp3->fr_parent = frp2; - if (frp3->fr_next == NULL) { - frp3->fr_next = frp->fr_next; - if (frp->fr_next != NULL) { - frp->fr_next->fr_prev = frp3; - } - break; - } - } - if (topframe->fr_child == frp) { - topframe->fr_child = frp2; - } - xfree(frp); +/// Undo changes from a prior call to winframe_remove, also restoring lost +/// vertical separators and statuslines. +/// Caller must ensure no other changes were made to the layout or window sizes! +static void winframe_restore(win_T *wp, int dir, frame_T *unflat_altfr) + FUNC_ATTR_NONNULL_ALL +{ + frame_T *frp = wp->w_frame; + + // Put "wp"'s frame back where it was. + if (frp->fr_prev != NULL) { + frame_append(frp->fr_prev, frp); + } else { + frame_insert(frp->fr_next, frp); + } + + // Vertical separators to the left may have been lost. Restore them. + if (wp->w_vsep_width == 0 && frp->fr_parent->fr_layout == FR_ROW && frp->fr_prev != NULL) { + frame_add_vsep(frp->fr_prev); + } + + // Statuslines or horizontal separators above may have been lost. Restore them. + if (frp->fr_parent->fr_layout == FR_COL && frp->fr_prev != NULL) { + if (global_stl_height() == 0 && wp->w_status_height == 0) { + frame_add_statusline(frp->fr_prev); + } else if (wp->w_hsep_height == 0) { + frame_add_hsep(frp->fr_prev); } } - return wp; + // Restore the lost room that was redistributed to the altframe. + if (dir == 'v') { + frame_new_height(unflat_altfr, unflat_altfr->fr_height - frp->fr_height, + unflat_altfr == frp->fr_next, false); + } else if (dir == 'h') { + frame_new_width(unflat_altfr, unflat_altfr->fr_width - frp->fr_width, + unflat_altfr == frp->fr_next, false); + } } /// If 'splitbelow' or 'splitright' is set, the space goes above or to the left @@ -7248,23 +7303,23 @@ void reset_lnums(void) void make_snapshot(int idx) { clear_snapshot(curtab, idx); - make_snapshot_rec(topframe, &curtab->tp_snapshot[idx], false); + make_snapshot_rec(topframe, &curtab->tp_snapshot[idx]); } -static void make_snapshot_rec(frame_T *fr, frame_T **frp, bool snap_wins) +static void make_snapshot_rec(frame_T *fr, frame_T **frp) { *frp = xcalloc(1, sizeof(frame_T)); (*frp)->fr_layout = fr->fr_layout; (*frp)->fr_width = fr->fr_width; (*frp)->fr_height = fr->fr_height; if (fr->fr_next != NULL) { - make_snapshot_rec(fr->fr_next, &((*frp)->fr_next), snap_wins); + make_snapshot_rec(fr->fr_next, &((*frp)->fr_next)); } if (fr->fr_child != NULL) { - make_snapshot_rec(fr->fr_child, &((*frp)->fr_child), snap_wins); + make_snapshot_rec(fr->fr_child, &((*frp)->fr_child)); } - if (fr->fr_layout == FR_LEAF && (snap_wins || fr->fr_win == curwin)) { - (*frp)->fr_win = fr->fr_win; + if (fr->fr_layout == FR_LEAF && fr->fr_win == curwin) { + (*frp)->fr_win = curwin; } } @@ -7381,80 +7436,6 @@ static win_T *restore_snapshot_rec(frame_T *sn, frame_T *fr) return wp; } -/// Return a snapshot of all frames in the current tabpage and which windows are -/// in them. -/// Use clear_snapshot_rec to free the snapshot. -static frame_T *make_full_snapshot(void) -{ - frame_T *frp; - make_snapshot_rec(topframe, &frp, true); - return frp; -} - -/// Restore all frames in the full snapshot "sn" for the current tabpage. -/// Caller must ensure that the screen size didn't change, no windows with frames -/// in the snapshot were freed, and windows with frames not in the snapshot are -/// removed from their frames! -/// Doesn't restore changed window vertical separators. -/// Frees the old frames. Don't call clear_snapshot_rec on "sn" afterwards! -static void restore_full_snapshot(frame_T *sn) -{ - if (sn == NULL) { - return; - } - - clear_snapshot_rec(topframe); - restore_full_snapshot_rec(sn); - curtab->tp_topframe = topframe = sn; - last_status(false); - - // If the amount of space available changed, first try setting the sizes of - // windows with 'winfix{width,height}'. If that doesn't result in the right - // size, forget about that option. - if (topframe->fr_width != Columns) { - frame_new_width(topframe, Columns, false, true); - if (!frame_check_width(topframe, Columns)) { - frame_new_width(topframe, Columns, false, false); - } - } - if (topframe->fr_height != ROWS_AVAIL) { - frame_new_height(topframe, (int)ROWS_AVAIL, false, true); - if (!frame_check_height(topframe, (int)ROWS_AVAIL)) { - frame_new_height(topframe, (int)ROWS_AVAIL, false, false); - } - } - - win_comp_pos(); -} - -static void restore_full_snapshot_rec(frame_T *sn) -{ - if (sn == NULL) { - return; - } - - if (sn->fr_child != NULL) { - sn->fr_child->fr_parent = sn; - } - if (sn->fr_next != NULL) { - sn->fr_next->fr_parent = sn->fr_parent; - sn->fr_next->fr_prev = sn; - } - if (sn->fr_win != NULL) { - sn->fr_win->w_frame = sn; - // Assume for now that all windows have statuslines, so last_status in restore_full_snapshot - // doesn't resize frames to fit any missing statuslines. - sn->fr_win->w_status_height = STATUS_HEIGHT; - sn->fr_win->w_hsep_height = 0; - - // Resize window to fit the frame. - frame_new_height(sn, sn->fr_height, false, false); - frame_new_width(sn, sn->fr_width, false, false); - } - restore_full_snapshot_rec(sn->fr_child); - restore_full_snapshot_rec(sn->fr_next); -} - /// Check that "topfrp" and its children are at the right height. /// /// @param topfrp top frame pointer diff --git a/src/nvim/winfloat.c b/src/nvim/winfloat.c index 8fe0315230..cddc4ed9dd 100644 --- a/src/nvim/winfloat.c +++ b/src/nvim/winfloat.c @@ -57,7 +57,7 @@ win_T *win_new_float(win_T *wp, bool last, WinConfig fconfig, Error *err) return NULL; } int dir; - winframe_remove(wp, &dir, NULL); + winframe_remove(wp, &dir, NULL, NULL); XFREE_CLEAR(wp->w_frame); win_comp_pos(); // recompute window positions win_remove(wp, NULL); diff --git a/test/functional/ui/statusline_spec.lua b/test/functional/ui/statusline_spec.lua index fee4b64d44..000e2b1b04 100644 --- a/test/functional/ui/statusline_spec.lua +++ b/test/functional/ui/statusline_spec.lua @@ -11,6 +11,7 @@ local exec = helpers.exec local exec_lua = helpers.exec_lua local eval = helpers.eval local sleep = vim.uv.sleep +local pcall_err = helpers.pcall_err local mousemodels = { 'extend', 'popup', 'popup_setpos' } @@ -474,6 +475,25 @@ describe('global statusline', function() | ]]) end) + + it('horizontal separators unchanged when failing to split-move window', function() + exec([[ + botright split + let &winwidth = &columns + let &winminwidth = &columns + ]]) + eq('Vim(wincmd):E36: Not enough room', pcall_err(command, 'wincmd L')) + command('mode') + screen:expect([[ + | + {1:~ }|*5 + ────────────────────────────────────────────────────────────| + ^ | + {1:~ }|*6 + {2:[No Name] 0,0-1 All}| + | + ]]) + end) end) it('statusline does not crash if it has Arabic characters #19447', function() -- cgit From 01b27410a347b90820d4255061944c31d20b8f33 Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Sun, 25 Feb 2024 01:11:40 +0000 Subject: vim-patch:9.1.0119: can move away from cmdwin using win_splitmove() Problem: can switch windows while textlocked via f_win_gotoid and f_win_splitmove (which also allows switching in the cmdwin). Solution: Check text_or_buf_locked in f_win_splitmove() (Sean Dewar) While at it, call text_or_buf_locked() in f_win_gotoid() instead of testing for cmdwin_type() (which text_buf_locked() does and in addition will also verify that the buffer is not locked). https://github.com/vim/vim/commit/f865895c874b0936b0563ebfef7490aac8cb8a1f --- src/nvim/eval/window.c | 6 +++--- test/old/testdir/test_window_cmd.vim | 28 ++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/nvim/eval/window.c b/src/nvim/eval/window.c index 17b8b01963..d20fc3f2f2 100644 --- a/src/nvim/eval/window.c +++ b/src/nvim/eval/window.c @@ -14,6 +14,7 @@ #include "nvim/eval/typval.h" #include "nvim/eval/typval_defs.h" #include "nvim/eval/window.h" +#include "nvim/ex_getln.h" #include "nvim/garray.h" #include "nvim/garray_defs.h" #include "nvim/gettext_defs.h" @@ -584,8 +585,7 @@ void f_win_gotoid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int id = (int)tv_get_number(&argvars[0]); - if (cmdwin_type != 0) { - emsg(_(e_cmdwin)); + if (text_or_buf_locked()) { return; } FOR_ALL_TAB_WINDOWS(tp, wp) { @@ -697,7 +697,7 @@ void f_win_splitmove(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } // Check if we can split the target before we bother switching windows. - if (is_aucmd_win(wp) || check_split_disallowed(targetwin) == FAIL) { + if (is_aucmd_win(wp) || text_or_buf_locked() || check_split_disallowed(targetwin) == FAIL) { return; } diff --git a/test/old/testdir/test_window_cmd.vim b/test/old/testdir/test_window_cmd.vim index fc379c198d..1f2eb2b624 100644 --- a/test/old/testdir/test_window_cmd.vim +++ b/test/old/testdir/test_window_cmd.vim @@ -2177,4 +2177,32 @@ func Test_splitmove_autocmd_window_no_room() %bw! endfunc +func Test_win_gotoid_splitmove_textlock_cmdwin() + call setline(1, 'foo') + new + let curwin = win_getid() + call setline(1, 'bar') + + set debug+=throw indentexpr=win_gotoid(win_getid(winnr('#'))) + call assert_fails('normal! ==', 'E565:') + call assert_equal(curwin, win_getid()) + + set indentexpr=win_splitmove(winnr('#'),winnr()) + call assert_fails('normal! ==', 'E565:') + call assert_equal(curwin, win_getid()) + + %bw! + set debug-=throw indentexpr& + + call feedkeys('q:' + \ .. ":call assert_fails('call win_splitmove(winnr(''#''), winnr())', 'E11:')\" + \ .. ":call assert_equal('command', win_gettype())\" + \ .. ":call assert_equal('', win_gettype(winnr('#')))\", 'ntx') + + call feedkeys('q:' + \ .. ":call assert_fails('call win_gotoid(win_getid(winnr(''#'')))', 'E11:')\" + \ .. ":call assert_equal('command', win_gettype())\" + \ .. ":call assert_equal('', win_gettype(winnr('#')))\", 'ntx') +endfunc + " vim: shiftwidth=2 sts=2 expandtab -- cgit From b2245307f2acfd7b62cf5d0c5b199c87c2d37b23 Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Sun, 25 Feb 2024 01:15:30 +0000 Subject: vim-patch:9.1.0121: Infinite loop or signed overflow with 'smoothscroll' Problem: infinite loop in win_update with 'smoothscroll' set when window width is equal to textoff, or signed integer overflow if smaller. Solution: don't revalidate wp->w_skipcol in that case, as no buffer text is being shown. (Sean Dewar) https://github.com/vim/vim/commit/02fcae02a926e4e8379d77fb716da4202029882d Test_window_split_no_room changes were already cherry-picked earlier. --- src/nvim/drawscreen.c | 2 +- test/old/testdir/test_scroll_opt.vim | 38 ++++++++++++++++++++++++++++++++++++ test/old/testdir/test_window_cmd.vim | 21 ++++---------------- 3 files changed, 43 insertions(+), 18 deletions(-) diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c index 1626e46cf6..402f7fa428 100644 --- a/src/nvim/drawscreen.c +++ b/src/nvim/drawscreen.c @@ -1513,7 +1513,7 @@ static void win_update(win_T *wp) // Make sure skipcol is valid, it depends on various options and the window // width. - if (wp->w_skipcol > 0) { + if (wp->w_skipcol > 0 && wp->w_width_inner > win_col_off(wp)) { int w = 0; int width1 = wp->w_width_inner - win_col_off(wp); int width2 = width1 + win_col_off2(wp); diff --git a/test/old/testdir/test_scroll_opt.vim b/test/old/testdir/test_scroll_opt.vim index a1987ed3c9..8130f7a1ac 100644 --- a/test/old/testdir/test_scroll_opt.vim +++ b/test/old/testdir/test_scroll_opt.vim @@ -963,4 +963,42 @@ func Test_smoothscroll_insert_bottom() call StopVimInTerminal(buf) endfunc +func Test_smoothscroll_in_zero_width_window() + set cpo+=n number smoothscroll + set winwidth=99999 winminwidth=0 + + vsplit + call assert_equal(0, winwidth(winnr('#'))) + call win_execute(win_getid(winnr('#')), "norm! \") + + only! + set winwidth& winminwidth& + set cpo-=n nonumber nosmoothscroll +endfunc + +func Test_smoothscroll_textoff_small_winwidth() + set smoothscroll number + call setline(1, 'llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch') + vsplit + + let textoff = getwininfo(win_getid())[0].textoff + execute 'vertical resize' textoff + 1 + redraw + call assert_equal(0, winsaveview().skipcol) + execute "normal! 0\" + redraw + call assert_equal(1, winsaveview().skipcol) + execute 'vertical resize' textoff - 1 + " This caused a signed integer overflow. + redraw + call assert_equal(1, winsaveview().skipcol) + execute 'vertical resize' textoff + " This caused an infinite loop. + redraw + call assert_equal(1, winsaveview().skipcol) + + %bw! + set smoothscroll& number& +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_window_cmd.vim b/test/old/testdir/test_window_cmd.vim index 1f2eb2b624..02fa3ac407 100644 --- a/test/old/testdir/test_window_cmd.vim +++ b/test/old/testdir/test_window_cmd.vim @@ -2105,19 +2105,6 @@ func Test_new_help_window_on_error() call assert_equal(expand(""), "'mod'") endfunc -func Test_smoothscroll_in_zero_width_window() - set cpo+=n number smoothscroll - set winwidth=99999 winminwidth=0 - - vsplit - call assert_equal(0, winwidth(winnr('#'))) - call win_execute(win_getid(winnr('#')), "norm! \") - - only! - set winwidth& winminwidth& - set cpo-=n nonumber nosmoothscroll -endfunc - func Test_splitmove_flatten_frame() split vsplit @@ -2131,7 +2118,7 @@ func Test_splitmove_flatten_frame() only! endfunc -func Test_splitmove_autocmd_window_no_room() +func Test_autocmd_window_force_room() " Open as many windows as possible while v:true try @@ -2158,7 +2145,7 @@ func Test_splitmove_autocmd_window_no_room() edit unload me enew bunload! unload\ me - augroup SplitMoveAucmdWin + augroup AucmdWinForceRoom au! au BufEnter * ++once let s:triggered = v:true \| call assert_equal('autocmd', win_gettype()) @@ -2172,8 +2159,8 @@ func Test_splitmove_autocmd_window_no_room() call assert_equal(winrestcmd(), restcmd) unlet! s:triggered - au! SplitMoveAucmdWin - augroup! SplitMoveAucmdWin + au! AucmdWinForceRoom + augroup! AucmdWinForceRoom %bw! endfunc -- cgit From e3d4dfb6c3fcd22205f6843b96f9a043871113ce Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Sun, 25 Feb 2024 01:22:55 +0000 Subject: vim-patch:9.1.0128: win_gotoid() may abort even when not switching a window Problem: win_gotoid() checks for textlock and other things when switching to a window that is already current (after v9.1.0119) Solution: return early with success when attempting to switch to curwin (Sean Dewar) https://github.com/vim/vim/commit/2a65e739447949a7aee966ce8a3b75521b2a79ea --- src/nvim/eval/window.c | 5 +++++ test/old/testdir/test_window_cmd.vim | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/src/nvim/eval/window.c b/src/nvim/eval/window.c index d20fc3f2f2..c2b9574579 100644 --- a/src/nvim/eval/window.c +++ b/src/nvim/eval/window.c @@ -584,6 +584,11 @@ void f_win_getid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) void f_win_gotoid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int id = (int)tv_get_number(&argvars[0]); + if (curwin->handle == id) { + // Nothing to do. + rettv->vval.v_number = 1; + return; + } if (text_or_buf_locked()) { return; diff --git a/test/old/testdir/test_window_cmd.vim b/test/old/testdir/test_window_cmd.vim index 02fa3ac407..7ea556b11f 100644 --- a/test/old/testdir/test_window_cmd.vim +++ b/test/old/testdir/test_window_cmd.vim @@ -2173,6 +2173,10 @@ func Test_win_gotoid_splitmove_textlock_cmdwin() set debug+=throw indentexpr=win_gotoid(win_getid(winnr('#'))) call assert_fails('normal! ==', 'E565:') call assert_equal(curwin, win_getid()) + " No error if attempting to switch to curwin; nothing happens. + set indentexpr=assert_equal(1,win_gotoid(win_getid())) + normal! == + call assert_equal(curwin, win_getid()) set indentexpr=win_splitmove(winnr('#'),winnr()) call assert_fails('normal! ==', 'E565:') @@ -2188,6 +2192,8 @@ func Test_win_gotoid_splitmove_textlock_cmdwin() call feedkeys('q:' \ .. ":call assert_fails('call win_gotoid(win_getid(winnr(''#'')))', 'E11:')\" + "\ No error if attempting to switch to curwin; nothing happens. + \ .. ":call assert_equal(1, win_gotoid(win_getid()))\" \ .. ":call assert_equal('command', win_gettype())\" \ .. ":call assert_equal('', win_gettype(winnr('#')))\", 'ntx') endfunc -- cgit From 832bc5c169d8b339ef139ef0bdcefb2e72864e6e Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Sun, 25 Feb 2024 01:30:37 +0000 Subject: vim-patch:9.1.0130: [security]: UAF if win_split_ins autocommands delete "wp" Problem: heap-use-after-free in win_splitmove if Enter/Leave autocommands from win_split_ins immediately closes "wp". Solution: check that "wp" is valid after win_split_ins. (Sean Dewar) https://github.com/vim/vim/commit/abf7030a5c22257f066fa9c4061ad150d5a82577 --- src/nvim/window.c | 4 ++-- test/old/testdir/test_window_cmd.vim | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/nvim/window.c b/src/nvim/window.c index 2d0010ad6c..80cb9ab6a0 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -1948,8 +1948,8 @@ int win_splitmove(win_T *wp, int size, int flags) } // If splitting horizontally, try to preserve height. - // Note that win_split_ins autocommands may have immediately made "wp" floating! - if (size == 0 && !(flags & WSP_VERT) && !wp->w_floating) { + // Note that win_split_ins autocommands may have immediately closed "wp", or made it floating! + if (size == 0 && !(flags & WSP_VERT) && win_valid(wp) && !wp->w_floating) { win_setheight_win(height, wp); if (p_ea) { // Equalize windows. Note that win_split_ins autocommands may have diff --git a/test/old/testdir/test_window_cmd.vim b/test/old/testdir/test_window_cmd.vim index 7ea556b11f..e6d591f8c9 100644 --- a/test/old/testdir/test_window_cmd.vim +++ b/test/old/testdir/test_window_cmd.vim @@ -1150,6 +1150,15 @@ func Test_win_splitmove() call assert_equal(v:true, s:triggered) unlet! s:triggered + split + let close_win = winnr('#') + augroup WinSplitMove + au! + au WinEnter * ++once quit! + augroup END + call win_splitmove(close_win, winnr()) + call assert_equal(0, win_id2win(close_win)) + au! WinSplitMove augroup! WinSplitMove %bw! -- cgit From d942c2b9432d81e4b509519bd48fa886e37e9ca8 Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Mon, 26 Feb 2024 14:51:31 +0000 Subject: fix(api): handle win_split_ins failure properly Problem: nvim_win_set_config does not handle failure in win_split_ins properly yet, which can cause all sorts of issues. Also nvim_open_win and nvim_win_set_config do not set the error message to the one from win_split_ins. Solution: handle failure by undoing winframe_remove, like in win_splitmove. Make sure autocommands from switching to the altwin fire within a valid window, and ensure they don't screw things up. Set the error message to that of win_split_ins, if any. Also change a few other small things, including: - adjust win_append to take a tabpage_T * argument, which is more consistent with win_remove (and also allows us to undo a call to win_remove). - allow winframe_restore to restore window positions. Useful if `wp` was in a different tabpage, as a call to win_comp_pos (which only works for the current tabpage) after winframe_restore should no longer be needed. Though enter_tabpage calls win_comp_pos anyway, this has the advantage of ensuring w_winrow/col remains accurate even before entering the tabpage (useful for stuff like win_screenpos, if used on a window in another tabpage). (This change should probably also be PR'd to Vim later, even though it doesn't use winframe_restore for a `wp` in a different tabpage yet). --- src/nvim/api/win_config.c | 147 ++++++++++------- src/nvim/autocmd.c | 2 +- src/nvim/window.c | 128 ++++++++++----- src/nvim/winfloat.c | 2 +- test/functional/api/window_spec.lua | 319 ++++++++++++++++++++++++++++++++++-- 5 files changed, 491 insertions(+), 107 deletions(-) diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 978d8515c8..bb28000719 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -259,18 +259,20 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Err } int flags = win_split_flags(fconfig.split, parent == NULL) | WSP_NOENTER; - if (parent == NULL) { - wp = win_split_ins(0, flags, NULL, 0, NULL); - } else { - tp = win_find_tabpage(parent); - switchwin_T switchwin; - // `parent` is valid in `tp`, so switch_win should not fail. - const int result = switch_win(&switchwin, parent, tp, true); - assert(result == OK); - (void)result; - wp = win_split_ins(0, flags, NULL, 0, NULL); - restore_win(&switchwin, true); - } + TRY_WRAP(err, { + if (parent == NULL || parent == curwin) { + wp = win_split_ins(0, flags, NULL, 0, NULL); + } else { + tp = win_find_tabpage(parent); + switchwin_T switchwin; + // `parent` is valid in `tp`, so switch_win should not fail. + const int result = switch_win(&switchwin, parent, tp, true); + assert(result == OK); + (void)result; + wp = win_split_ins(0, flags, NULL, 0, NULL); + restore_win(&switchwin, true); + } + }); if (wp) { wp->w_config = fconfig; } @@ -278,7 +280,9 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Err wp = win_new_float(NULL, false, fconfig, err); } if (!wp) { - api_set_error(err, kErrorTypeException, "Failed to create window"); + if (!ERROR_SET(err)) { + api_set_error(err, kErrorTypeException, "Failed to create window"); + } return 0; } @@ -448,17 +452,47 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) return; // error already set } - if (was_split) { - win_T *new_curwin = NULL; + bool to_split_ok = false; + // If we are moving curwin to another tabpage, switch windows *before* we remove it from the + // window list or remove its frame (if non-floating), so it's valid for autocommands. + const bool curwin_moving_tp + = win == curwin && parent != NULL && win_tp != win_find_tabpage(parent); + if (curwin_moving_tp) { + if (was_split) { + int dir; + win_goto(winframe_find_altwin(win, &dir, NULL, NULL)); + } else { + win_goto(win_valid(prevwin) && prevwin != win ? prevwin : firstwin); + } + // Autocommands may have been a real nuisance and messed things up... + if (curwin == win) { + api_set_error(err, kErrorTypeException, "Failed to switch away from window %d", + win->handle); + return; + } + win_tp = win_find_tabpage(win); + if (!win_tp || !win_valid_any_tab(parent)) { + api_set_error(err, kErrorTypeException, "Windows to split were closed"); + goto restore_curwin; + } + if (was_split == win->w_floating || parent->w_floating) { + api_set_error(err, kErrorTypeException, "Floating state of windows to split changed"); + goto restore_curwin; + } + } + + int dir = 0; + frame_T *unflat_altfr = NULL; + if (was_split) { // If the window is the last in the tabpage or `fconfig.win` is // a handle to itself, we can't split it. if (win->w_frame->fr_parent == NULL) { // FIXME(willothy): if the window is the last in the tabpage but there is another tabpage // and the target window is in that other tabpage, should we move the window to that // tabpage and close the previous one, or just error? - api_set_error(err, kErrorTypeValidation, "Cannot move last window"); - return; + api_set_error(err, kErrorTypeException, "Cannot move last window"); + goto restore_curwin; } else if (parent != NULL && parent->handle == win->handle) { int n_frames = 0; for (frame_T *fr = win->w_frame->fr_parent->fr_child; fr != NULL; fr = fr->fr_next) { @@ -494,64 +528,67 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) } // If the frame doesn't have a parent, the old frame // was the root frame and we need to create a top-level split. - int dir; - new_curwin = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp, NULL); + winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp, &unflat_altfr); } else if (n_frames == 2) { // There are two windows in the frame, we can just rotate it. - int dir; - neighbor = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp, NULL); - new_curwin = neighbor; + neighbor = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp, &unflat_altfr); } else { // There is only one window in the frame, we can't split it. - api_set_error(err, kErrorTypeValidation, "Cannot split window into itself"); - return; + api_set_error(err, kErrorTypeException, "Cannot split window into itself"); + goto restore_curwin; } - // Set the parent to whatever the correct - // neighbor window was determined to be. + // Set the parent to whatever the correct neighbor window was determined to be. parent = neighbor; } else { - int dir; - new_curwin = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp, NULL); - } - win_remove(win, win_tp == curtab ? NULL : win_tp); - // move to neighboring window if we're moving the current window to a new tabpage - if (curwin == win && parent != NULL && new_curwin != NULL - && win_tp != win_find_tabpage(parent)) { - win_enter(new_curwin, true); - if (!win_valid_any_tab(parent)) { - // win_enter autocommands closed the `parent` to split from. - api_set_error(err, kErrorTypeException, "Window to split was closed"); - return; - } + winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp, &unflat_altfr); } - } else { - win_remove(win, win_tp == curtab ? NULL : win_tp); } + win_remove(win, win_tp == curtab ? NULL : win_tp); int flags = win_split_flags(fconfig.split, parent == NULL) | WSP_NOENTER; + TRY_WRAP(err, { + const bool need_switch = parent != NULL && parent != curwin; + switchwin_T switchwin; + if (need_switch) { + // `parent` is valid in its tabpage, so switch_win should not fail. + const int result = switch_win(&switchwin, parent, win_find_tabpage(parent), true); + (void)result; + assert(result == OK); + } + to_split_ok = win_split_ins(0, flags, win, 0, unflat_altfr) != NULL; + if (!to_split_ok) { + // Restore `win` to the window list now, so it's valid for restore_win (if used). + win_append(win->w_prev, win, win_tp == curtab ? NULL : win_tp); + } + if (need_switch) { + restore_win(&switchwin, true); + } + }); + if (!to_split_ok) { + if (was_split) { + // win_split_ins doesn't change sizes or layout if it fails to insert an existing window, so + // just undo winframe_remove. + winframe_restore(win, dir, unflat_altfr); + } + if (!ERROR_SET(err)) { + api_set_error(err, kErrorTypeException, "Failed to move window %d into split", win->handle); + } - if (parent == NULL) { - if (!win_split_ins(0, flags, win, 0, NULL)) { - // TODO(willothy): What should this error message say? - api_set_error(err, kErrorTypeException, "Failed to split window"); - return; +restore_curwin: + // If `win` was the original curwin, and autocommands didn't move it outside of curtab, be a + // good citizen and try to return to it. + if (curwin_moving_tp && win_valid(win)) { + win_goto(win); } - } else { - switchwin_T switchwin; - // `parent` is valid in its tabpage, so switch_win should not fail. - const int result = switch_win(&switchwin, parent, win_find_tabpage(parent), true); - (void)result; - assert(result == OK); - win_split_ins(0, flags, win, 0, NULL); - restore_win(&switchwin, true); + return; } + if (HAS_KEY_X(config, width)) { win_setwidth_win(fconfig.width, win); } if (HAS_KEY_X(config, height)) { win_setheight_win(fconfig.height, win); } - return; } else { win_config_float(win, fconfig); win->w_pos_changed = true; diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index 3f93906942..652b6ba74e 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -1333,7 +1333,7 @@ void aucmd_prepbuf(aco_save_T *aco, buf_T *buf) block_autocmds(); // We don't want BufEnter/WinEnter autocommands. if (need_append) { - win_append(lastwin, auc_win); + win_append(lastwin, auc_win, NULL); pmap_put(int)(&window_handles, auc_win->handle, auc_win); win_config_float(auc_win, auc_win->w_config); } diff --git a/src/nvim/window.c b/src/nvim/window.c index 80cb9ab6a0..b55dd79260 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -1218,13 +1218,13 @@ win_T *win_split_ins(int size, int flags, win_T *new_wp, int dir, frame_T *to_fl if (new_wp == NULL) { wp = win_alloc(oldwin, false); } else { - win_append(oldwin, wp); + win_append(oldwin, wp, NULL); } } else { if (new_wp == NULL) { wp = win_alloc(oldwin->w_prev, false); } else { - win_append(oldwin->w_prev, wp); + win_append(oldwin->w_prev, wp, NULL); } } @@ -1783,13 +1783,13 @@ static void win_exchange(int Prenum) if (wp->w_prev != curwin) { win_remove(curwin, NULL); frame_remove(curwin->w_frame); - win_append(wp->w_prev, curwin); + win_append(wp->w_prev, curwin, NULL); frame_insert(frp, curwin->w_frame); } if (wp != wp2) { win_remove(wp, NULL); frame_remove(wp->w_frame); - win_append(wp2, wp); + win_append(wp2, wp, NULL); if (frp2 == NULL) { frame_insert(wp->w_frame->fr_parent->fr_child, wp->w_frame); } else { @@ -1863,7 +1863,7 @@ static void win_rotate(bool upwards, int count) // find last frame and append removed window/frame after it for (; frp->fr_next != NULL; frp = frp->fr_next) {} - win_append(frp->fr_win, wp1); + win_append(frp->fr_win, wp1, NULL); frame_append(frp, wp1->w_frame); wp2 = frp->fr_win; // previously last window @@ -1878,7 +1878,7 @@ static void win_rotate(bool upwards, int count) assert(frp->fr_parent->fr_child); // append the removed window/frame before the first in the list - win_append(frp->fr_parent->fr_child->fr_win->w_prev, wp1); + win_append(frp->fr_parent->fr_child->fr_win->w_prev, wp1, NULL); frame_insert(frp->fr_parent->fr_child, frp); } @@ -1937,7 +1937,7 @@ int win_splitmove(win_T *wp, int size, int flags) // Split a window on the desired side and put "wp" there. if (win_split_ins(size, flags, wp, dir, unflat_altfr) == NULL) { - win_append(wp->w_prev, wp); + win_append(wp->w_prev, wp, NULL); if (!wp->w_floating) { // win_split_ins doesn't change sizes or layout if it fails to insert an // existing window, so just undo winframe_remove. @@ -2016,7 +2016,7 @@ void win_move_after(win_T *win1, win_T *win2) } win_remove(win1, NULL); frame_remove(win1->w_frame); - win_append(win2, win1); + win_append(win2, win1, NULL); frame_append(win2->w_frame, win1->w_frame); win_comp_pos(); // recompute w_winrow for all windows @@ -3151,6 +3151,55 @@ void win_free_all(void) /// @return a pointer to the window that got the freed up space. win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp, frame_T **unflat_altfr) FUNC_ATTR_NONNULL_ARG(1, 2) +{ + frame_T *altfr; + win_T *wp = winframe_find_altwin(win, dirp, tp, &altfr); + if (wp == NULL) { + return NULL; + } + + frame_T *frp_close = win->w_frame; + // Remove this frame from the list of frames. + frame_remove(frp_close); + + if (*dirp == 'v') { + frame_new_height(altfr, altfr->fr_height + frp_close->fr_height, + altfr == frp_close->fr_next, false); + } else { + assert(*dirp == 'h'); + frame_new_width(altfr, altfr->fr_width + frp_close->fr_width, + altfr == frp_close->fr_next, false); + } + + // If rows/columns go to a window below/right its positions need to be + // updated. Can only be done after the sizes have been updated. + if (altfr == frp_close->fr_next) { + int row = win->w_winrow; + int col = win->w_wincol; + + frame_comp_pos(altfr, &row, &col); + } + + if (unflat_altfr == NULL) { + frame_flatten(altfr); + } else { + *unflat_altfr = altfr; + } + + return wp; +} + +/// Find the window that will get the freed space from a call to `winframe_remove`. +/// Makes no changes to the window layout. +/// +/// @param dirp set to 'v' or 'h' for the direction where "altfr" will be resized +/// to fill the space +/// @param tp tab page "win" is in, NULL for current +/// @param altfr if not NULL, set to pointer of frame that will get the space +/// +/// @return a pointer to the window that will get the freed up space. +win_T *winframe_find_altwin(win_T *win, int *dirp, tabpage_T *tp, frame_T **altfr) + FUNC_ATTR_NONNULL_ARG(1, 2) { assert(tp == NULL || tp != curtab); @@ -3161,13 +3210,10 @@ win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp, frame_T **unflat_al frame_T *frp_close = win->w_frame; - // Remove the window from its frame. + // Find the window and frame that gets the space. frame_T *frp2 = win_altframe(win, tp); win_T *wp = frame2win(frp2); - // Remove this frame from the list of frames. - frame_remove(frp_close); - if (frp_close->fr_parent->fr_layout == FR_COL) { // When 'winfixheight' is set, try to find another frame in the column // (as close to the closed frame as possible) to distribute the height @@ -3194,8 +3240,6 @@ win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp, frame_T **unflat_al } } } - frame_new_height(frp2, frp2->fr_height + frp_close->fr_height, - frp2 == frp_close->fr_next, false); *dirp = 'v'; } else { // When 'winfixwidth' is set, try to find another frame in the column @@ -3223,24 +3267,12 @@ win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp, frame_T **unflat_al } } } - frame_new_width(frp2, frp2->fr_width + frp_close->fr_width, - frp2 == frp_close->fr_next, false); *dirp = 'h'; } - // If rows/columns go to a window below/right its positions need to be - // updated. Can only be done after the sizes have been updated. - if (frp2 == frp_close->fr_next) { - int row = win->w_winrow; - int col = win->w_wincol; - - frame_comp_pos(frp2, &row, &col); - } - - if (unflat_altfr == NULL) { - frame_flatten(frp2); - } else { - *unflat_altfr = frp2; + assert(wp != win && frp2 != frp_close); + if (altfr != NULL) { + *altfr = frp2; } return wp; @@ -3305,9 +3337,10 @@ static void frame_flatten(frame_T *frp) } /// Undo changes from a prior call to winframe_remove, also restoring lost -/// vertical separators and statuslines. +/// vertical separators and statuslines, and changed window positions for +/// windows within "unflat_altfr". /// Caller must ensure no other changes were made to the layout or window sizes! -static void winframe_restore(win_T *wp, int dir, frame_T *unflat_altfr) +void winframe_restore(win_T *wp, int dir, frame_T *unflat_altfr) FUNC_ATTR_NONNULL_ALL { frame_T *frp = wp->w_frame; @@ -3333,13 +3366,24 @@ static void winframe_restore(win_T *wp, int dir, frame_T *unflat_altfr) } } + int row = wp->w_winrow; + int col = wp->w_wincol; + // Restore the lost room that was redistributed to the altframe. if (dir == 'v') { frame_new_height(unflat_altfr, unflat_altfr->fr_height - frp->fr_height, unflat_altfr == frp->fr_next, false); + row += frp->fr_height; } else if (dir == 'h') { frame_new_width(unflat_altfr, unflat_altfr->fr_width - frp->fr_width, unflat_altfr == frp->fr_next, false); + col += frp->fr_width; + } + + // If rows/columns went to a window below/right, its positions need to be + // restored. Can only be done after the sizes have been updated. + if (unflat_altfr == frp->fr_next) { + frame_comp_pos(unflat_altfr, &row, &col); } } @@ -4445,7 +4489,7 @@ static void tabpage_check_windows(tabpage_T *old_curtab) if (wp->w_floating) { if (wp->w_config.external) { win_remove(wp, old_curtab); - win_append(lastwin_nofloating(), wp); + win_append(lastwin_nofloating(), wp, NULL); } else { ui_comp_remove_grid(&wp->w_grid_alloc); } @@ -5085,7 +5129,7 @@ win_T *win_alloc(win_T *after, bool hidden) block_autocmds(); // link the window in the window list if (!hidden) { - win_append(after, new_wp); + win_append(after, new_wp, NULL); } new_wp->w_wincol = 0; @@ -5255,21 +5299,29 @@ void win_free_grid(win_T *wp, bool reinit) } } -// Append window "wp" in the window list after window "after". -void win_append(win_T *after, win_T *wp) +/// Append window "wp" in the window list after window "after". +/// +/// @param tp tab page "win" (and "after", if not NULL) is in, NULL for current +void win_append(win_T *after, win_T *wp, tabpage_T *tp) + FUNC_ATTR_NONNULL_ARG(2) { + assert(tp == NULL || tp != curtab); + + win_T **first = tp == NULL ? &firstwin : &tp->tp_firstwin; + win_T **last = tp == NULL ? &lastwin : &tp->tp_lastwin; + // after NULL is in front of the first - win_T *before = after == NULL ? firstwin : after->w_next; + win_T *before = after == NULL ? *first : after->w_next; wp->w_next = before; wp->w_prev = after; if (after == NULL) { - firstwin = wp; + *first = wp; } else { after->w_next = wp; } if (before == NULL) { - lastwin = wp; + *last = wp; } else { before->w_prev = wp; } diff --git a/src/nvim/winfloat.c b/src/nvim/winfloat.c index cddc4ed9dd..10d8c7ac90 100644 --- a/src/nvim/winfloat.c +++ b/src/nvim/winfloat.c @@ -61,7 +61,7 @@ win_T *win_new_float(win_T *wp, bool last, WinConfig fconfig, Error *err) XFREE_CLEAR(wp->w_frame); win_comp_pos(); // recompute window positions win_remove(wp, NULL); - win_append(lastwin_nofloating(), wp); + win_append(lastwin_nofloating(), wp, NULL); } wp->w_floating = true; wp->w_status_height = 0; diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index 6e20c81fc2..abe1ca9344 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -1654,6 +1654,18 @@ describe('API/win', function() api.nvim_open_win(new_buf, false, { split = 'left' }) eq('foobarbaz', eval('triggered')) end) + + it('sets error when no room', function() + matches('E36: Not enough room$', pcall_err(command, 'execute "split|"->repeat(&lines)')) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_open_win, 0, true, { split = 'above', win = 0 }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_open_win, 0, true, { split = 'below', win = 0 }) + ) + end) end) describe('set_config', function() @@ -1965,23 +1977,89 @@ describe('API/win', function() it('closing new curwin when moving window to other tabpage works', function() command('split | tabnew') - local w = api.nvim_get_current_win() - local t = api.nvim_get_current_tabpage() - command('tabfirst | autocmd WinEnter * quit') - api.nvim_win_set_config(0, { win = w, split = 'left' }) - -- New tabpage is now the only one, as WinEnter closed the new curwin in the original. - eq(t, api.nvim_get_current_tabpage()) - eq({ t }, api.nvim_list_tabpages()) + local t2_win = api.nvim_get_current_win() + command('tabfirst | autocmd WinEnter * ++once quit') + local t1_move_win = api.nvim_get_current_win() + -- win_set_config fails to switch away from "t1_move_win" because the WinEnter autocmd that + -- closed the window we're switched to returns us to "t1_move_win", as it filled the space. + eq( + 'Failed to switch away from window ' .. t1_move_win, + pcall_err(api.nvim_win_set_config, t1_move_win, { win = t2_win, split = 'left' }) + ) + eq(t1_move_win, api.nvim_get_current_win()) + + command('split | split | autocmd WinEnter * ++once quit') + t1_move_win = api.nvim_get_current_win() + -- In this case, we closed the window that we got switched to, but doing so didn't switch us + -- back to "t1_move_win", which is fine. + api.nvim_win_set_config(t1_move_win, { win = t2_win, split = 'left' }) + neq(t1_move_win, api.nvim_get_current_win()) end) - it('closing split parent when moving window to other tabpage aborts', function() + it('messing with "win" or "parent" when moving "win" to other tabpage', function() command('split | tabnew') - local w = api.nvim_get_current_win() - command('tabfirst | autocmd WinEnter * call nvim_win_close(' .. w .. ', 1)') + local t2 = api.nvim_get_current_tabpage() + local t2_win1 = api.nvim_get_current_win() + command('split') + local t2_win2 = api.nvim_get_current_win() + command('split') + local t2_win3 = api.nvim_get_current_win() + + command('tabfirst | autocmd WinEnter * ++once call nvim_win_close(' .. t2_win1 .. ', 1)') + local cur_win = api.nvim_get_current_win() + eq( + 'Windows to split were closed', + pcall_err(api.nvim_win_set_config, 0, { win = t2_win1, split = 'left' }) + ) + eq(cur_win, api.nvim_get_current_win()) + + command('split | autocmd WinLeave * ++once quit!') + cur_win = api.nvim_get_current_win() + eq( + 'Windows to split were closed', + pcall_err(api.nvim_win_set_config, 0, { win = t2_win2, split = 'left' }) + ) + neq(cur_win, api.nvim_get_current_win()) + + exec([[ + split + autocmd WinLeave * ++once + \ call nvim_win_set_config(0, #{relative:'editor', row:0, col:0, width:5, height:5}) + ]]) + cur_win = api.nvim_get_current_win() eq( - 'Window to split was closed', - pcall_err(api.nvim_win_set_config, 0, { win = w, split = 'left' }) + 'Floating state of windows to split changed', + pcall_err(api.nvim_win_set_config, 0, { win = t2_win3, split = 'left' }) ) + eq('editor', api.nvim_win_get_config(0).relative) + eq(cur_win, api.nvim_get_current_win()) + + command('autocmd WinLeave * ++once wincmd J') + cur_win = api.nvim_get_current_win() + eq( + 'Floating state of windows to split changed', + pcall_err(api.nvim_win_set_config, 0, { win = t2_win3, split = 'left' }) + ) + eq('', api.nvim_win_get_config(0).relative) + eq(cur_win, api.nvim_get_current_win()) + + -- Try to make "parent" floating. This should give the same error as before, but because + -- changing a split from another tabpage into a float isn't supported yet, check for that + -- error instead for now. + -- Use ":silent!" to avoid the one second delay from printing the error message. + exec(([[ + autocmd WinLeave * ++once silent! + \ call nvim_win_set_config(%d, #{relative:'editor', row:0, col:0, width:5, height:5}) + ]]):format(t2_win3)) + cur_win = api.nvim_get_current_win() + api.nvim_win_set_config(0, { win = t2_win3, split = 'left' }) + matches( + 'Cannot change window from different tabpage into float$', + api.nvim_get_vvar('errmsg') + ) + -- The error doesn't abort moving the window (or maybe it should, if that's wanted?) + neq(cur_win, api.nvim_get_current_win()) + eq(t2, api.nvim_win_get_tabpage(cur_win)) end) it('expected autocmds when moving window to other tabpage', function() @@ -2031,6 +2109,223 @@ describe('API/win', function() command('autocmd BufHidden * ++once call nvim_win_set_config(' .. w .. ', #{split: "left"})') command('new | quit') end) + + --- Returns a function to get information about the window layout, sizes and positions of a + --- tabpage. + local function define_tp_info_function() + exec_lua([[ + function tp_info(tp) + return { + layout = vim.fn.winlayout(vim.api.nvim_tabpage_get_number(tp)), + pos_sizes = vim.tbl_map( + function(w) + local pos = vim.fn.win_screenpos(w) + return { + row = pos[1], + col = pos[2], + width = vim.fn.winwidth(w), + height = vim.fn.winheight(w) + } + end, + vim.api.nvim_tabpage_list_wins(tp) + ) + } + end + ]]) + + return function(tp) + return exec_lua('return tp_info(...)', tp) + end + end + + it('attempt to move window with no room', function() + -- Fill the 2nd tabpage full of windows until we run out of room. + -- Use &laststatus=0 to ensure restoring missing statuslines doesn't affect things. + command('set laststatus=0 | tabnew') + matches('E36: Not enough room$', pcall_err(command, 'execute "split|"->repeat(&lines)')) + command('vsplit | wincmd | | wincmd p') + local t2 = api.nvim_get_current_tabpage() + local t2_cur_win = api.nvim_get_current_win() + local t2_top_split = fn.win_getid(1) + local t2_bot_split = fn.win_getid(fn.winnr('$')) + local t2_float = api.nvim_open_win( + 0, + false, + { relative = 'editor', row = 0, col = 0, width = 10, height = 10 } + ) + local t2_float_config = api.nvim_win_get_config(t2_float) + local tp_info = define_tp_info_function() + local t2_info = tp_info(t2) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, 0, { win = t2_top_split, split = 'above' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, 0, { win = t2_top_split, split = 'below' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, 0, { win = t2_bot_split, split = 'above' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, 0, { win = t2_bot_split, split = 'below' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, t2_float, { win = t2_top_split, split = 'above' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, t2_float, { win = t2_top_split, split = 'below' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, t2_float, { win = t2_bot_split, split = 'above' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, t2_float, { win = t2_bot_split, split = 'below' }) + ) + eq(t2_cur_win, api.nvim_get_current_win()) + eq(t2_info, tp_info(t2)) + eq(t2_float_config, api.nvim_win_get_config(t2_float)) + + -- Try to move windows from the 1st tabpage to the 2nd. + command('tabfirst | split | wincmd _') + local t1 = api.nvim_get_current_tabpage() + local t1_cur_win = api.nvim_get_current_win() + local t1_float = api.nvim_open_win( + 0, + false, + { relative = 'editor', row = 5, col = 3, width = 7, height = 6 } + ) + local t1_float_config = api.nvim_win_get_config(t1_float) + local t1_info = tp_info(t1) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, 0, { win = t2_top_split, split = 'above' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, 0, { win = t2_top_split, split = 'below' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, 0, { win = t2_bot_split, split = 'above' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, 0, { win = t2_bot_split, split = 'below' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, t1_float, { win = t2_top_split, split = 'above' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, t1_float, { win = t2_top_split, split = 'below' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, t1_float, { win = t2_bot_split, split = 'above' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, t1_float, { win = t2_bot_split, split = 'below' }) + ) + eq(t1_cur_win, api.nvim_get_current_win()) + eq(t1_info, tp_info(t1)) + eq(t1_float_config, api.nvim_win_get_config(t1_float)) + end) + + it('attempt to move window from other tabpage with no room', function() + -- Fill up the 1st tabpage with horizontal splits, then create a 2nd with only a few. Go back + -- to the 1st and try to move windows from the 2nd (while it's non-current) to it. Check that + -- window positions and sizes in the 2nd are unchanged. + command('set laststatus=0') + matches('E36: Not enough room$', pcall_err(command, 'execute "split|"->repeat(&lines)')) + + command('tab split') + local t2 = api.nvim_get_current_tabpage() + local t2_top = api.nvim_get_current_win() + command('belowright split') + local t2_mid_left = api.nvim_get_current_win() + command('belowright vsplit') + local t2_mid_right = api.nvim_get_current_win() + command('split | wincmd J') + local t2_bot = api.nvim_get_current_win() + local tp_info = define_tp_info_function() + local t2_info = tp_info(t2) + eq({ + 'col', + { + { 'leaf', t2_top }, + { + 'row', + { + { 'leaf', t2_mid_left }, + { 'leaf', t2_mid_right }, + }, + }, + { 'leaf', t2_bot }, + }, + }, t2_info.layout) + + local function try_move_t2_wins_to_t1() + for _, w in ipairs({ t2_bot, t2_mid_left, t2_mid_right, t2_top }) do + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, w, { win = 0, split = 'below' }) + ) + eq(t2_info, tp_info(t2)) + end + end + command('tabfirst') + try_move_t2_wins_to_t1() + -- Go to the 2nd tabpage to ensure nothing changes after win_comp_pos, last_status, .etc. + -- from enter_tabpage. + command('tabnext') + eq(t2_info, tp_info(t2)) + + -- Check things are fine with the global statusline too, for good measure. + -- Set it while the 2nd tabpage is current, so last_status runs for it. + command('set laststatus=3') + t2_info = tp_info(t2) + command('tabfirst') + try_move_t2_wins_to_t1() + end) + + it('does not switch window when textlocked or in the cmdwin', function() + command('tabnew') + local t2_win = api.nvim_get_current_win() + command('tabfirst') + feed('q:') + local cur_win = api.nvim_get_current_win() + eq( + 'Failed to switch away from window ' .. cur_win, + pcall_err(api.nvim_win_set_config, 0, { split = 'left', win = t2_win }) + ) + eq( + 'E11: Invalid in command-line window; executes, CTRL-C quits', + api.nvim_get_vvar('errmsg') + ) + eq(cur_win, api.nvim_get_current_win()) + command('quit!') + + exec(([[ + new + call setline(1, 'foo') + setlocal debug=throw indentexpr=nvim_win_set_config(0,#{split:'left',win:%d}) + ]]):format(t2_win)) + cur_win = api.nvim_get_current_win() + matches( + 'E565: Not allowed to change text or change window$', + pcall_err(command, 'normal! ==') + ) + eq(cur_win, api.nvim_get_current_win()) + end) end) describe('get_config', function() -- cgit From e7c262f5553c1c6e1de95bcbdc8cfe7cc9d5e55e Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Tue, 27 Feb 2024 13:25:44 +0000 Subject: fix(api): patch some cmdwin/textlock holes Problem: there are new ways to escape textlock or break the cmdwin in nvim_win_set_config and nvim_tabpage_set_win. Solution: fix them. Use win_goto to check it in nvim_tabpage_set_win and use the try_start/end pattern like with similar functions such as nvim_set_current_win (which uses the existing msg_list, if set). Careful not to use `wp->handle` when printing the window ID in the error message for nvim_tabpage_set_win, as win_goto autocommands may have freed the window. On a related note, I have a feeling some API functions ought to be checking curbuf_locked... --- src/nvim/api/tabpage.c | 6 +++- src/nvim/api/win_config.c | 6 ++++ src/nvim/winfloat.c | 13 ++++++++ test/functional/api/tabpage_spec.lua | 26 +++++++++++++++ test/functional/api/window_spec.lua | 65 ++++++++++++++++++++++++++++++++---- 5 files changed, 109 insertions(+), 7 deletions(-) diff --git a/src/nvim/api/tabpage.c b/src/nvim/api/tabpage.c index 040abb1e3f..56a3f1cf23 100644 --- a/src/nvim/api/tabpage.c +++ b/src/nvim/api/tabpage.c @@ -146,7 +146,11 @@ void nvim_tabpage_set_win(Tabpage tabpage, Window win, Error *err) } if (tp == curtab) { - win_enter(wp, true); + try_start(); + win_goto(wp); + if (!try_end(err) && curwin != wp) { + api_set_error(err, kErrorTypeException, "Failed to switch to window %d", win); + } } else if (tp->tp_curwin != wp) { tp->tp_prevwin = tp->tp_curwin; tp->tp_curwin = wp; diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index bb28000719..dab1e4e80b 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -451,6 +451,12 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) if (!check_split_disallowed_err(win, err)) { return; // error already set } + // Can't move the cmdwin or its old curwin to a different tabpage. + if ((win == cmdwin_win || win == cmdwin_old_curwin) && parent != NULL + && win_find_tabpage(parent) != win_tp) { + api_set_error(err, kErrorTypeException, "%s", e_cmdwin); + return; + } bool to_split_ok = false; // If we are moving curwin to another tabpage, switch windows *before* we remove it from the diff --git a/src/nvim/winfloat.c b/src/nvim/winfloat.c index 10d8c7ac90..3ddff8aa5a 100644 --- a/src/nvim/winfloat.c +++ b/src/nvim/winfloat.c @@ -55,6 +55,19 @@ win_T *win_new_float(win_T *wp, bool last, WinConfig fconfig, Error *err) api_set_error(err, kErrorTypeException, "Cannot change window from different tabpage into float"); return NULL; + } else if (cmdwin_win != NULL && !cmdwin_win->w_floating) { + // cmdwin can't become the only non-float. Check for others. + bool other_nonfloat = false; + for (win_T *wp2 = firstwin; wp2 != NULL && !wp2->w_floating; wp2 = wp2->w_next) { + if (wp2 != wp && wp2 != cmdwin_win) { + other_nonfloat = true; + break; + } + } + if (!other_nonfloat) { + api_set_error(err, kErrorTypeException, "%s", e_cmdwin); + return NULL; + } } int dir; winframe_remove(wp, &dir, NULL, NULL); diff --git a/test/functional/api/tabpage_spec.lua b/test/functional/api/tabpage_spec.lua index 36955c4ace..f7e6eed047 100644 --- a/test/functional/api/tabpage_spec.lua +++ b/test/functional/api/tabpage_spec.lua @@ -1,5 +1,7 @@ local helpers = require('test.functional.helpers')(after_each) local clear, eq, ok = helpers.clear, helpers.eq, helpers.ok +local exec = helpers.exec +local feed = helpers.feed local api = helpers.api local fn = helpers.fn local request = helpers.request @@ -86,6 +88,30 @@ describe('api/tabpage', function() pcall_err(api.nvim_tabpage_set_win, tab1, win3) ) end) + + it('does not switch window when textlocked or in the cmdwin', function() + local target_win = api.nvim_get_current_win() + feed('q:') + local cur_win = api.nvim_get_current_win() + eq( + 'Vim:E11: Invalid in command-line window; executes, CTRL-C quits', + pcall_err(api.nvim_tabpage_set_win, 0, target_win) + ) + eq(cur_win, api.nvim_get_current_win()) + command('quit!') + + exec(([[ + new + call setline(1, 'foo') + setlocal debug=throw indentexpr=nvim_tabpage_set_win(0,%d) + ]]):format(target_win)) + cur_win = api.nvim_get_current_win() + eq( + 'Vim(normal):E5555: API call: Vim:E565: Not allowed to change text or change window', + pcall_err(command, 'normal! ==') + ) + eq(cur_win, api.nvim_get_current_win()) + end) end) describe('{get,set,del}_var', function() diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index abe1ca9344..ae8a519f11 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -2297,29 +2297,82 @@ describe('API/win', function() try_move_t2_wins_to_t1() end) - it('does not switch window when textlocked or in the cmdwin', function() + it('handles cmdwin and textlock restrictions', function() command('tabnew') + local t2 = api.nvim_get_current_tabpage() local t2_win = api.nvim_get_current_win() command('tabfirst') + local t1_move_win = api.nvim_get_current_win() + command('split') + + -- Can't move the cmdwin, or its old curwin to a different tabpage. + local old_curwin = api.nvim_get_current_win() feed('q:') - local cur_win = api.nvim_get_current_win() eq( - 'Failed to switch away from window ' .. cur_win, + 'E11: Invalid in command-line window; executes, CTRL-C quits', pcall_err(api.nvim_win_set_config, 0, { split = 'left', win = t2_win }) ) eq( 'E11: Invalid in command-line window; executes, CTRL-C quits', - api.nvim_get_vvar('errmsg') + pcall_err(api.nvim_win_set_config, old_curwin, { split = 'left', win = t2_win }) ) - eq(cur_win, api.nvim_get_current_win()) + -- But we can move other windows. + api.nvim_win_set_config(t1_move_win, { split = 'left', win = t2_win }) + eq(t2, api.nvim_win_get_tabpage(t1_move_win)) command('quit!') + -- Can't configure windows such that the cmdwin would become the only non-float. + command('only!') + feed('q:') + eq( + 'E11: Invalid in command-line window; executes, CTRL-C quits', + pcall_err( + api.nvim_win_set_config, + old_curwin, + { relative = 'editor', row = 0, col = 0, width = 5, height = 5 } + ) + ) + -- old_curwin is now no longer the only other non-float, so we can make it floating now. + local t1_new_win = api.nvim_open_win( + api.nvim_create_buf(true, true), + false, + { split = 'left', win = old_curwin } + ) + api.nvim_win_set_config( + old_curwin, + { relative = 'editor', row = 0, col = 0, width = 5, height = 5 } + ) + eq('editor', api.nvim_win_get_config(old_curwin).relative) + -- ...which means we shouldn't be able to also make the new window floating too! + eq( + 'E11: Invalid in command-line window; executes, CTRL-C quits', + pcall_err( + api.nvim_win_set_config, + t1_new_win, + { relative = 'editor', row = 0, col = 0, width = 5, height = 5 } + ) + ) + -- Nothing ought to stop us from making the cmdwin itself floating, though... + api.nvim_win_set_config(0, { relative = 'editor', row = 0, col = 0, width = 5, height = 5 }) + eq('editor', api.nvim_win_get_config(0).relative) + -- We can't make our new window from before floating too, as it's now the only non-float. + eq( + 'Cannot change last window into float', + pcall_err( + api.nvim_win_set_config, + t1_new_win, + { relative = 'editor', row = 0, col = 0, width = 5, height = 5 } + ) + ) + command('quit!') + + -- Can't switch away from window before moving it to a different tabpage during textlock. exec(([[ new call setline(1, 'foo') setlocal debug=throw indentexpr=nvim_win_set_config(0,#{split:'left',win:%d}) ]]):format(t2_win)) - cur_win = api.nvim_get_current_win() + local cur_win = api.nvim_get_current_win() matches( 'E565: Not allowed to change text or change window$', pcall_err(command, 'normal! ==') -- cgit From 54022a2946aca5de991e7fa1ebc2954340ec20a8 Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Sat, 9 Mar 2024 01:00:33 +0000 Subject: fix(api): win_set_config update statuslines after removing splits Problem: nvim_win_set_config does not update statuslines after removing a split. Solution: call last_status. Didn't realize this was missing in the original nvim_win_set_config for splits PR. As it can only be done for the current tabpage, do it if win_tp == curtab; enter_tabpage will eventually call last_status anyway when the user enters another tabpage. --- src/nvim/api/win_config.c | 4 ++++ test/functional/api/window_spec.lua | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index dab1e4e80b..32b2156313 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -550,6 +550,10 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) } } win_remove(win, win_tp == curtab ? NULL : win_tp); + if (win_tp == curtab) { + last_status(false); // may need to remove last status line + win_comp_pos(); // recompute window positions + } int flags = win_split_flags(fconfig.split, parent == NULL) | WSP_NOENTER; TRY_WRAP(err, { diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index ae8a519f11..6b7c550ca8 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -2379,6 +2379,28 @@ describe('API/win', function() ) eq(cur_win, api.nvim_get_current_win()) end) + + it('updates statusline when moving bottom split', function() + local screen = Screen.new(10, 10) + screen:set_default_attr_ids({ + [0] = { bold = true, foreground = Screen.colors.Blue }, -- NonText + [1] = { bold = true, reverse = true }, -- StatusLine + }) + screen:attach() + exec([[ + set laststatus=0 + belowright split + call nvim_win_set_config(0, #{split: 'above', win: win_getid(winnr('#'))}) + ]]) + screen:expect([[ + ^ | + {0:~ }|*3 + {1:[No Name] }| + | + {0:~ }|*3 + | + ]]) + end) end) describe('get_config', function() -- cgit From 6416c6bc94fa7ae553a6020d0ed2f07dd34ee3f1 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 9 Mar 2024 12:27:01 +0800 Subject: test(old): remove Test_floatwin_splitmove() Its corresponding test in Vim is in test_popupwin.win, so having it in the middle of test_window_cmd.vim is strange, and it is now covered by tests in ui/float_spec.lua anyway. --- test/old/testdir/test_window_cmd.vim | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/test/old/testdir/test_window_cmd.vim b/test/old/testdir/test_window_cmd.vim index e6d591f8c9..8898d3a02d 100644 --- a/test/old/testdir/test_window_cmd.vim +++ b/test/old/testdir/test_window_cmd.vim @@ -1164,19 +1164,6 @@ func Test_win_splitmove() %bw! endfunc -func Test_floatwin_splitmove() - vsplit - let win2 = win_getid() - let popup_winid = nvim_open_win(0, 0, {'relative': 'win', - \ 'row': 3, 'col': 3, 'width': 12, 'height': 3}) - " Nvim: floating windows are supported for the first argument. - " call assert_fails('call win_splitmove(popup_winid, win2)', 'E957:') - call assert_fails('call win_splitmove(win2, popup_winid)', 'E957:') - - call nvim_win_close(popup_winid, 1) - bwipe -endfunc - " Test for the :only command func Test_window_only() new -- cgit From 33dfb5a383d7afacda35b8fd392ad18d57db2870 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 9 Mar 2024 12:47:40 +0800 Subject: fix(window): :close may cause Nvim to quit with autocmd and float Problem: :close may cause Nvim to quit if an autocommand triggered when closing the buffer closes all other non-floating windows and there are floating windows. Solution: Correct the check for the only non-floating window. --- src/nvim/buffer.c | 4 ++-- src/nvim/window.c | 46 ++++++++++----------------------------- test/functional/ui/float_spec.lua | 33 ++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 37 deletions(-) diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index b013f43ceb..7154be36be 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -568,7 +568,7 @@ bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last, bool i } buf->b_locked--; buf->b_locked_split--; - if (abort_if_last && last_nonfloat(win)) { + if (abort_if_last && one_window(win)) { // Autocommands made this the only window. emsg(_(e_auabort)); return false; @@ -587,7 +587,7 @@ bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last, bool i } buf->b_locked--; buf->b_locked_split--; - if (abort_if_last && last_nonfloat(win)) { + if (abort_if_last && one_window(win)) { // Autocommands made this the only window. emsg(_(e_auabort)); return false; diff --git a/src/nvim/window.c b/src/nvim/window.c index b55dd79260..4dc6ed370e 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -455,7 +455,7 @@ newwindow: case 'H': case 'L': CHECK_CMDWIN; - if (firstwin == curwin && lastwin_nofloating() == curwin) { + if (one_window(curwin)) { beep_flush(); } else { const int dir = ((nchar == 'H' || nchar == 'L') ? WSP_VERT : 0) @@ -1018,7 +1018,7 @@ win_T *win_split_ins(int size, int flags, win_T *new_wp, int dir, frame_T *to_fl bool toplevel = flags & (WSP_TOP | WSP_BOT); // add a status line when p_ls == 1 and splitting the first window - if (one_nonfloat() && p_ls == 1 && oldwin->w_status_height == 0) { + if (one_window(firstwin) && p_ls == 1 && oldwin->w_status_height == 0) { if (oldwin->w_height <= p_wmh) { emsg(_(e_noroom)); return NULL; @@ -1741,7 +1741,7 @@ static void win_exchange(int Prenum) return; } - if (firstwin == curwin && lastwin_nofloating() == curwin) { + if (one_window(curwin)) { // just one window beep_flush(); return; @@ -1833,7 +1833,7 @@ static void win_rotate(bool upwards, int count) return; } - if (count <= 0 || (firstwin == curwin && lastwin_nofloating() == curwin)) { + if (count <= 0 || one_window(curwin)) { // nothing to do beep_flush(); return; @@ -2495,37 +2495,13 @@ bool last_window(win_T *win) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT } /// Check if "win" is the only non-floating window in the current tabpage. -bool one_window(win_T *win) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT -{ - if (win->w_floating) { - return false; - } - - bool seen_one = false; - - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (!wp->w_floating) { - if (seen_one) { - return false; - } - seen_one = true; - } - } - return true; -} - -/// Like ONE_WINDOW but only considers non-floating windows -bool one_nonfloat(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT -{ - return firstwin->w_next == NULL || firstwin->w_next->w_floating; -} - -/// if wp is the last non-floating window /// -/// always false for a floating window -bool last_nonfloat(win_T *wp) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +/// This should be used in place of ONE_WINDOW when necessary, +/// with "firstwin" or the affected window as argument depending on the situation. +bool one_window(win_T *win) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - return wp != NULL && firstwin == wp && !(wp->w_next && !wp->w_floating); + assert(!firstwin->w_floating); + return firstwin == win && (win->w_next == NULL || win->w_next->w_floating); } /// Check if floating windows in the current tab can be closed. @@ -3950,7 +3926,7 @@ void close_others(int message, int forceit) return; } - if (one_nonfloat() && !lastwin->w_floating) { + if (one_window(firstwin) && !lastwin->w_floating) { if (message && !autocmd_busy) { msg(_(m_onlyone), 0); @@ -7224,7 +7200,7 @@ int global_stl_height(void) /// @param morewin pretend there are two or more windows if true. int last_stl_height(bool morewin) { - return (p_ls > 1 || (p_ls == 1 && (morewin || !one_nonfloat()))) ? STATUS_HEIGHT : 0; + return (p_ls > 1 || (p_ls == 1 && (morewin || !one_window(firstwin)))) ? STATUS_HEIGHT : 0; } /// Return the minimal number of rows that is needed on the screen to display diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index e324d03b30..8b7107fdf2 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -873,6 +873,39 @@ describe('float window', function() end) end) + describe(':close on non-float with floating windows', function() + it('does not quit Nvim if BufWinLeave makes it the only non-float', function() + exec([[ + let firstbuf = bufnr() + new + let midwin = win_getid() + new + setlocal bufhidden=wipe + call nvim_win_set_config(midwin, + \ #{relative: 'editor', row: 5, col: 5, width: 5, height: 5}) + autocmd BufWinLeave * ++once exe firstbuf .. 'bwipe!' + ]]) + eq('Vim(close):E855: Autocommands caused command to abort', pcall_err(command, 'close')) + assert_alive() + end) + + pending('does not crash if BufWinLeave makes it the only non-float in tabpage', function() + exec([[ + tabnew + let firstbuf = bufnr() + new + let midwin = win_getid() + new + setlocal bufhidden=wipe + call nvim_win_set_config(midwin, + \ #{relative: 'editor', row: 5, col: 5, width: 5, height: 5}) + autocmd BufWinLeave * ++once exe firstbuf .. 'bwipe!' + ]]) + eq('Vim(close):E855: Autocommands caused command to abort', pcall_err(command, 'close')) + assert_alive() + end) + end) + local function with_ext_multigrid(multigrid) local screen, attrs before_each(function() -- cgit From b52d15853e89149472c1ecd9cce3a84e4af0785a Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Sat, 9 Mar 2024 16:56:32 +0000 Subject: fix(api): win_set_config set tp_curwin of win moved from other tabpage Problem: nvim_win_set_config does not update the tp_curwin of win's original tabpage when moving it to another. Solution: update it if win was the tp_curwin. Add a test. --- src/nvim/api/win_config.c | 23 ++++++++++++++++----- src/nvim/window.c | 15 +------------- src/nvim/winfloat.c | 18 ++++++++++++++++ test/functional/api/window_spec.lua | 41 +++++++++++++++++++++++++++++++++++++ 4 files changed, 78 insertions(+), 19 deletions(-) diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 32b2156313..8b8eca62ca 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -468,7 +468,7 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) int dir; win_goto(winframe_find_altwin(win, &dir, NULL, NULL)); } else { - win_goto(win_valid(prevwin) && prevwin != win ? prevwin : firstwin); + win_goto(win_float_find_altwin(win, NULL)); } // Autocommands may have been a real nuisance and messed things up... @@ -490,6 +490,8 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) int dir = 0; frame_T *unflat_altfr = NULL; + win_T *altwin = NULL; + if (was_split) { // If the window is the last in the tabpage or `fconfig.win` is // a handle to itself, we can't split it. @@ -534,10 +536,11 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) } // If the frame doesn't have a parent, the old frame // was the root frame and we need to create a top-level split. - winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp, &unflat_altfr); + altwin = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp, &unflat_altfr); } else if (n_frames == 2) { // There are two windows in the frame, we can just rotate it. - neighbor = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp, &unflat_altfr); + altwin = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp, &unflat_altfr); + neighbor = altwin; } else { // There is only one window in the frame, we can't split it. api_set_error(err, kErrorTypeException, "Cannot split window into itself"); @@ -546,9 +549,12 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) // Set the parent to whatever the correct neighbor window was determined to be. parent = neighbor; } else { - winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp, &unflat_altfr); + altwin = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp, &unflat_altfr); } + } else { + altwin = win_float_find_altwin(win, win_tp == curtab ? NULL : win_tp); } + win_remove(win, win_tp == curtab ? NULL : win_tp); if (win_tp == curtab) { last_status(false); // may need to remove last status line @@ -556,12 +562,14 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) } int flags = win_split_flags(fconfig.split, parent == NULL) | WSP_NOENTER; + tabpage_T *const parent_tp = parent ? win_find_tabpage(parent) : curtab; + TRY_WRAP(err, { const bool need_switch = parent != NULL && parent != curwin; switchwin_T switchwin; if (need_switch) { // `parent` is valid in its tabpage, so switch_win should not fail. - const int result = switch_win(&switchwin, parent, win_find_tabpage(parent), true); + const int result = switch_win(&switchwin, parent, parent_tp, true); (void)result; assert(result == OK); } @@ -593,6 +601,11 @@ restore_curwin: return; } + // If `win` moved tabpages and was the curwin of its old one, select a new curwin for it. + if (win_tp != parent_tp && win_tp->tp_curwin == win) { + win_tp->tp_curwin = altwin; + } + if (HAS_KEY_X(config, width)) { win_setwidth_win(fconfig.width, win); } diff --git a/src/nvim/window.c b/src/nvim/window.c index 4dc6ed370e..ecd2e83500 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -3044,20 +3044,7 @@ static win_T *win_free_mem(win_T *win, int *dirp, tabpage_T *tp) xfree(frp); } else { *dirp = 'h'; // Dummy value. - if (tp == NULL) { - if (win_valid(prevwin) && prevwin != win) { - wp = prevwin; - } else { - wp = firstwin; - } - } else { - assert(tp != curtab); - if (tabpage_win_valid(tp, tp->tp_prevwin) && tp->tp_prevwin != win) { - wp = tp->tp_prevwin; - } else { - wp = tp->tp_firstwin; - } - } + wp = win_float_find_altwin(win, tp); } win_free(win, tp); diff --git a/src/nvim/winfloat.c b/src/nvim/winfloat.c index 3ddff8aa5a..65d2c1306b 100644 --- a/src/nvim/winfloat.c +++ b/src/nvim/winfloat.c @@ -319,3 +319,21 @@ win_T *win_float_find_preview(void) } return NULL; } + +/// Select an alternative window to `win` (assumed floating) in tabpage `tp`. +/// +/// Useful for finding a window to switch to if `win` is the current window, but is then closed or +/// moved to a different tabpage. +/// +/// @param tp `win`'s original tabpage, or NULL for current. +win_T *win_float_find_altwin(const win_T *win, const tabpage_T *tp) + FUNC_ATTR_NONNULL_ARG(1) +{ + if (tp == NULL) { + return (win_valid(prevwin) && prevwin != win) ? prevwin : firstwin; + } + + assert(tp != curtab); + return (tabpage_win_valid(tp, tp->tp_prevwin) && tp->tp_prevwin != win) ? tp->tp_prevwin + : tp->tp_firstwin; +} diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index 6b7c550ca8..a10c8f48ef 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -2401,6 +2401,47 @@ describe('API/win', function() | ]]) end) + + it("updates tp_curwin of moved window's original tabpage", function() + local t1 = api.nvim_get_current_tabpage() + command('tab split | split') + local t2 = api.nvim_get_current_tabpage() + local t2_alt_win = api.nvim_get_current_win() + command('vsplit') + local t2_cur_win = api.nvim_get_current_win() + command('tabprevious') + eq(t2_cur_win, api.nvim_tabpage_get_win(t2)) + + -- tp_curwin is unchanged when moved within the same tabpage. + api.nvim_win_set_config(t2_cur_win, { split = 'left', win = t2_alt_win }) + eq(t2_cur_win, api.nvim_tabpage_get_win(t2)) + + -- Also unchanged if the move failed. + command('let &winwidth = &columns | let &winminwidth = &columns') + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, t2_cur_win, { split = 'left', win = 0 }) + ) + eq(t2_cur_win, api.nvim_tabpage_get_win(t2)) + command('set winminwidth& winwidth&') + + -- But is changed if successfully moved to a different tabpage. + api.nvim_win_set_config(t2_cur_win, { split = 'left', win = 0 }) + eq(t2_alt_win, api.nvim_tabpage_get_win(t2)) + eq(t1, api.nvim_win_get_tabpage(t2_cur_win)) + + -- Now do it for a float, which has different altwin logic. + command('tabnext') + t2_cur_win = + api.nvim_open_win(0, true, { relative = 'editor', row = 5, col = 5, width = 5, height = 5 }) + eq(t2_alt_win, fn.win_getid(fn.winnr('#'))) + command('tabprevious') + eq(t2_cur_win, api.nvim_tabpage_get_win(t2)) + + api.nvim_win_set_config(t2_cur_win, { split = 'left', win = 0 }) + eq(t2_alt_win, api.nvim_tabpage_get_win(t2)) + eq(t1, api.nvim_win_get_tabpage(t2_cur_win)) + end) end) describe('get_config', function() -- cgit From c3d22d32ee4b4c1911ec15f2a77683d09b09f845 Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Sat, 9 Mar 2024 20:24:08 +0000 Subject: vim-patch:8.2.3862: crash on exit with EXITFREE and using win_execute() Problem: Crash on exit with EXITFREE and using win_execute(). Solution: Also save and restore tp_topframe. (issue vim/vim#9374) https://github.com/vim/vim/commit/dab17a0689a2f31f69f428975f84b0c3c7ba3030 Couldn't repro the crash in the test, but I only care about this patch so switch_win sets topframe properly for win_split_ins in nvim_open_win and nvim_win_set_config. Add a test using nvim_win_call and :wincmd, as I couldn't repro the issue via nvim_open_win or nvim_win_set_config (though it's clear they're affected by this patch). That said, at that point, could just use {un}use_tabpage inside switch_win instead, which also updates tp_curwin (though maybe continue to not set it in restore_win). That would also fix possible inconsistent behaviour such as: :call win_execute(w, "let curwin_nr1 = tabpagewinnr(1)") :let curwin_nr2 = tabpagewinnr(1) Where it's possible for curwin_nr1 != curwin_nr2 if these commands are run from the 1st tabpage, but window "w" is in the 2nd (as the 1st tabpage's tp_curwin may still be invalid). I'll probably PR a fix for that later in Vim. Co-authored-by: Bram Moolenaar --- src/nvim/eval/window.c | 4 ++++ test/functional/lua/vim_spec.lua | 14 ++++++++++++++ test/old/testdir/test_execute_func.vim | 22 ++++++++++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/src/nvim/eval/window.c b/src/nvim/eval/window.c index c2b9574579..e54f46dcc3 100644 --- a/src/nvim/eval/window.c +++ b/src/nvim/eval/window.c @@ -958,9 +958,11 @@ int switch_win_noblock(switchwin_T *switchwin, win_T *win, tabpage_T *tp, bool n if (no_display) { curtab->tp_firstwin = firstwin; curtab->tp_lastwin = lastwin; + curtab->tp_topframe = topframe; curtab = tp; firstwin = curtab->tp_firstwin; lastwin = curtab->tp_lastwin; + topframe = curtab->tp_topframe; } else { goto_tabpage_tp(tp, false, false); } @@ -989,9 +991,11 @@ void restore_win_noblock(switchwin_T *switchwin, bool no_display) if (no_display) { curtab->tp_firstwin = firstwin; curtab->tp_lastwin = lastwin; + curtab->tp_topframe = topframe; curtab = switchwin->sw_curtab; firstwin = curtab->tp_firstwin; lastwin = curtab->tp_lastwin; + topframe = curtab->tp_topframe; } else { goto_tabpage_tp(switchwin->sw_curtab, false, false); } diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index a262d239e8..add3df6d8a 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -3664,6 +3664,20 @@ describe('lua stdlib', function() ]] ) end) + + it('layout in current tabpage does not affect windows in others', function() + command('tab split') + local t2_move_win = api.nvim_get_current_win() + command('vsplit') + local t2_other_win = api.nvim_get_current_win() + command('tabprevious') + matches('E36: Not enough room$', pcall_err(command, 'execute "split|"->repeat(&lines)')) + command('vsplit') + + -- Without vim-patch:8.2.3862, this gives E36, despite just the 1st tabpage being full. + exec_lua('vim.api.nvim_win_call(..., function() vim.cmd.wincmd "J" end)', t2_move_win) + eq({ 'col', { { 'leaf', t2_other_win }, { 'leaf', t2_move_win } } }, fn.winlayout(2)) + end) end) describe('vim.iconv', function() diff --git a/test/old/testdir/test_execute_func.vim b/test/old/testdir/test_execute_func.vim index 2edae39b8f..d9909e92a6 100644 --- a/test/old/testdir/test_execute_func.vim +++ b/test/old/testdir/test_execute_func.vim @@ -3,6 +3,7 @@ source view_util.vim source check.vim source vim9.vim +source term_util.vim func NestedEval() let nested = execute('echo "nested\nlines"') @@ -177,6 +178,27 @@ func Test_win_execute_visual_redraw() bwipe! endfunc +func Test_win_execute_on_startup() + CheckRunVimInTerminal + + let lines =<< trim END + vim9script + [repeat('x', &columns)]->writefile('Xfile1') + silent tabedit Xfile2 + var id = win_getid() + silent tabedit Xfile3 + autocmd VimEnter * win_execute(id, 'close') + END + call writefile(lines, 'XwinExecute') + let buf = RunVimInTerminal('-p Xfile1 -Nu XwinExecute', {}) + + " this was crashing on exit with EXITFREE defined + call StopVimInTerminal(buf) + + call delete('XwinExecute') + call delete('Xfile1') +endfunc + func Test_execute_cmd_with_null() call assert_equal("", execute(v:_null_string)) call assert_equal("", execute(v:_null_list)) -- cgit From 241c16129919e169b71ef1e788420224b358fbb3 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 10 Mar 2024 06:47:09 +0800 Subject: vim-patch:9.1.0161: expand() removes slash after env variable that ends with colon (#27791) Problem: expand() removes a slash after an environment variable that ends with a colon on Windows. Solution: Check the correct char for a colon (zeertzjq) closes: vim/vim#14161 Note: Vim still removes the path-separator at the end, if another path separator follows directly after it, e.g. on: ``` echo $FOO='/usr/' echo expand('$FOO/bar') == '/usr/bar' ``` see: ,----[ misc1.c:1630 ] | // if var[] ends in a path separator and tail[] starts | // with it, skip a character | if (after_pathsep(dst, dst + c) | #if defined(BACKSLASH_IN_FILENAME) || defined(AMIGA) | && (dst == save_dst || dst[-1] != ':') | #endif | && vim_ispathsep(*tail)) | ++tail; `---- https://github.com/vim/vim/commit/13a014452a7a020a119ac555a690c65b41f3126d Cherry-pick test_expand.vim change from patch 9.0.1257. --- src/nvim/os/env.c | 5 +---- test/old/testdir/test_expand.vim | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index 5b1cb01976..8a81f6e928 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -586,9 +586,6 @@ void expand_env_esc(char *restrict srcp, char *restrict dst, int dstlen, bool es bool copy_char; bool mustfree; // var was allocated, need to free it later bool at_start = true; // at start of a name -#if defined(BACKSLASH_IN_FILENAME) - char *const save_dst = dst; -#endif int prefix_len = (prefix == NULL) ? 0 : (int)strlen(prefix); @@ -729,7 +726,7 @@ void expand_env_esc(char *restrict srcp, char *restrict dst, int dstlen, bool es // with it, skip a character if (after_pathsep(dst, dst + c) #if defined(BACKSLASH_IN_FILENAME) - && (dst == save_dst || dst[-1] != ':') + && dst[c - 1] != ':' #endif && vim_ispathsep(*tail)) { tail++; diff --git a/test/old/testdir/test_expand.vim b/test/old/testdir/test_expand.vim index cd537f4ea1..24df156386 100644 --- a/test/old/testdir/test_expand.vim +++ b/test/old/testdir/test_expand.vim @@ -45,12 +45,25 @@ endfunc func Test_expand_tilde_filename() split ~ - call assert_equal('~', expand('%')) + call assert_equal('~', expand('%')) call assert_notequal(expand('%:p'), expand('~/')) - call assert_match('\~', expand('%:p')) + call assert_match('\~', expand('%:p')) bwipe! endfunc +func Test_expand_env_pathsep() + let $FOO = './foo' + call assert_equal('./foo/bar', expand('$FOO/bar')) + let $FOO = './foo/' + call assert_equal('./foo/bar', expand('$FOO/bar')) + let $FOO = 'C:' + call assert_equal('C:/bar', expand('$FOO/bar')) + let $FOO = 'C:/' + call assert_equal('C:/bar', expand('$FOO/bar')) + + unlet $FOO +endfunc + func Test_expandcmd() let $FOO = 'Test' call assert_equal('e x/Test/y', expandcmd('e x/$FOO/y')) -- cgit From 448cf10c47e0678cee080baaf75f395511e13269 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 10 Mar 2024 07:03:36 +0800 Subject: vim-patch:9.1.0159: Crash in WinClosed after BufUnload closes other windows (#27792) Problem: Crash in WinClosed after BufUnload closes other windows Solution: Don't trigger WinClosed if the buffer is NULL (zeertzjq) Now win_close_othertab() doesn't trigger any autocommands if the buffer is NULL, so remove the autocmd blocking above (which was added not long ago in patch v9.0.0550) for consistency. Also remove an unreachable close_last_window_tabpage() above: - It is only reached if only_one_window() returns TRUE and last_window() returns FALSE. - If only_one_window() returns TRUE, there is only one tabpage. - If there is only one tabpage and last_window() returns FALSE, the one_window() in last_window() must return FALSE, and the ONE_WINDOW in close_last_window_tabpage() must also be FALSE. - So close_last_window_tabpage() doesn't do anything and returns FALSE. Then the curtab != prev_curtab check also doesn't make much sense, and the only_one_window() can be replaced with a check for popup and a call to last_window() since this is a stricter check than only_one_window(). closes: vim/vim#14166 https://github.com/vim/vim/commit/b2ec0da080fb24f12a8d6f54bd7318a078ca4e6c --- src/nvim/window.c | 23 +++++++++++------------ test/old/testdir/test_autocmd.vim | 21 +++++++++++++++++++++ 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/src/nvim/window.c b/src/nvim/window.c index ecd2e83500..6c7ca86636 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -2546,7 +2546,7 @@ bool can_close_in_cmdwin(win_T *win, Error *err) /// @param prev_curtab previous tabpage that will be closed if "win" is the /// last window in the tabpage /// -/// @return true when the window was closed already. +/// @return false if there are other windows and nothing is done, true otherwise. static bool close_last_window_tabpage(win_T *win, bool free_buf, tabpage_T *prev_curtab) FUNC_ATTR_NONNULL_ARG(1) { @@ -2751,10 +2751,8 @@ int win_close(win_T *win, bool free_buf, bool force) win_close_buffer(win, free_buf ? DOBUF_UNLOAD : 0, true); - if (only_one_window() && win_valid(win) && win->w_buffer == NULL - && (last_window(win) || curtab != prev_curtab - || close_last_window_tabpage(win, free_buf, prev_curtab)) - && !win->w_floating) { + if (win_valid(win) && win->w_buffer == NULL + && !win->w_floating && last_window(win)) { // Autocommands have closed all windows, quit now. Restore // curwin->w_buffer, otherwise writing ShaDa file may fail. if (curwin->w_buffer == NULL) { @@ -2766,10 +2764,7 @@ int win_close(win_T *win, bool free_buf, bool force) if (curtab != prev_curtab && win_valid_any_tab(win) && win->w_buffer == NULL) { // Need to close the window anyway, since the buffer is NULL. - // Don't trigger autocmds with a NULL buffer. - block_autocmds(); win_close_othertab(win, false, prev_curtab); - unblock_autocmds(); return FAIL; } @@ -2940,10 +2935,14 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) } // Fire WinClosed just before starting to free window-related resources. - do_autocmd_winclosed(win); - // autocmd may have freed the window already. - if (!win_valid_any_tab(win)) { - return; + // If the buffer is NULL, it isn't safe to trigger autocommands, + // and win_close() should have already triggered WinClosed. + if (win->w_buffer != NULL) { + do_autocmd_winclosed(win); + // autocmd may have freed the window already. + if (!win_valid_any_tab(win)) { + return; + } } if (win->w_buffer != NULL) { diff --git a/test/old/testdir/test_autocmd.vim b/test/old/testdir/test_autocmd.vim index 4d88573a1f..2b37ccf4a6 100644 --- a/test/old/testdir/test_autocmd.vim +++ b/test/old/testdir/test_autocmd.vim @@ -740,6 +740,27 @@ func Test_WinClosed_switch_tab() %bwipe! endfunc +" This used to trigger WinClosed twice for the same window, and the window's +" buffer was NULL in the second autocommand. +func Test_WinClosed_BufUnload_close_other() + tabnew + let g:tab = tabpagenr() + let g:buf = bufnr() + new + setlocal bufhidden=wipe + augroup test-WinClosed + autocmd BufUnload * ++once exe g:buf .. 'bwipe!' + autocmd WinClosed * call tabpagebuflist(g:tab) + augroup END + close + + unlet g:tab + unlet g:buf + autocmd! test-WinClosed + augroup! test-WinClosed + %bwipe! +endfunc + func s:AddAnAutocmd() augroup vimBarTest au BufReadCmd * echo 'hello' -- cgit From 731e7f51ee40778b5baeec99aaf1d551b0855667 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 10 Mar 2024 07:55:04 +0800 Subject: fix(window): :close crash with autocmd, floats and tabpage (#27793) Problem: :close crash with autocmd, floats and tabpage. Solution: Close floating windows in one more case. --- src/nvim/window.c | 54 ++++++++++++++++++++------------------- test/functional/ui/float_spec.lua | 24 +++++++++-------- 2 files changed, 42 insertions(+), 36 deletions(-) diff --git a/src/nvim/window.c b/src/nvim/window.c index 6c7ca86636..c0a9b1e39b 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -1916,7 +1916,7 @@ int win_splitmove(win_T *wp, int size, int flags) int dir = 0; int height = wp->w_height; - if (firstwin == wp && lastwin_nofloating() == wp) { + if (one_window(wp)) { return OK; // nothing to do } if (is_aucmd_win(wp) || check_split_disallowed(wp) == FAIL) { @@ -2539,21 +2539,42 @@ bool can_close_in_cmdwin(win_T *win, Error *err) return true; } -/// Close the possibly last window in a tab page. +/// Close the possibly last non-floating window in a tab page. /// /// @param win window to close /// @param free_buf whether to free the window's current buffer +/// @param force close floating windows even if they are modified /// @param prev_curtab previous tabpage that will be closed if "win" is the /// last window in the tabpage /// -/// @return false if there are other windows and nothing is done, true otherwise. -static bool close_last_window_tabpage(win_T *win, bool free_buf, tabpage_T *prev_curtab) +/// @return false if there are other non-floating windows and nothing is done, true otherwise. +static bool close_last_window_tabpage(win_T *win, bool free_buf, bool force, tabpage_T *prev_curtab) FUNC_ATTR_NONNULL_ARG(1) { - if (!ONE_WINDOW) { + if (!one_window(win)) { return false; } + if (lastwin->w_floating && one_window(win)) { + if (is_aucmd_win(lastwin)) { + emsg(_("E814: Cannot close window, only autocmd window would remain")); + return true; + } + if (force || can_close_floating_windows()) { + // close the last window until the there are no floating windows + while (lastwin->w_floating) { + // `force` flag isn't actually used when closing a floating window. + if (win_close(lastwin, free_buf, true) == FAIL) { + // If closing the window fails give up, to avoid looping forever. + return true; + } + } + } else { + emsg(e_floatonly); + return true; + } + } + buf_T *old_curbuf = curbuf; Terminal *term = win->w_buffer ? win->w_buffer->terminal : NULL; @@ -2653,30 +2674,11 @@ int win_close(win_T *win, bool free_buf, bool force) emsg(_(e_autocmd_close)); return FAIL; } - if (lastwin->w_floating && one_window(win)) { - if (is_aucmd_win(lastwin)) { - emsg(_("E814: Cannot close window, only autocmd window would remain")); - return FAIL; - } - if (force || can_close_floating_windows()) { - // close the last window until the there are no floating windows - while (lastwin->w_floating) { - // `force` flag isn't actually used when closing a floating window. - if (win_close(lastwin, free_buf, true) == FAIL) { - // If closing the window fails give up, to avoid looping forever. - return FAIL; - } - } - } else { - emsg(e_floatonly); - return FAIL; - } - } // When closing the last window in a tab page first go to another tab page // and then close the window and the tab page to avoid that curwin and // curtab are invalid while we are freeing memory. - if (close_last_window_tabpage(win, free_buf, prev_curtab)) { + if (close_last_window_tabpage(win, free_buf, force, prev_curtab)) { return FAIL; } @@ -2771,7 +2773,7 @@ int win_close(win_T *win, bool free_buf, bool force) // Autocommands may have closed the window already, or closed the only // other window or moved to another tab page. if (!win_valid(win) || (!win->w_floating && last_window(win)) - || close_last_window_tabpage(win, free_buf, prev_curtab)) { + || close_last_window_tabpage(win, free_buf, force, prev_curtab)) { return FAIL; } diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index 8b7107fdf2..50b4b3b073 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -874,34 +874,38 @@ describe('float window', function() end) describe(':close on non-float with floating windows', function() + -- XXX: it isn't really clear whether this should quit Nvim, as if the autocommand + -- here is BufUnload then it does quit Nvim. + -- But with BufWinLeave, this doesn't quit Nvim if there are no floating windows, + -- so it shouldn't quit Nvim if there are floating windows. it('does not quit Nvim if BufWinLeave makes it the only non-float', function() exec([[ - let firstbuf = bufnr() + let g:buf = bufnr() new - let midwin = win_getid() + let s:midwin = win_getid() new setlocal bufhidden=wipe - call nvim_win_set_config(midwin, + call nvim_win_set_config(s:midwin, \ #{relative: 'editor', row: 5, col: 5, width: 5, height: 5}) - autocmd BufWinLeave * ++once exe firstbuf .. 'bwipe!' + autocmd BufWinLeave * ++once exe g:buf .. 'bwipe!' ]]) eq('Vim(close):E855: Autocommands caused command to abort', pcall_err(command, 'close')) assert_alive() end) - pending('does not crash if BufWinLeave makes it the only non-float in tabpage', function() + it('does not crash if BufUnload makes it the only non-float in tabpage', function() exec([[ tabnew - let firstbuf = bufnr() + let g:buf = bufnr() new - let midwin = win_getid() + let s:midwin = win_getid() new setlocal bufhidden=wipe - call nvim_win_set_config(midwin, + call nvim_win_set_config(s:midwin, \ #{relative: 'editor', row: 5, col: 5, width: 5, height: 5}) - autocmd BufWinLeave * ++once exe firstbuf .. 'bwipe!' + autocmd BufUnload * ++once exe g:buf .. 'bwipe!' ]]) - eq('Vim(close):E855: Autocommands caused command to abort', pcall_err(command, 'close')) + command('close') assert_alive() end) end) -- cgit From 9bd4a2807960ea3e82b0454861b399f4ac6d8a92 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 10 Mar 2024 08:37:16 +0800 Subject: fix(window): :close crash if WinClosed from float closes window (#27794) Problem: :close crash if WinClosed from float closes window. Solution: Check if window has already been closed. --- src/nvim/window.c | 9 +++++---- test/functional/ui/float_spec.lua | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/nvim/window.c b/src/nvim/window.c index c0a9b1e39b..a76953d900 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -2560,6 +2560,7 @@ static bool close_last_window_tabpage(win_T *win, bool free_buf, bool force, tab emsg(_("E814: Cannot close window, only autocmd window would remain")); return true; } + if (force || can_close_floating_windows()) { // close the last window until the there are no floating windows while (lastwin->w_floating) { @@ -2573,6 +2574,10 @@ static bool close_last_window_tabpage(win_T *win, bool free_buf, bool force, tab emsg(e_floatonly); return true; } + + if (!win_valid_any_tab(win)) { + return true; // window already closed by autocommands + } } buf_T *old_curbuf = curbuf; @@ -2591,10 +2596,6 @@ static bool close_last_window_tabpage(win_T *win, bool free_buf, bool force, tab // that below. goto_tabpage_tp(alt_tabpage(), false, true); - // save index for tabclosed event - char prev_idx[NUMBUFLEN]; - snprintf(prev_idx, NUMBUFLEN, "%i", tabpage_index(prev_curtab)); - // Safety check: Autocommands may have closed the window when jumping // to the other tab page. if (valid_tabpage(prev_curtab) && prev_curtab->tp_firstwin == win) { diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index 50b4b3b073..6bc3fd14ec 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -908,6 +908,21 @@ describe('float window', function() command('close') assert_alive() end) + + it('does not crash if WinClosed from floating windows closes it', function() + exec([[ + tabnew + let g:buf = bufnr() + new + let s:win = win_getid() + call nvim_win_set_config(s:win, + \ #{relative: 'editor', row: 5, col: 5, width: 5, height: 5}) + wincmd t + exe $"autocmd WinClosed {s:win} 1close" + ]]) + command('close') + assert_alive() + end) end) local function with_ext_multigrid(multigrid) -- cgit From 6052b346f1b7a3fb616dfcefe3bc05cb6fe3f2f3 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 10 Mar 2024 10:33:10 +0800 Subject: revert: "fix(window): :close crash with autocmd, floats and tabpage" (#27796) This reverts PR #27793. On second thought, this solution may still crash, because it can leave a window with a NULL buffer if there are autocommand windows or if closing a floating window fails. It also makes close_last_window_tabpage() more complicated, so revert it. --- src/nvim/window.c | 60 ++++++++++++++++++--------------------- test/functional/ui/float_spec.lua | 5 ++-- 2 files changed, 30 insertions(+), 35 deletions(-) diff --git a/src/nvim/window.c b/src/nvim/window.c index a76953d900..ff40a9adef 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -2539,47 +2539,21 @@ bool can_close_in_cmdwin(win_T *win, Error *err) return true; } -/// Close the possibly last non-floating window in a tab page. +/// Close the possibly last window in a tab page. /// /// @param win window to close /// @param free_buf whether to free the window's current buffer -/// @param force close floating windows even if they are modified /// @param prev_curtab previous tabpage that will be closed if "win" is the /// last window in the tabpage /// -/// @return false if there are other non-floating windows and nothing is done, true otherwise. -static bool close_last_window_tabpage(win_T *win, bool free_buf, bool force, tabpage_T *prev_curtab) +/// @return false if there are other windows and nothing is done, true otherwise. +static bool close_last_window_tabpage(win_T *win, bool free_buf, tabpage_T *prev_curtab) FUNC_ATTR_NONNULL_ARG(1) { - if (!one_window(win)) { + if (!ONE_WINDOW) { return false; } - if (lastwin->w_floating && one_window(win)) { - if (is_aucmd_win(lastwin)) { - emsg(_("E814: Cannot close window, only autocmd window would remain")); - return true; - } - - if (force || can_close_floating_windows()) { - // close the last window until the there are no floating windows - while (lastwin->w_floating) { - // `force` flag isn't actually used when closing a floating window. - if (win_close(lastwin, free_buf, true) == FAIL) { - // If closing the window fails give up, to avoid looping forever. - return true; - } - } - } else { - emsg(e_floatonly); - return true; - } - - if (!win_valid_any_tab(win)) { - return true; // window already closed by autocommands - } - } - buf_T *old_curbuf = curbuf; Terminal *term = win->w_buffer ? win->w_buffer->terminal : NULL; @@ -2675,11 +2649,33 @@ int win_close(win_T *win, bool free_buf, bool force) emsg(_(e_autocmd_close)); return FAIL; } + if (lastwin->w_floating && one_window(win)) { + if (is_aucmd_win(lastwin)) { + emsg(_("E814: Cannot close window, only autocmd window would remain")); + return FAIL; + } + if (force || can_close_floating_windows()) { + // close the last window until the there are no floating windows + while (lastwin->w_floating) { + // `force` flag isn't actually used when closing a floating window. + if (win_close(lastwin, free_buf, true) == FAIL) { + // If closing the window fails give up, to avoid looping forever. + return FAIL; + } + } + if (!win_valid_any_tab(win)) { + return FAIL; // window already closed by autocommands + } + } else { + emsg(e_floatonly); + return FAIL; + } + } // When closing the last window in a tab page first go to another tab page // and then close the window and the tab page to avoid that curwin and // curtab are invalid while we are freeing memory. - if (close_last_window_tabpage(win, free_buf, force, prev_curtab)) { + if (close_last_window_tabpage(win, free_buf, prev_curtab)) { return FAIL; } @@ -2774,7 +2770,7 @@ int win_close(win_T *win, bool free_buf, bool force) // Autocommands may have closed the window already, or closed the only // other window or moved to another tab page. if (!win_valid(win) || (!win->w_floating && last_window(win)) - || close_last_window_tabpage(win, free_buf, force, prev_curtab)) { + || close_last_window_tabpage(win, free_buf, prev_curtab)) { return FAIL; } diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index 6bc3fd14ec..65a7d359af 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -893,7 +893,7 @@ describe('float window', function() assert_alive() end) - it('does not crash if BufUnload makes it the only non-float in tabpage', function() + pending('does not crash if BufUnload makes it the only non-float in tabpage', function() exec([[ tabnew let g:buf = bufnr() @@ -909,10 +909,9 @@ describe('float window', function() assert_alive() end) - it('does not crash if WinClosed from floating windows closes it', function() + it('does not crash if WinClosed from floating window closes it', function() exec([[ tabnew - let g:buf = bufnr() new let s:win = win_getid() call nvim_win_set_config(s:win, -- cgit From 3c66e285cc4e7bc9953945caac91049463dc1d75 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 10 Mar 2024 10:19:15 +0800 Subject: vim-patch:a2c65809dafe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit runtime(java): Recognise string templates (vim/vim#14150) As this is encouraged in the referenced JEPs, "to visually distinguish a string template from a string literal, and a text block template from a text block", the default colours for java\%[Debug]StrTempl are made distinct from java\%[Debug]String. According to §3.2 Lexical Translations (JLS, c. 1996 or any more recent version), line terminators, white space, and comments are discarded before tokens are accepted. Since a template expression comprises a template processor, a dot, and a template, it may be visually appealing to break up its head across a few lines whenever its tail already spans multiple lines. Curiously, no allowance for it is made in the distributed tests for OpenJDK 21; the proposed regexp patterns take in consideration a line terminator and white space after a dot. References: https://openjdk.org/jeps/430 (Preview) https://openjdk.org/jeps/459 (Second Preview) https://openjdk.org/jeps/465 https://github.com/vim/vim/commit/a2c65809dafe5c4f45f278fddf368c7c971d83e9 Co-authored-by: Aliaksei Budavei <32549825+zzzyxwvut@users.noreply.github.com> --- runtime/syntax/java.vim | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/runtime/syntax/java.vim b/runtime/syntax/java.vim index f6d2660277..c059de603c 100644 --- a/runtime/syntax/java.vim +++ b/runtime/syntax/java.vim @@ -2,7 +2,7 @@ " Language: Java " Maintainer: Claudio Fleiner " URL: https://github.com/fleiner/vim/blob/master/runtime/syntax/java.vim -" Last Change: 2024 Mar 02 +" Last Change: 2024 Mar 06 " Please check :help java.vim for comments on some of the options available. @@ -187,8 +187,8 @@ if exists("java_comment_strings") syn match javaCommentCharacter contained "'\\[^']\{1,6\}'" contains=javaSpecialChar syn match javaCommentCharacter contained "'\\''" contains=javaSpecialChar syn match javaCommentCharacter contained "'[^\\]'" - syn cluster javaCommentSpecial add=javaCommentString,javaCommentCharacter,javaNumber - syn cluster javaCommentSpecial2 add=javaComment2String,javaCommentCharacter,javaNumber + syn cluster javaCommentSpecial add=javaCommentString,javaCommentCharacter,javaNumber,javaStrTempl + syn cluster javaCommentSpecial2 add=javaComment2String,javaCommentCharacter,javaNumber,javaStrTempl endif syn region javaComment start="/\*" end="\*/" contains=@javaCommentSpecial,javaTodo,@Spell @@ -234,6 +234,9 @@ syn match javaSpecialChar contained "\\\%(u\x\x\x\x\|[0-3]\o\o\|\o\o\=\|[bstn syn region javaString start=+"+ end=+"+ end=+$+ contains=javaSpecialChar,javaSpecialError,@Spell syn region javaString start=+"""[ \t\x0c\r]*$+hs=e+1 end=+"""+he=s-1 contains=javaSpecialChar,javaSpecialError,javaTextBlockError,@Spell syn match javaTextBlockError +"""\s*"""+ +syn region javaStrTemplEmbExp contained matchgroup=javaStrTempl start="\\{" end="}" contains=TOP +syn region javaStrTempl start=+\%(\.[[:space:]\n]*\)\@<="+ end=+"+ contains=javaStrTemplEmbExp,javaSpecialChar,javaSpecialError,@Spell +syn region javaStrTempl start=+\%(\.[[:space:]\n]*\)\@<="""[ \t\x0c\r]*$+hs=e+1 end=+"""+he=s-1 contains=javaStrTemplEmbExp,javaSpecialChar,javaSpecialError,javaTextBlockError,@Spell " The next line is commented out, it can cause a crash for a long line "syn match javaStringError +"\%([^"\\]\|\\.\)*$+ syn match javaCharacter "'[^']*'" contains=javaSpecialChar,javaSpecialCharError @@ -254,7 +257,7 @@ syn match javaNumber "\<0[xX]\%(\x\%(_*\x\)*\.\=\|\%(\x\%(_*\x\)*\)\=\.\x\%( " Unicode characters syn match javaSpecial "\\u\x\x\x\x" -syn cluster javaTop add=javaString,javaCharacter,javaNumber,javaSpecial,javaStringError,javaTextBlockError +syn cluster javaTop add=javaString,javaStrTempl,javaCharacter,javaNumber,javaSpecial,javaStringError,javaTextBlockError if exists("java_highlight_functions") if java_highlight_functions == "indent" @@ -280,7 +283,12 @@ if exists("java_highlight_debug") syn match javaDebugSpecial contained "\\\%(u\x\x\x\x\|[0-3]\o\o\|\o\o\=\|[bstnfr"'\\]\)" syn region javaDebugString contained start=+"+ end=+"+ contains=javaDebugSpecial syn region javaDebugString contained start=+"""[ \t\x0c\r]*$+hs=e+1 end=+"""+he=s-1 contains=javaDebugSpecial,javaDebugTextBlockError -" The next line is commented out, it can cause a crash for a long line + " The highlight groups of java{StrTempl,Debug{,Paren,StrTempl}}\, + " share one colour by default. Do not conflate unrelated parens. + syn region javaDebugStrTemplEmbExp contained matchgroup=javaDebugStrTempl start="\\{" end="}" contains=javaComment,javaLineComment,javaDebug\%(Paren\)\@!.* + syn region javaDebugStrTempl contained start=+\%(\.[[:space:]\n]*\)\@<="+ end=+"+ contains=javaDebugStrTemplEmbExp,javaDebugSpecial + syn region javaDebugStrTempl contained start=+\%(\.[[:space:]\n]*\)\@<="""[ \t\x0c\r]*$+hs=e+1 end=+"""+he=s-1 contains=javaDebugStrTemplEmbExp,javaDebugSpecial,javaDebugTextBlockError + " The next line is commented out, it can cause a crash for a long line " syn match javaDebugStringError contained +"\%([^"\\]\|\\.\)*$+ syn match javaDebugTextBlockError contained +"""\s*"""+ syn match javaDebugCharacter contained "'[^\\]'" @@ -307,6 +315,7 @@ if exists("java_highlight_debug") hi def link javaDebug Debug hi def link javaDebugString DebugString + hi def link javaDebugStrTempl Macro hi def link javaDebugStringError javaError hi def link javaDebugTextBlockError javaDebugStringError hi def link javaDebugType DebugType @@ -376,6 +385,7 @@ hi def link javaSpecial Special hi def link javaSpecialError Error hi def link javaSpecialCharError Error hi def link javaString String +hi def link javaStrTempl Macro hi def link javaCharacter Character hi def link javaSpecialChar SpecialChar hi def link javaNumber Number -- cgit From 84b6ae82c728403480cfb5b876643c7fe1c08515 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 10 Mar 2024 10:18:35 +0800 Subject: vim-patch:62b26040eb4b runtime(vim): Update base-syntax, improve :menu{,translate} highlighting (vim/vim#14162) Improve :menu and :menutranslate highlighting. - Match args to :menutranslation and :popup. - Only highlight special notation in {rhs} of :menu, like :map. - Allow line continuations in {rhs} of :menu and between {english} and {mylang} of :menutranslation, matching common usage. - Bug fixes. https://github.com/vim/vim/commit/62b26040eb4b6752be2c46852e8986083737a1bb Co-authored-by: dkearns --- runtime/syntax/vim.vim | 42 ++++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim index 2857146949..77e9735b78 100644 --- a/runtime/syntax/vim.vim +++ b/runtime/syntax/vim.vim @@ -154,7 +154,7 @@ syn match vimNumber '0[0-7]\+' skipwhite nextgroup=vimGlobal,vimSubst1,v syn match vimNumber '0[bB][01]\+' skipwhite nextgroup=vimGlobal,vimSubst1,vimCommand,vimComment,vim9Comment " All vimCommands are contained by vimIsCommand. {{{2 -syn cluster vimCmdList contains=vimAbb,vimAddress,vimAutoCmd,vimAugroup,vimBehave,vimEcho,vimEchoHL,vimExecute,vimIsCommand,vimExtCmd,vimFunction,vimGlobal,vimHighlight,vimLet,vimMap,vimMark,vimNotFunc,vimNorm,vimSet,vimSyntax,vimUnlet,vimUnmap,vimUserCmd +syn cluster vimCmdList contains=vimAbb,vimAddress,vimAutoCmd,vimAugroup,vimBehave,vimEcho,vimEchoHL,vimExecute,vimIsCommand,vimExtCmd,vimFunction,vimGlobal,vimHighlight,vimLet,vimMap,vimMark,vimNotFunc,vimNorm,vimSet,vimSyntax,vimUnlet,vimUnmap,vimUserCmd,vimMenu,vimMenutranslate syn match vimCmdSep "[:|]\+" skipwhite nextgroup=@vimCmdList,vimSubst1 syn match vimIsCommand "\<\%(\h\w*\|[23]mat\%[ch]\)\>" contains=vimCommand syn match vimVar contained "\<\h[a-zA-Z0-9#_]*\>" @@ -468,16 +468,28 @@ syn case match " Menus: {{{2 " ===== -syn cluster vimMenuList contains=vimMenuBang,vimMenuPriority,vimMenuName,vimMenuMod -" GEN_SYN_VIM: vimCommand menu, START_STR='syn keyword vimCommand', END_STR='skipwhite nextgroup=@vimMenuList' -syn keyword vimCommand am[enu] an[oremenu] aun[menu] cme[nu] cnoreme[nu] cunme[nu] ime[nu] inoreme[nu] iunme[nu] me[nu] nme[nu] nnoreme[nu] noreme[nu] nunme[nu] ome[nu] onoreme[nu] ounme[nu] sme[nu] snoreme[nu] sunme[nu] tlm[enu] tln[oremenu] tlu[nmenu] unme[nu] vme[nu] vnoreme[nu] vunme[nu] xme[nu] xnoreme[nu] xunme[nu] skipwhite nextgroup=@vimMenuList -syn match vimMenuName "[^ \t\\<]\+" contained nextgroup=vimMenuNameMore,vimMenuMap -syn match vimMenuPriority "\d\+\(\.\d\+\)*" contained skipwhite nextgroup=vimMenuName -syn match vimMenuNameMore "\c\\\s\|\|\\\." contained nextgroup=vimMenuName,vimMenuNameMore contains=vimNotation -syn match vimMenuMod contained "\c<\(script\|silent\)\+>" skipwhite contains=vimMapModKey,vimMapModErr nextgroup=@vimMenuList -syn match vimMenuMap "\s" contained skipwhite nextgroup=vimMenuRhs -syn match vimMenuRhs ".*$" contained contains=vimString,vimComment,vim9Comment,vimIsCommand -syn match vimMenuBang "!" contained skipwhite nextgroup=@vimMenuList +" NOTE: tail comments disallowed +" GEN_SYN_VIM: vimCommand menu, START_STR='syn keyword vimMenu', END_STR='skipwhite nextgroup=vimMenuBang,vimMenuMod,vimMenuName,vimMenuPriority,vimMenuStatus' +syn keyword vimMenu am[enu] an[oremenu] aun[menu] cme[nu] cnoreme[nu] cunme[nu] ime[nu] inoreme[nu] iunme[nu] me[nu] nme[nu] nnoreme[nu] noreme[nu] nunme[nu] ome[nu] onoreme[nu] ounme[nu] sme[nu] snoreme[nu] sunme[nu] tlm[enu] tln[oremenu] tlu[nmenu] tm[enu] tu[nmenu] unme[nu] vme[nu] vnoreme[nu] vunme[nu] xme[nu] xnoreme[nu] xunme[nu] skipwhite nextgroup=vimMenuBang,vimMenuMod,vimMenuName,vimMenuPriority,vimMenuStatus +syn keyword vimMenu popu[p] skipwhite nextgroup=vimMenuBang,vimMenuName +syn region vimMenuRhs contained contains=@vimContinue,vimNotation start="|\@!\S" skip=+\\\\\|\\|\|\n\s*\\\|\n\s*"\\ + end="$" matchgroup=vimSep end="|" +syn region vimMenuRhsContinue contained contains=@vimContinue,vimNotation start=+^\s*\%(\\\|"\\ \)+ skip=+\\\\\|\\|\|\n\s*\\\|\n\s*"\\ + end="$" matchgroup=vimSep end="|" +syn match vimMenuName "\%(\\\s\|\S\)\+" contained contains=vimMenuNotation,vimNotation skipwhite nextgroup=vimCmdSep,vimMenuRhs +syn match vimMenuName "\%(\\\s\|\S\)\+\ze\s*$" contained contains=vimMenuNotation,vimNotation skipwhite skipnl nextgroup=vimCmdSep,vimMenuRhsContinue +syn match vimMenuNotation "&\a\|&&\|\\\s\|\\\." contained +syn match vimMenuPriority "\<\d\+\%(\.\d\+\)*\>" contained skipwhite nextgroup=vimMenuName +syn match vimMenuMod "\c<\%(script\|silent\|special\)>" contained skipwhite nextgroup=vimMenuName,vimMenuPriority,vimMenuMod contains=vimMapModKey,vimMapModErr +syn keyword vimMenuStatus enable disable nextgroup=vimMenuName skipwhite +syn match vimMenuBang "\a\@1<=!" contained skipwhite nextgroup=vimMenuName,vimMenuMod + +syn region vimMenutranslate + \ matchgroup=vimCommand start="\" + \ skip=+\\\\\|\\|\|\n\s*\\\|\n\s*"\\ + + \ end="$" matchgroup=vimCmdSep end="|" matchgroup=vimMenuClear end="\\s\+[eE][nN][dD]" " ==================== @@ -1010,9 +1023,14 @@ if !exists("skip_vim_syntax_inits") hi def link vimMark Number hi def link vimMarkNumber vimNumber hi def link vimMenuBang vimBang + hi def link vimMenuClear Special hi def link vimMenuMod vimMapMod - hi def link vimMenuNameMore vimMenuName hi def link vimMenuName PreProc + hi def link vimMenu vimCommand + hi def link vimMenuNotation vimNotation + hi def link vimMenuPriority Number + hi def link vimMenuStatus Special + hi def link vimMenutranslateComment vimComment hi def link vimMtchComment vimComment hi def link vimNorm vimCommand hi def link vimNotation Special -- cgit From b5f870cf12c768984536cda2fd6b1519045b8c78 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 10 Mar 2024 10:21:34 +0800 Subject: vim-patch:5d67aef3060d runtime(vim): Update base-syntax, improve :map highlighting (vim/vim#14141) Improve :map command highlighting. - Fix multiline RHS matching, allow continued lines and comments. - Allow ^V-escaped whitespace in LHS. - Handle map-bar properly and allow trailing commands. Fixes issue vim/vim#12672. https://github.com/vim/vim/commit/5d67aef3060d6d3aa14d273c39f23d8a90c4cef1 Co-authored-by: dkearns --- runtime/syntax/vim.vim | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim index 77e9735b78..6d535c5e7e 100644 --- a/runtime/syntax/vim.vim +++ b/runtime/syntax/vim.vim @@ -457,11 +457,13 @@ syn keyword vimMap cmapc[lear] imapc[lear] lmapc[lear] nmapc[lear] omapc[lear] s syn keyword vimMap mapc[lear] skipwhite nextgroup=vimMapBang,vimMapMod " GEN_SYN_VIM: vimCommand unmap, START_STR='syn keyword vimUnmap', END_STR='skipwhite nextgroup=vimMapBang,vimMapMod,vimMapLhs' syn keyword vimUnmap cu[nmap] iu[nmap] lu[nmap] nun[map] ou[nmap] sunm[ap] tunma[p] unm[ap] vu[nmap] xu[nmap] skipwhite nextgroup=vimMapBang,vimMapMod,vimMapLhs -syn match vimMapLhs contained "\S\+" contains=vimNotation,vimCtrlChar skipwhite nextgroup=vimMapRhs -syn match vimMapBang contained "\a\@1<=!" skipwhite nextgroup=vimMapMod,vimMapLhs +syn match vimMapLhs contained "\%(.\|\S\)\+" contains=vimCtrlChar,vimNotation skipwhite nextgroup=vimMapRhs +syn match vimMapLhs contained "\%(.\|\S\)\+\ze\s*$" contains=vimCtrlChar,vimNotation skipwhite skipnl nextgroup=vimMapRhsContinue +syn match vimMapBang contained "\a\@1<=!" skipwhite nextgroup=vimMapMod,vimMapLhs syn match vimMapMod contained "\%#=1\c<\(buffer\|expr\|\(local\)\=leader\|nowait\|plug\|script\|sid\|unique\|silent\)\+>" contains=vimMapModKey,vimMapModErr skipwhite nextgroup=vimMapMod,vimMapLhs -syn match vimMapRhs contained ".*" contains=vimNotation,vimCtrlChar skipnl nextgroup=vimMapRhsExtend -syn match vimMapRhsExtend contained "^\s*\\.*$" contains=vimContinue +syn region vimMapRhs contained start="\S" skip=+\\|\|\@1<=|\|\n\s*\\\|\n\s*"\\ + end="|" end="$" contains=@vimContinue,vimCtrlChar,vimNotation skipnl nextgroup=vimMapRhsContinue +" assume a continuation comment introduces the RHS +syn region vimMapRhsContinue contained start=+^\s*\%(\\\|"\\ \)+ skip=+\\|\|\@1<=|\|\n\s*\\\|\n\s*"\\ + end="|" end="$" contains=@vimContinue,vimCtrlChar,vimNotation syn case ignore syn keyword vimMapModKey contained buffer expr leader localleader nowait plug script sid silent unique syn case match @@ -1134,4 +1136,4 @@ delc VimFoldr delc VimFoldt let &cpo = s:keepcpo unlet s:keepcpo -" vim:ts=18 fdm=marker +" vim:ts=18 fdm=marker ft=vim -- cgit From a441bdc936f9258851be3fa04c108c37e0a497ab Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 10 Mar 2024 16:58:01 +0800 Subject: vim-patch:9.1.0162: problem with writing extended attributes on failure (#27800) Problem: problem with writing extended attributes on failure Solution: Change return type to ssize_t and check listxattr's return value correctly on failure (Paul Tagliamonte) The existing logic will return when the listxattr call returns with the errno set to ENOTSUP (or a size of 0 bytes), without checking to see if listxattr actually failed. listxattr can fail with at least E2BIG, ENOTSUP, ERANGE, or anything that `stat(2)` can fail with (in my case; ENOENT from stat). The returned size is stored to a size_t, but the return type is a ssize_t. On failure, listxattr returns -1, which will get translated to size_t's MAX. If the listxattr call failed with anything other than ENOTSUP, this triggers a request for size_t MAX bytes. This means that, if the listxattr call fails with anything other than ENOTSUP on save, vim will error with `E342: Out of memory! (allocating 18446744073709551615 bytes)` (keen observers will note 18446744073709551615 is 0xffffffffffffffff) In reality, this is likely masking a different (usually filesystem?) error -- but at least it's an error being pushed to the user now, and we don't try to allocate size_t MAX bytes. I've opted to change the type that we store listxattr to from size_t to ssize_t, to match listxattr(2)'s signature, and to check for the -1 return value. Additionally, I've removed the errno check -- if we get a listxattr failure for any reason, we may as well bail without trying; it's not like we can even recover. closes: vim/vim#14169 https://github.com/vim/vim/commit/14759ded57447345ba11c11a99fd84344797862c Co-authored-by: Paul R. Tagliamonte --- src/nvim/os/fs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index ade745df2c..85caf4aa43 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -789,7 +789,7 @@ void os_copy_xattr(const char *from_file, const char *to_file) // get the length of the extended attributes ssize_t size = listxattr((char *)from_file, NULL, 0); // not supported or no attributes to copy - if (errno == ENOTSUP || size <= 0) { + if (size <= 0) { return; } char *xattr_buf = xmalloc((size_t)size); -- cgit From b465ede2c7a4fb39cf84682d645a3acd08631010 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 10 Mar 2024 17:08:00 +0800 Subject: vim-patch:9.1.0138: too many STRLEN calls when getting a memline (#27799) Problem: too many STRLEN calls when getting a memline Solution: Optimize calls to STRLEN(), add a few functions in memline.c that return the byte length instead of relying on STRLEN() (John Marriott) closes: vim/vim#14052 https://github.com/vim/vim/commit/02d7a6c6cfceb3faf9c98fcb7c458760cd50d269 Cherry-pick line break changes from patch 8.1.0226. Cherry-pick ml_line_len from patch 8.1.0579. Cherry-pick test_comments.vim change from patch 9.1.0153. Co-authored-by: John Marriott --- runtime/doc/dev_vimpatch.txt | 2 ++ src/nvim/change.c | 11 +++++++---- src/nvim/cursor.c | 12 ++++++++++++ src/nvim/edit.c | 10 +++++----- src/nvim/memline.c | 40 +++++++++++++++++++++++++++++++------- src/nvim/memline_defs.h | 1 + src/nvim/normal.c | 14 ++++++------- src/nvim/textformat.c | 3 +-- test/old/testdir/test_comments.vim | 6 ++++++ 9 files changed, 73 insertions(+), 26 deletions(-) diff --git a/runtime/doc/dev_vimpatch.txt b/runtime/doc/dev_vimpatch.txt index 96307dc7df..1f48324d46 100644 --- a/runtime/doc/dev_vimpatch.txt +++ b/runtime/doc/dev_vimpatch.txt @@ -209,6 +209,8 @@ information. utf_off2cells grid_off2cells ml_get_curline get_cursor_line_ptr ml_get_cursor get_cursor_pos_ptr + ml_get_curline_len get_cursor_line_len + ml_get_cursor_len get_cursor_pos_len screen_char ui_line screen_line grid_put_linebuf screen_* (most functions) grid_* diff --git a/src/nvim/change.c b/src/nvim/change.c index 1c7724f010..b914bc29fe 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -926,21 +926,24 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) count = oldlen - col; movelen = 1; } + colnr_T newlen = oldlen - count; // If the old line has been allocated the deletion can be done in the // existing line. Otherwise a new line has to be allocated. - bool was_alloced = ml_line_alloced(); // check if oldp was allocated + bool alloc_newp = !ml_line_alloced(); // check if oldp was allocated char *newp; - if (was_alloced) { + if (!alloc_newp) { ml_add_deleted_len(curbuf->b_ml.ml_line_ptr, oldlen); newp = oldp; // use same allocated memory } else { // need to allocate a new line - newp = xmalloc((size_t)(oldlen + 1 - count)); + newp = xmalloc((size_t)newlen + 1); memmove(newp, oldp, (size_t)col); } memmove(newp + col, oldp + col + count, (size_t)movelen); - if (!was_alloced) { + if (alloc_newp) { ml_replace(lnum, newp, false); + } else { + curbuf->b_ml.ml_line_len -= count; } // mark the buffer as changed and prepare for displaying diff --git a/src/nvim/cursor.c b/src/nvim/cursor.c index e93e658f1e..ab99d1b854 100644 --- a/src/nvim/cursor.c +++ b/src/nvim/cursor.c @@ -514,3 +514,15 @@ char *get_cursor_pos_ptr(void) { return ml_get_buf(curbuf, curwin->w_cursor.lnum) + curwin->w_cursor.col; } + +/// @return length (excluding the NUL) of the cursor line. +colnr_T get_cursor_line_len(void) +{ + return ml_get_buf_len(curbuf, curwin->w_cursor.lnum); +} + +/// @return length (excluding the NUL) of the cursor position. +colnr_T get_cursor_pos_len(void) +{ + return ml_get_buf_len(curbuf, curwin->w_cursor.lnum) - curwin->w_cursor.col; +} diff --git a/src/nvim/edit.c b/src/nvim/edit.c index b7b32883c2..54deb0f1c3 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -3785,9 +3785,10 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) if (has_format_option(FO_AUTO) && has_format_option(FO_WHITE_PAR)) { char *ptr = ml_get_buf_mut(curbuf, curwin->w_cursor.lnum); - int len = (int)strlen(ptr); + int len = get_cursor_line_len(); if (len > 0 && ptr[len - 1] == ' ') { ptr[len - 1] = NUL; + curbuf->b_ml.ml_line_len--; } } @@ -4411,13 +4412,13 @@ static bool ins_tab(void) if (i > 0) { STRMOVE(ptr, ptr + i); // correct replace stack. - if ((State & REPLACE_FLAG) - && !(State & VREPLACE_FLAG)) { + if ((State & REPLACE_FLAG) && !(State & VREPLACE_FLAG)) { for (temp = i; --temp >= 0;) { replace_join(repl_off); } } if (!(State & VREPLACE_FLAG)) { + curbuf->b_ml.ml_line_len -= i; inserted_bytes(fpos.lnum, change_col, cursor->col - change_col, fpos.col - change_col); } @@ -4462,8 +4463,7 @@ bool ins_eol(int c) // Strange Vi behaviour: In Replace mode, typing a NL will not delete the // character under the cursor. Only push a NUL on the replace stack, // nothing to put back when the NL is deleted. - if ((State & REPLACE_FLAG) - && !(State & VREPLACE_FLAG)) { + if ((State & REPLACE_FLAG) && !(State & VREPLACE_FLAG)) { replace_push(NUL); } diff --git a/src/nvim/memline.c b/src/nvim/memline.c index 6b2f26b2d8..a63c23f0a3 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -1174,7 +1174,7 @@ void ml_recover(bool checkext) } else { for (idx = 1; idx <= lnum; idx++) { // Need to copy one line, fetching the other one may flush it. - p = xstrdup(ml_get(idx)); + p = xstrnsave(ml_get(idx), (size_t)ml_get_len(idx)); int i = strcmp(p, ml_get(idx + lnum)); xfree(p); if (i != 0) { @@ -1834,6 +1834,22 @@ char *ml_get_pos(const pos_T *pos) return ml_get_buf(curbuf, pos->lnum) + pos->col; } +/// @return length (excluding the NUL) of the given line. +colnr_T ml_get_len(linenr_T lnum) +{ + return ml_get_buf_len(curbuf, lnum); +} + +/// @return length (excluding the NUL) of the given line in the given buffer. +colnr_T ml_get_buf_len(buf_T *buf, linenr_T lnum) +{ + if (*ml_get_buf(buf, lnum) == NUL) { + return 0; + } + + return buf->b_ml.ml_line_len - 1; +} + /// @return codepoint at pos. pos must be either valid or have col set to MAXCOL! int gchar_pos(pos_T *pos) FUNC_ATTR_NONNULL_ARG(1) @@ -1865,6 +1881,7 @@ static char *ml_get_buf_impl(buf_T *buf, linenr_T lnum, bool will_change) ml_flush_line(buf, false); errorret: STRCPY(questions, "???"); + buf->b_ml.ml_line_len = 4; buf->b_ml.ml_line_lnum = lnum; return questions; } @@ -1873,6 +1890,7 @@ errorret: } if (buf->b_ml.ml_mfp == NULL) { // there are no lines + buf->b_ml.ml_line_len = 1; return ""; } @@ -1903,8 +1921,14 @@ errorret: DataBlock *dp = hp->bh_data; - char *ptr = (char *)dp + (dp->db_index[lnum - buf->b_ml.ml_locked_low] & DB_INDEX_MASK); - buf->b_ml.ml_line_ptr = ptr; + int idx = lnum - buf->b_ml.ml_locked_low; + unsigned start = (dp->db_index[idx] & DB_INDEX_MASK); + // The text ends where the previous line starts. The first line ends + // at the end of the block. + unsigned end = idx == 0 ? dp->db_txt_end : (dp->db_index[idx - 1] & DB_INDEX_MASK); + + buf->b_ml.ml_line_ptr = (char *)dp + start; + buf->b_ml.ml_line_len = (colnr_T)(end - start); buf->b_ml.ml_line_lnum = lnum; buf->b_ml.ml_flags &= ~(ML_LINE_DIRTY | ML_ALLOCATED); } @@ -1922,7 +1946,8 @@ errorret: #ifdef ML_GET_ALLOC_LINES if ((buf->b_ml.ml_flags & (ML_LINE_DIRTY | ML_ALLOCATED)) == 0) { // make sure the text is in allocated memory - buf->b_ml.ml_line_ptr = xstrdup(buf->b_ml.ml_line_ptr); + buf->b_ml.ml_line_ptr = xmemdup(buf->b_ml.ml_line_ptr, + (size_t)buf->b_ml.ml_line_len); buf->b_ml.ml_flags |= ML_ALLOCATED; if (will_change) { // can't make the change in the data block @@ -2468,6 +2493,7 @@ int ml_replace_buf(buf_T *buf, linenr_T lnum, char *line, bool copy, bool noallo } buf->b_ml.ml_line_ptr = line; + buf->b_ml.ml_line_len = (colnr_T)strlen(line) + 1; buf->b_ml.ml_line_lnum = lnum; buf->b_ml.ml_flags = (buf->b_ml.ml_flags | ML_LINE_DIRTY) & ~ML_EMPTY; if (noalloc) { @@ -2765,7 +2791,7 @@ static void ml_flush_line(buf_T *buf, bool noalloc) } else { // text of previous line follows old_len = (int)(dp->db_index[idx - 1] & DB_INDEX_MASK) - start; } - colnr_T new_len = (colnr_T)strlen(new_line) + 1; + colnr_T new_len = buf->b_ml.ml_line_len; int extra = new_len - old_len; // negative if lines gets smaller // if new line fits in data block, replace directly @@ -3456,7 +3482,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_ char *const name = xmalloc(name_len); memcpy(name, sw_msg_1, sw_msg_1_len + 1); - home_replace(NULL, fname, &name[sw_msg_1_len], fname_len, true); + home_replace(NULL, fname, name + sw_msg_1_len, fname_len, true); xstrlcat(name, sw_msg_2, name_len); int dialog_result = do_dialog(VIM_WARNING, @@ -3734,7 +3760,7 @@ static void ml_updatechunk(buf_T *buf, linenr_T line, int len, int updtype) // First line in empty buffer from ml_flush_line() -- reset buf->b_ml.ml_usedchunks = 1; buf->b_ml.ml_chunksize[0].mlcs_numlines = 1; - buf->b_ml.ml_chunksize[0].mlcs_totalsize = (int)strlen(buf->b_ml.ml_line_ptr) + 1; + buf->b_ml.ml_chunksize[0].mlcs_totalsize = buf->b_ml.ml_line_len; return; } diff --git a/src/nvim/memline_defs.h b/src/nvim/memline_defs.h index 1a217c96d4..f675a2b15f 100644 --- a/src/nvim/memline_defs.h +++ b/src/nvim/memline_defs.h @@ -56,6 +56,7 @@ typedef struct { #define ML_ALLOCATED 0x10 // ml_line_ptr is an allocated copy int ml_flags; + colnr_T ml_line_len; // length of the cached line + NUL linenr_T ml_line_lnum; // line number of cached line, 0 if not valid char *ml_line_ptr; // pointer to cached line size_t ml_line_offset; // cached byte offset of ml_line_lnum diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 8ff47097fa..f586ad6704 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -3223,8 +3223,7 @@ static void nv_colon(cmdarg_T *cap) clearop(cap->oap); } else if (cap->oap->op_type != OP_NOP && (cap->oap->start.lnum > curbuf->b_ml.ml_line_count - || cap->oap->start.col > - (colnr_T)strlen(ml_get(cap->oap->start.lnum)) + || cap->oap->start.col > ml_get_len(cap->oap->start.lnum) || did_emsg)) { // The start of the operator has become invalid by the Ex command. clearopbeep(cap->oap); @@ -3592,7 +3591,7 @@ bool get_visual_text(cmdarg_T *cap, char **pp, size_t *lenp) } if (VIsual_mode == 'V') { *pp = get_cursor_line_ptr(); - *lenp = strlen(*pp); + *lenp = (size_t)get_cursor_line_len(); } else { if (lt(curwin->w_cursor, VIsual)) { *pp = ml_get_pos(&curwin->w_cursor); @@ -4527,9 +4526,8 @@ static void nv_replace(cmdarg_T *cap) } // Abort if not enough characters to replace. - char *ptr = get_cursor_pos_ptr(); - if (strlen(ptr) < (unsigned)cap->count1 - || (mb_charlen(ptr) < cap->count1)) { + if ((size_t)get_cursor_pos_len() < (unsigned)cap->count1 + || (mb_charlen(get_cursor_pos_ptr()) < cap->count1)) { clearopbeep(cap->oap); return; } @@ -5347,7 +5345,7 @@ static void nv_gi_cmd(cmdarg_T *cap) if (curbuf->b_last_insert.mark.lnum != 0) { curwin->w_cursor = curbuf->b_last_insert.mark; check_cursor_lnum(curwin); - int i = (int)strlen(get_cursor_line_ptr()); + int i = (int)get_cursor_line_len(); if (curwin->w_cursor.col > (colnr_T)i) { if (virtual_active()) { curwin->w_cursor.coladd += curwin->w_cursor.col - i; @@ -6036,7 +6034,7 @@ bool unadjust_for_sel(void) mark_mb_adjustpos(curbuf, pp); } else if (pp->lnum > 1) { pp->lnum--; - pp->col = (colnr_T)strlen(ml_get(pp->lnum)); + pp->col = ml_get_len(pp->lnum); return true; } } diff --git a/src/nvim/textformat.c b/src/nvim/textformat.c index bfe3ed5972..2cb08df7b5 100644 --- a/src/nvim/textformat.c +++ b/src/nvim/textformat.c @@ -86,8 +86,7 @@ void internal_format(int textwidth, int second_indent, int flags, bool format_on // When 'ai' is off we don't want a space under the cursor to be // deleted. Replace it with an 'x' temporarily. - if (!curbuf->b_p_ai - && !(State & VREPLACE_FLAG)) { + if (!curbuf->b_p_ai && !(State & VREPLACE_FLAG)) { cc = gchar_cursor(); if (ascii_iswhite(cc)) { save_char = (char)cc; diff --git a/test/old/testdir/test_comments.vim b/test/old/testdir/test_comments.vim index c34b85c42d..67454f477e 100644 --- a/test/old/testdir/test_comments.vim +++ b/test/old/testdir/test_comments.vim @@ -237,6 +237,12 @@ func Test_comment_autoformat() call feedkeys("aone\ntwo\n", 'xt') call assert_equal(['one', 'two', ''], getline(1, '$')) + set backspace=indent,eol,start + %d + call feedkeys("aone \n\", 'xt') + call assert_equal(['one'], getline(1, '$')) + set backspace& + close! endfunc -- cgit From 06fcf71bd0953baf9dc6d4c4bddf586c448f5ca6 Mon Sep 17 00:00:00 2001 From: Oscar Creator Date: Sat, 9 Mar 2024 17:10:58 +0100 Subject: fix(fswatch): --latency is locale dependent --- runtime/lua/vim/_watch.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/runtime/lua/vim/_watch.lua b/runtime/lua/vim/_watch.lua index 97c5481ad1..cf2689861a 100644 --- a/runtime/lua/vim/_watch.lua +++ b/runtime/lua/vim/_watch.lua @@ -303,6 +303,8 @@ function M.fswatch(path, opts, callback) fswatch_output_handler(line, opts, callback) end end, + -- --latency is locale dependent but tostring() isn't and will always have '.' as decimal point. + env = { LC_NUMERIC = 'C' }, }) return function() -- cgit From 92d4dbbd8cd330606d8a4e1ce1fc550eb6a70d9b Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 11 Mar 2024 06:21:32 +0800 Subject: vim-patch:9.1.0164: Internal error when passing invalid position to getregion() (#27805) Problem: Internal error or crash when passing invalid position to getregion(). Solution: Give an error for invalid position (zeertzjq). closes: vim/vim#14172 https://github.com/vim/vim/commit/26dd09ad5e86f4e2179be0181421bfab9a6b3b75 --- src/nvim/eval/funcs.c | 26 +++++++++++++++++--- src/nvim/globals.h | 1 + test/old/testdir/test_visual.vim | 52 +++++++++++++++++++++++++++------------- 3 files changed, 60 insertions(+), 19 deletions(-) diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 2f9472f158..f37542890b 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -2859,20 +2859,40 @@ static void f_getregion(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } else if (type[0] == Ctrl_V && type[1] == NUL) { region_type = kMTBlockWise; } else { + semsg(_(e_invargNval), "type", type); return; } buf_T *const save_curbuf = curbuf; + buf_T *findbuf = curbuf; if (fnum1 != 0) { - buf_T *findbuf = buflist_findnr(fnum1); + findbuf = buflist_findnr(fnum1); // buffer not loaded if (findbuf == NULL || findbuf->b_ml.ml_mfp == NULL) { + emsg(_(e_buffer_is_not_loaded)); return; } - curbuf = findbuf; } + if (p1.lnum < 1 || p1.lnum > findbuf->b_ml.ml_line_count) { + semsg(_(e_invalid_line_number_nr), p1.lnum); + return; + } + if (p1.col < 1 || p1.col > ml_get_buf_len(findbuf, p1.lnum) + 1) { + semsg(_(e_invalid_column_number_nr), p1.col); + return; + } + if (p2.lnum < 1 || p2.lnum > findbuf->b_ml.ml_line_count) { + semsg(_(e_invalid_line_number_nr), p2.lnum); + return; + } + if (p2.col < 1 || p2.col > ml_get_buf_len(findbuf, p2.lnum) + 1) { + semsg(_(e_invalid_column_number_nr), p2.col); + return; + } + + curbuf = findbuf; const TriState save_virtual = virtual_op; virtual_op = virtual_active(); @@ -2900,7 +2920,7 @@ static void f_getregion(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) mark_mb_adjustpos(curbuf, &p2); } else if (p2.lnum > 1) { p2.lnum--; - p2.col = (colnr_T)strlen(ml_get(p2.lnum)); + p2.col = ml_get_len(p2.lnum); if (p2.col > 0) { p2.col--; mark_mb_adjustpos(curbuf, &p2); diff --git a/src/nvim/globals.h b/src/nvim/globals.h index c1c9ae456c..113985cb52 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -958,6 +958,7 @@ EXTERN const char e_highlight_group_name_invalid_char[] INIT(= N_("E5248: Invali EXTERN const char e_highlight_group_name_too_long[] INIT(= N_("E1249: Highlight group name too long")); +EXTERN const char e_invalid_column_number_nr[] INIT( = N_("E964: Invalid column number: %ld")); EXTERN const char e_invalid_line_number_nr[] INIT(= N_("E966: Invalid line number: %ld")); EXTERN const char e_stray_closing_curly_str[] diff --git a/test/old/testdir/test_visual.vim b/test/old/testdir/test_visual.vim index 4c5ab9f61f..74742abc5d 100644 --- a/test/old/testdir/test_visual.vim +++ b/test/old/testdir/test_visual.vim @@ -1741,40 +1741,60 @@ func Test_visual_getregion() \ getregion(getpos('v'), getpos('.'), {'type': "\" })) set virtualedit& - #" Invalid position + #" using wrong types for positions call cursor(1, 1) call feedkeys("\vjj$", 'tx') call assert_fails("call getregion(1, 2)", 'E1211:') call assert_fails("call getregion(getpos('.'), {})", 'E1211:') - call assert_equal([], getregion(getpos('.'), getpos('.'), {'type': '' })) - - #" using the wrong type call assert_fails(':echo "."->getpos()->getregion("$", [])', 'E1211:') + #" using invalid value for "type" + call assert_fails("call getregion(getpos('.'), getpos('.'), {'type': '' })", 'E475:') + #" using a mark from another buffer to current buffer new - VAR newbuf = bufnr() + LET g:buf = bufnr() call setline(1, range(10)) normal! GmA wincmd p - call assert_equal([newbuf, 10, 1, 0], getpos("'A")) + call assert_equal([g:buf, 10, 1, 0], getpos("'A")) call assert_equal([], getregion(getpos('.'), getpos("'A"), {'type': 'v' })) call assert_equal([], getregion(getpos("'A"), getpos('.'), {'type': 'v' })) - exe $':{newbuf}bwipe!' - #" using a mark from another buffer to another buffer - new - VAR anotherbuf = bufnr() - call setline(1, range(10)) - normal! GmA + #" using two marks from another buffer + wincmd p normal! GmB wincmd p - call assert_equal([anotherbuf, 10, 1, 0], getpos("'A")) + call assert_equal([g:buf, 10, 1, 0], getpos("'B")) call assert_equal(['9'], getregion(getpos("'B"), getpos("'A"), {'type': 'v' })) - exe $':{anotherbuf}bwipe!' + + #" using two positions from another buffer + for type in ['v', 'V', "\"] + for exclusive in [v:false, v:true] + call assert_equal(range(10)->mapnew('string(v:val)'), + \ getregion([g:buf, 1, 1, 0], [g:buf, 10, 2, 0]), + \ {'type': type, 'exclusive': exclusive }) + call assert_equal(range(10)->mapnew('string(v:val)'), + \ getregion([g:buf, 10, 2, 0], [g:buf, 1, 1, 0]), + \ {'type': type, 'exclusive': exclusive }) + endfor + endfor + + #" using invalid positions in buffer + call assert_fails('call getregion([g:buf, 0, 1, 0], [g:buf, 10, 2, 0])', 'E966:') + call assert_fails('call getregion([g:buf, 10, 2, 0], [g:buf, 0, 1, 0])', 'E966:') + call assert_fails('call getregion([g:buf, 1, 1, 0], [g:buf, 11, 2, 0])', 'E966:') + call assert_fails('call getregion([g:buf, 11, 2, 0], [g:buf, 1, 1, 0])', 'E966:') + call assert_fails('call getregion([g:buf, 1, 1, 0], [g:buf, 10, 0, 0])', 'E964:') + call assert_fails('call getregion([g:buf, 10, 0, 0], [g:buf, 1, 1, 0])', 'E964:') + call assert_fails('call getregion([g:buf, 1, 1, 0], [g:buf, 10, 3, 0])', 'E964:') + call assert_fails('call getregion([g:buf, 10, 3, 0], [g:buf, 1, 1, 0])', 'E964:') #" using invalid buffer - call assert_equal([], getregion([10000, 10, 1, 0], [10000, 10, 1, 0])) + call assert_fails('call getregion([10000, 10, 1, 0], [10000, 10, 1, 0])', 'E681:') + + exe $':{g:buf}bwipe!' + unlet g:buf END call CheckLegacyAndVim9Success(lines) @@ -1935,7 +1955,7 @@ func Test_getregion_invalid_buf() call assert_equal(['Move around:'], getregion(getpos("'A"), getpos("'B"))) " close the help window q - call assert_equal([], getregion(getpos("'A"), getpos("'B"))) + call assert_fails("call getregion(getpos(\"'A\"), getpos(\"'B\"))", 'E681:') bwipe! endfunc -- cgit From 47942db30780b22774e810e1d0ade96bb6ba2da4 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 11 Mar 2024 06:41:53 +0800 Subject: vim-patch:675cbfb47f03 (#27806) runtime(doc): Update Markdown syntax, add missing configs fixes: vim/vim#14168 https://github.com/vim/vim/commit/675cbfb47f03c65b2a5c245b632bdd7a0bf10e4f Co-authored-by: Christian Brabandt --- runtime/doc/syntax.txt | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt index c02752a2b7..4b99fbface 100644 --- a/runtime/doc/syntax.txt +++ b/runtime/doc/syntax.txt @@ -1764,10 +1764,19 @@ MARKDOWN *ft-markdown-syntax* If you have long regions there might be wrong highlighting. At the cost of slowing down displaying, you can have the engine look further back to sync on -the start of a region, for example 500 lines: > +the start of a region, for example 500 lines (default is 50): > :let g:markdown_minlines = 500 +If you want to enable fenced code block syntax highlighting in your markdown +documents you can enable like this: > + + :let g:markdown_fenced_languages = ['html', 'python', 'bash=sh'] + +To disable markdown syntax concealing add the following to your vimrc: > + + :let g:markdown_syntax_conceal = 0 + MATHEMATICA *mma.vim* *ft-mma-syntax* *ft-mathematica-syntax* -- cgit From 09a919f313ec8ae691798e45ee459a4467ce5d6a Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Sat, 30 Dec 2023 21:08:07 -0800 Subject: docs: more accurate typing for vim.tbl_extend --- runtime/doc/lua.txt | 8 ++++---- runtime/lua/vim/shared.lua | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 367b5c36d2..89f62126bb 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -2190,8 +2190,8 @@ vim.tbl_deep_extend({behavior}, {...}) *vim.tbl_deep_extend()* Merges recursively two or more tables. Parameters: ~ - • {behavior} (`"error"|"keep"|"force"`) (string) Decides what to do if - a key is found in more than one map: + • {behavior} (`'error'|'keep'|'force'`) Decides what to do if a key is + found in more than one map: • "error": raise an error • "keep": use value from the leftmost map • "force": use value from the rightmost map @@ -2207,8 +2207,8 @@ vim.tbl_extend({behavior}, {...}) *vim.tbl_extend()* Merges two or more tables. Parameters: ~ - • {behavior} (`string`) Decides what to do if a key is found in more - than one map: + • {behavior} (`'error'|'keep'|'force'`) Decides what to do if a key is + found in more than one map: • "error": raise an error • "keep": use value from the leftmost map • "force": use value from the rightmost map diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index bd553598c7..a9eebf36da 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -402,7 +402,7 @@ end --- ---@see |extend()| --- ----@param behavior string Decides what to do if a key is found in more than one map: +---@param behavior 'error'|'keep'|'force' Decides what to do if a key is found in more than one map: --- - "error": raise an error --- - "keep": use value from the leftmost map --- - "force": use value from the rightmost map @@ -418,7 +418,7 @@ end --- ---@generic T1: table ---@generic T2: table ----@param behavior "error"|"keep"|"force" (string) Decides what to do if a key is found in more than one map: +---@param behavior 'error'|'keep'|'force' Decides what to do if a key is found in more than one map: --- - "error": raise an error --- - "keep": use value from the leftmost map --- - "force": use value from the rightmost map -- cgit From a09ddd7ce55037edc9747a682810fba6a26bc201 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Sat, 9 Mar 2024 12:21:01 +0000 Subject: docs(editorconfig): move to source --- runtime/doc/editorconfig.txt | 106 ++++++++++---------- runtime/doc/lua.txt | 13 +++ runtime/lua/editorconfig.lua | 197 ++++++++++++++++++++++++-------------- runtime/lua/vim/_editor.lua | 1 - runtime/lua/vim/_meta/builtin.lua | 1 - scripts/gen_vimdoc.lua | 70 +++++++++----- scripts/text_utils.lua | 8 +- 7 files changed, 243 insertions(+), 153 deletions(-) diff --git a/runtime/doc/editorconfig.txt b/runtime/doc/editorconfig.txt index a2281a7b7c..c7011cfbba 100644 --- a/runtime/doc/editorconfig.txt +++ b/runtime/doc/editorconfig.txt @@ -3,7 +3,7 @@ NVIM REFERENCE MANUAL - +============================================================================== EditorConfig integration *editorconfig* Nvim supports EditorConfig. When a file is opened, Nvim searches all parent @@ -13,69 +13,23 @@ entire (recursive) directory. For more information see https://editorconfig.org/. *g:editorconfig* *b:editorconfig* -EditorConfig is enabled by default. To disable it, add to your config: >lua +EditorConfig is enabled by default. To disable it, add to your config: >lua vim.g.editorconfig = false < + (Vimscript: `let g:editorconfig = v:false`). It can also be disabled per-buffer by setting the |b:editorconfig| buffer-local variable to `false`. Nvim stores the applied properties in |b:editorconfig| if it is not `false`. - *editorconfig-properties* -The following properties are supported by default: - - *editorconfig_root* -root If "true", then stop searching for .editorconfig files - in parent directories. This property must be at the - top-level of the .editorconfig file (i.e. it must not - be within a glob section). - - *editorconfig_charset* -charset One of "utf-8", "utf-8-bom", "latin1", "utf-16be", or - "utf-16le". Sets the 'fileencoding' and 'bomb' - options. - - *editorconfig_end_of_line* -end_of_line One of "lf", "crlf", or "cr". These correspond to - setting 'fileformat' to "unix", "dos", or "mac", - respectively. - - *editorconfig_indent_style* -indent_style One of "tab" or "space". Sets the 'expandtab' option. - - *editorconfig_indent_size* -indent_size A number indicating the size of a single indent. - Alternatively, use the value "tab" to use the value of - the tab_width property. Sets the 'shiftwidth' and - 'softtabstop' options. - If this value is not "tab" and the tab_width property - is not set, 'tabstop' is also set to this value. - - *editorconfig_insert_final_newline* -insert_final_newline "true" or "false" to ensure the file always has a - trailing newline as its last byte. Sets the - 'fixendofline' and 'endofline' options. - - *editorconfig_max_line_length* -max_line_length A number indicating the maximum length of a single - line. Sets the 'textwidth' option. - - *editorconfig_tab_width* -tab_width The display size of a single tab character. Sets the - 'tabstop' option. - - *editorconfig_trim_trailing_whitespace* -trim_trailing_whitespace - When "true", trailing whitespace is automatically - removed when the buffer is written. - *editorconfig-custom-properties* + New properties can be added by adding a new entry to the "properties" table. The table key is a property name and the value is a callback function which -accepts the number of the buffer to be modified, the value of the property -in the .editorconfig file, and (optionally) a table containing all of the -other properties and their values (useful for properties which depend on other +accepts the number of the buffer to be modified, the value of the property in +the `.editorconfig` file, and (optionally) a table containing all of the other +properties and their values (useful for properties which depend on other properties). The value is always a string and must be coerced if necessary. Example: >lua @@ -86,4 +40,48 @@ Example: >lua vim.b[bufnr].foo = val end < - vim:tw=78:ts=8:et:sw=4:ft=help:norl: + + *editorconfig-properties* + +The following properties are supported by default: + + +charset *editorconfig.charset* + One of `"utf-8"`, `"utf-8-bom"`, `"latin1"`, `"utf-16be"`, or + `"utf-16le"`. Sets the 'fileencoding' and 'bomb' options. + +end_of_line *editorconfig.end_of_line* + One of `"lf"`, `"crlf"`, or `"cr"`. These correspond to setting + 'fileformat' to "unix", "dos", or "mac", respectively. + +indent_size *editorconfig.indent_size* + A number indicating the size of a single indent. Alternatively, use the + value "tab" to use the value of the tab_width property. Sets the + 'shiftwidth' and 'softtabstop' options. If this value is not "tab" and the + tab_width property is not set, 'tabstop' is also set to this value. + +indent_style *editorconfig.indent_style* + One of `"tab"` or `"space"`. Sets the 'expandtab' option. + +insert_final_newline *editorconfig.insert_final_newline* + `"true"` or `"false"` to ensure the file always has a trailing newline as + its last byte. Sets the 'fixendofline' and 'endofline' options. + +max_line_length *editorconfig.max_line_length* + A number indicating the maximum length of a single line. Sets the + 'textwidth' option. + +root *editorconfig.root* + If "true", then stop searching for `.editorconfig` files in parent + directories. This property must be at the top-level of the `.editorconfig` + file (i.e. it must not be within a glob section). + +tab_width *editorconfig.tab_width* + The display size of a single tab character. Sets the 'tabstop' option. + +trim_trailing_whitespace *editorconfig.trim_trailing_whitespace* + When `"true"`, trailing whitespace is automatically removed when the + buffer is written. + + + vim:tw=78:ts=8:sw=4:sts=4:et:ft=help:norl: diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 89f62126bb..e1e3f88a1d 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -1161,6 +1161,7 @@ Lua list copies the list object to Vimscript and does NOT modify the Lua list: > vim.print(list) --> "{ 1, 2, 3 }" < + vim.call({func}, {...}) *vim.call()* Invokes |vim-function| or |user-function| {func} with arguments {...}. See also |vim.fn|. @@ -1239,6 +1240,7 @@ vim.v *vim.v* |v:| variables. Invalid or unset key returns `nil`. + *lua-options* *lua-vim-options* *lua-vim-set* @@ -1262,6 +1264,7 @@ window-scoped options. Note that this must NOT be confused with |local-options| and |:setlocal|. There is also |vim.go| that only accesses the global value of a |global-local| option, see |:setglobal|. + *vim.opt_local* *vim.opt_global* *vim.opt* @@ -3897,6 +3900,7 @@ Iter:enumerate() *Iter:enumerate()* < Example: >lua + local it = vim.iter(vim.gsplit('abc', '')):enumerate() it:next() -- 1 'a' @@ -3931,6 +3935,7 @@ Iter:find({f}) *Iter:find()* found. Examples: >lua + local it = vim.iter({ 3, 6, 9, 12 }) it:find(12) -- 12 @@ -4011,6 +4016,7 @@ Iter:last() *Iter:last()* Drains the iterator and returns the last item. Example: >lua + local it = vim.iter(vim.gsplit('abcdefg', '')) it:last() -- 'g' @@ -4051,6 +4057,7 @@ Iter:next() *Iter:next()* Gets the next value from the iterator. Example: >lua + local it = vim.iter(string.gmatch('1 2 3', '%d+')):map(tonumber) it:next() -- 1 @@ -4082,6 +4089,7 @@ Iter:nth({n}) *Iter:nth()* Gets the nth value of an iterator (and advances to it). Example: >lua + local it = vim.iter({ 3, 6, 9, 12 }) it:nth(2) -- 6 @@ -4099,6 +4107,7 @@ Iter:nthback({n}) *Iter:nthback()* Gets the nth value from the end of a |list-iterator| (and advances to it). Example: >lua + local it = vim.iter({ 3, 6, 9, 12 }) it:nthback(2) -- 9 @@ -4116,6 +4125,7 @@ Iter:peek() *Iter:peek()* Gets the next value in a |list-iterator| without consuming it. Example: >lua + local it = vim.iter({ 3, 6, 9, 12 }) it:peek() -- 3 @@ -4150,6 +4160,7 @@ Iter:rev() *Iter:rev()* Reverses a |list-iterator| pipeline. Example: >lua + local it = vim.iter({ 3, 6, 9, 12 }):rev() it:totable() -- { 12, 9, 6, 3 } @@ -4166,6 +4177,7 @@ Iter:rfind({f}) *Iter:rfind()* found. Examples: >lua + local it = vim.iter({ 1, 2, 3, 2, 1 }):enumerate() it:rfind(1) -- 5 1 @@ -4186,6 +4198,7 @@ Iter:skip({n}) *Iter:skip()* Skips `n` values of an iterator pipeline. Example: >lua + local it = vim.iter({ 3, 6, 9, 12 }):skip(2) it:next() -- 9 diff --git a/runtime/lua/editorconfig.lua b/runtime/lua/editorconfig.lua index 49d63807a6..6c5c820b0c 100644 --- a/runtime/lua/editorconfig.lua +++ b/runtime/lua/editorconfig.lua @@ -1,31 +1,80 @@ -local M = {} +--- @brief +--- Nvim supports EditorConfig. When a file is opened, Nvim searches all parent +--- directories of that file for ".editorconfig" files, parses them, and applies +--- any properties that match the opened file. Think of it like 'modeline' for an +--- entire (recursive) directory. For more information see +--- https://editorconfig.org/. +--- + +--- @brief [g:editorconfig]() [b:editorconfig]() +--- +--- EditorConfig is enabled by default. To disable it, add to your config: +--- ```lua +--- vim.g.editorconfig = false +--- ``` +--- +--- (Vimscript: `let g:editorconfig = v:false`). It can also be disabled +--- per-buffer by setting the [b:editorconfig] buffer-local variable to `false`. +--- +--- Nvim stores the applied properties in [b:editorconfig] if it is not `false`. + +--- @brief [editorconfig-custom-properties]() +--- +--- New properties can be added by adding a new entry to the "properties" table. +--- The table key is a property name and the value is a callback function which +--- accepts the number of the buffer to be modified, the value of the property +--- in the `.editorconfig` file, and (optionally) a table containing all of the +--- other properties and their values (useful for properties which depend on other +--- properties). The value is always a string and must be coerced if necessary. +--- Example: +--- +--- ```lua +--- +--- require('editorconfig').properties.foo = function(bufnr, val, opts) +--- if opts.charset and opts.charset ~= "utf-8" then +--- error("foo can only be set when charset is utf-8", 0) +--- end +--- vim.b[bufnr].foo = val +--- end +--- +--- ``` + +--- @brief [editorconfig-properties]() +--- +--- The following properties are supported by default: --- @type table -M.properties = {} +local properties = {} +--- @private --- Modified version of the builtin assert that does not include error position information --- ----@param v any Condition ----@param message string Error message to display if condition is false or nil ----@return any v if not false or nil, otherwise an error is displayed ---- ----@private +--- @param v any Condition +--- @param message string Error message to display if condition is false or nil +--- @return any v if not false or nil, otherwise an error is displayed local function assert(v, message) return v or error(message, 0) end +--- @private --- Show a warning message ---- ----@param msg string Message to show ---- ----@private +--- @param msg string Message to show local function warn(msg, ...) - vim.notify_once(string.format(msg, ...), vim.log.levels.WARN, { + vim.notify_once(msg:format(...), vim.log.levels.WARN, { title = 'editorconfig', }) end -function M.properties.charset(bufnr, val) +--- If "true", then stop searching for `.editorconfig` files in parent +--- directories. This property must be at the top-level of the +--- `.editorconfig` file (i.e. it must not be within a glob section). +function properties.root() + -- Unused +end + +--- One of `"utf-8"`, `"utf-8-bom"`, `"latin1"`, `"utf-16be"`, or `"utf-16le"`. +--- Sets the 'fileencoding' and 'bomb' options. +function properties.charset(bufnr, val) assert( vim.list_contains({ 'utf-8', 'utf-8-bom', 'latin1', 'utf-16be', 'utf-16le' }, val), 'charset must be one of "utf-8", "utf-8-bom", "latin1", "utf-16be", or "utf-16le"' @@ -40,14 +89,18 @@ function M.properties.charset(bufnr, val) end end -function M.properties.end_of_line(bufnr, val) +--- One of `"lf"`, `"crlf"`, or `"cr"`. +--- These correspond to setting 'fileformat' to "unix", "dos", or "mac", +--- respectively. +function properties.end_of_line(bufnr, val) vim.bo[bufnr].fileformat = assert( ({ lf = 'unix', crlf = 'dos', cr = 'mac' })[val], 'end_of_line must be one of "lf", "crlf", or "cr"' ) end -function M.properties.indent_style(bufnr, val, opts) +--- One of `"tab"` or `"space"`. Sets the 'expandtab' option. +function properties.indent_style(bufnr, val, opts) assert(val == 'tab' or val == 'space', 'indent_style must be either "tab" or "space"') vim.bo[bufnr].expandtab = val == 'space' if val == 'tab' and not opts.indent_size then @@ -56,7 +109,11 @@ function M.properties.indent_style(bufnr, val, opts) end end -function M.properties.indent_size(bufnr, val, opts) +--- A number indicating the size of a single indent. Alternatively, use the +--- value "tab" to use the value of the tab_width property. Sets the +--- 'shiftwidth' and 'softtabstop' options. If this value is not "tab" and +--- the tab_width property is not set, 'tabstop' is also set to this value. +function properties.indent_size(bufnr, val, opts) if val == 'tab' then vim.bo[bufnr].shiftwidth = 0 vim.bo[bufnr].softtabstop = 0 @@ -70,11 +127,14 @@ function M.properties.indent_size(bufnr, val, opts) end end -function M.properties.tab_width(bufnr, val) +--- The display size of a single tab character. Sets the 'tabstop' option. +function properties.tab_width(bufnr, val) vim.bo[bufnr].tabstop = assert(tonumber(val), 'tab_width must be a number') end -function M.properties.max_line_length(bufnr, val) +--- A number indicating the maximum length of a single +--- line. Sets the 'textwidth' option. +function properties.max_line_length(bufnr, val) local n = tonumber(val) if n then vim.bo[bufnr].textwidth = n @@ -84,7 +144,8 @@ function M.properties.max_line_length(bufnr, val) end end -function M.properties.trim_trailing_whitespace(bufnr, val) +--- When `"true"`, trailing whitespace is automatically removed when the buffer is written. +function properties.trim_trailing_whitespace(bufnr, val) assert( val == 'true' or val == 'false', 'trim_trailing_whitespace must be either "true" or "false"' @@ -109,7 +170,9 @@ function M.properties.trim_trailing_whitespace(bufnr, val) end end -function M.properties.insert_final_newline(bufnr, val) +--- `"true"` or `"false"` to ensure the file always has a trailing newline as its last byte. +--- Sets the 'fixendofline' and 'endofline' options. +function properties.insert_final_newline(bufnr, val) assert(val == 'true' or val == 'false', 'insert_final_newline must be either "true" or "false"') vim.bo[bufnr].fixendofline = val == 'true' @@ -128,63 +191,56 @@ function M.properties.insert_final_newline(bufnr, val) end end ---- Modified version of |glob2regpat()| that does not match path separators on *. ---- ---- This function replaces single instances of * with the regex pattern [^/]*. However, the star in ---- the replacement pattern also gets interpreted by glob2regpat, so we insert a placeholder, pass ---- it through glob2regpat, then replace the placeholder with the actual regex pattern. +--- @private +--- Modified version of [glob2regpat()] that does not match path separators on `*`. --- ----@param glob string Glob to convert into a regular expression ----@return string Regular expression +--- This function replaces single instances of `*` with the regex pattern `[^/]*`. +--- However, the star in the replacement pattern also gets interpreted by glob2regpat, +--- so we insert a placeholder, pass it through glob2regpat, then replace the +--- placeholder with the actual regex pattern. --- ----@private +--- @param glob string Glob to convert into a regular expression +--- @return string regex Regular expression local function glob2regpat(glob) local placeholder = '@@PLACEHOLDER@@' - return ( - string.gsub( - vim.fn.glob2regpat( - vim.fn.substitute( - string.gsub(glob, '{(%d+)%.%.(%d+)}', '[%1-%2]'), - '\\*\\@ Table of options to apply to the given file ---- ----@private +--- @private +--- Parse options from an `.editorconfig` file +--- @param filepath string File path of the file to apply EditorConfig settings to +--- @param dir string Current directory +--- @return table Table of options to apply to the given file local function parse(filepath, dir) local pat --- @type vim.regex? local opts = {} --- @type table @@ -215,11 +271,11 @@ local function parse(filepath, dir) return opts end ---- Configure the given buffer with options from an .editorconfig file ---- ----@param bufnr integer Buffer number to configure ---- ----@private +local M = {} + +--- @private +--- Configure the given buffer with options from an `.editorconfig` file +--- @param bufnr integer Buffer number to configure function M.config(bufnr) bufnr = bufnr or vim.api.nvim_get_current_buf() if not vim.api.nvim_buf_is_valid(bufnr) then @@ -247,8 +303,9 @@ function M.config(bufnr) local applied = {} --- @type table for opt, val in pairs(opts) do if val ~= 'unset' then - local func = M.properties[opt] + local func = properties[opt] if func then + --- @type boolean, string? local ok, err = pcall(func, bufnr, val, opts) if ok then applied[opt] = val diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 6cf77b4648..f527fc194c 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -74,7 +74,6 @@ vim.log = { --- Examples: --- --- ```lua ---- --- local on_exit = function(obj) --- print(obj.code) --- print(obj.signal) diff --git a/runtime/lua/vim/_meta/builtin.lua b/runtime/lua/vim/_meta/builtin.lua index 9a67667f02..ef9821fa32 100644 --- a/runtime/lua/vim/_meta/builtin.lua +++ b/runtime/lua/vim/_meta/builtin.lua @@ -215,7 +215,6 @@ function vim.schedule(fn) end --- Examples: --- --- ```lua ---- --- --- --- -- Wait for 100 ms, allowing other events to process --- vim.wait(100, function() end) diff --git a/scripts/gen_vimdoc.lua b/scripts/gen_vimdoc.lua index 22df411a35..b3b211d5a6 100755 --- a/scripts/gen_vimdoc.lua +++ b/scripts/gen_vimdoc.lua @@ -94,12 +94,12 @@ end local function fn_helptag_fmt_common(fun) local fn_sfx = fun.table and '' or '()' if fun.classvar then - return fmt('*%s:%s%s*', fun.classvar, fun.name, fn_sfx) + return fmt('%s:%s%s', fun.classvar, fun.name, fn_sfx) end if fun.module then - return fmt('*%s.%s%s*', fun.module, fun.name, fn_sfx) + return fmt('%s.%s%s', fun.module, fun.name, fn_sfx) end - return fmt('*%s%s*', fun.name, fn_sfx) + return fun.name .. fn_sfx end --- @type table @@ -129,7 +129,7 @@ local config = { return name .. ' Functions' end, helptag_fmt = function(name) - return fmt('*api-%s*', name:lower()) + return fmt('api-%s', name:lower()) end, }, lua = { @@ -241,22 +241,22 @@ local config = { end, helptag_fmt = function(name) if name == '_editor' then - return '*lua-vim*' + return 'lua-vim' elseif name == '_options' then - return '*lua-vimscript*' + return 'lua-vimscript' elseif name == 'tohtml' then - return '*tohtml*' + return 'tohtml' end - return '*vim.' .. name:lower() .. '*' + return 'vim.' .. name:lower() end, fn_helptag_fmt = function(fun) local name = fun.name if vim.startswith(name, 'vim.') then local fn_sfx = fun.table and '' or '()' - return fmt('*%s%s*', name, fn_sfx) + return name .. fn_sfx elseif fun.classvar == 'Option' then - return fmt('*vim.opt:%s()*', name) + return fmt('vim.opt:%s()', name) end return fn_helptag_fmt_common(fun) @@ -297,9 +297,9 @@ local config = { end, helptag_fmt = function(name) if name:lower() == 'lsp' then - return '*lsp-core*' + return 'lsp-core' end - return fmt('*lsp-%s*', name:lower()) + return fmt('lsp-%s', name:lower()) end, }, diagnostic = { @@ -312,7 +312,7 @@ local config = { return 'Lua module: vim.diagnostic' end, helptag_fmt = function() - return '*diagnostic-api*' + return 'diagnostic-api' end, }, treesitter = { @@ -337,9 +337,28 @@ local config = { end, helptag_fmt = function(name) if name:lower() == 'treesitter' then - return '*lua-treesitter-core*' + return 'lua-treesitter-core' end - return '*lua-treesitter-' .. name:lower() .. '*' + return 'lua-treesitter-' .. name:lower() + end, + }, + editorconfig = { + filename = 'editorconfig.txt', + files = { + 'runtime/lua/editorconfig.lua', + }, + section_order = { + 'editorconfig.lua', + }, + section_fmt = function(_name) + return 'EditorConfig integration' + end, + helptag_fmt = function(name) + return name:lower() + end, + fn_xform = function(fun) + fun.table = true + fun.name = vim.split(fun.name, '.', { plain = true })[2] end, }, } @@ -600,7 +619,7 @@ local function render_fun_header(fun, cfg) cfg.fn_helptag_fmt = fn_helptag_fmt_common end - local tag = cfg.fn_helptag_fmt(fun) + local tag = '*' .. cfg.fn_helptag_fmt(fun) .. '*' if #proto + #tag > TEXT_WIDTH - 8 then table.insert(ret, fmt('%78s\n', tag)) @@ -816,7 +835,7 @@ local function make_section(filename, cfg, section_docs, funs_txt) local sectname = cfg.section_name and cfg.section_name[filename] or mktitle(name) -- section tag: e.g., "*api-autocmd*" - local help_tag = cfg.helptag_fmt(sectname) + local help_tag = '*' .. cfg.helptag_fmt(sectname) .. '*' if funs_txt == '' and #section_docs == 0 then return @@ -845,9 +864,9 @@ local function render_section(section, add_header) }) end - if section.doc and #section.doc > 0 then - table.insert(doc, '\n\n') - vim.list_extend(doc, section.doc) + local sdoc = '\n\n' .. table.concat(section.doc or {}, '\n') + if sdoc:find('[^%s]') then + doc[#doc + 1] = sdoc end if section.funs_txt then @@ -880,6 +899,7 @@ end --- @param cfg nvim.gen_vimdoc.Config local function gen_target(cfg) + print('Target:', cfg.filename) local sections = {} --- @type table expand_files(cfg.files) @@ -891,7 +911,7 @@ local function gen_target(cfg) local all_classes = {} --- First pass so we can collect all classes - for _, f in pairs(cfg.files) do + for _, f in vim.spairs(cfg.files) do local ext = assert(f:match('%.([^.]+)$')) --[[@as 'h'|'c'|'lua']] local parser = assert(parsers[ext]) local classes, funs, briefs = parser(f) @@ -899,13 +919,14 @@ local function gen_target(cfg) all_classes = vim.tbl_extend('error', all_classes, classes) end - for f, r in pairs(file_results) do + for f, r in vim.spairs(file_results) do local classes, funs, briefs = r[1], r[2], r[3] local briefs_txt = {} --- @type string[] for _, b in ipairs(briefs) do briefs_txt[#briefs_txt + 1] = md_to_vimdoc(b, 0, 0, TEXT_WIDTH) end + print(' Processing file:', f) local funs_txt = render_funs(funs, all_classes, cfg) if next(classes) then local classes_txt = render_classes(classes) @@ -923,8 +944,9 @@ local function gen_target(cfg) for _, f in ipairs(cfg.section_order) do local section = sections[f] if section then + print(string.format(" Rendering section: '%s'", section.title)) local add_sep_and_header = not vim.tbl_contains(cfg.append_only or {}, f) - table.insert(docs, render_section(section, add_sep_and_header)) + docs[#docs + 1] = render_section(section, add_sep_and_header) end end @@ -945,7 +967,7 @@ local function gen_target(cfg) end local function run() - for _, cfg in pairs(config) do + for _, cfg in vim.spairs(config) do gen_target(cfg) end end diff --git a/scripts/text_utils.lua b/scripts/text_utils.lua index 937408c546..75b3bfedd5 100644 --- a/scripts/text_utils.lua +++ b/scripts/text_utils.lua @@ -318,7 +318,7 @@ local function align_tags(text_width) --- @param line string --- @return string return function(line) - local tag_pat = '%s+(%*[^ ]+%*)%s*$' + local tag_pat = '%s*(%*.+%*)%s*$' local tags = {} for m in line:gmatch(tag_pat) do table.insert(tags, m) @@ -327,7 +327,9 @@ local function align_tags(text_width) if #tags > 0 then line = line:gsub(tag_pat, '') local tags_str = ' ' .. table.concat(tags, ' ') - local pad = string.rep(' ', text_width - #line - #tags_str) + --- @type integer + local conceal_offset = select(2, tags_str:gsub('%*', '')) - 2 + local pad = string.rep(' ', text_width - #line - #tags_str + conceal_offset) return line .. pad .. tags_str end @@ -352,7 +354,7 @@ function M.md_to_vimdoc(text, start_indent, indent, text_width, is_list) local s = table.concat(lines, '\n') -- Reduce whitespace in code-blocks - s = s:gsub('\n+%s*>([a-z]+)\n?\n', ' >%1\n') + s = s:gsub('\n+%s*>([a-z]+)\n', ' >%1\n') s = s:gsub('\n+%s*>\n?\n', ' >\n') return s -- cgit From 74b2f6c3d95647ad07f56bf9ed6865a8db3dfb97 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Sun, 10 Mar 2024 19:58:35 -0400 Subject: fix: ignore non-existent properties during header generation `get_target_property( ...)` sets `` to `-NOTFOUND` if the property doesn't exist for the given target. Detect this situation to avoid adding various `-Dprop-NOTFOUND` and `-Iprop-NOTFOUND` to the command-line when generating the headers. --- src/nvim/CMakeLists.txt | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 047b22edcc..7aa7904286 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -450,18 +450,23 @@ endif() #------------------------------------------------------------------------------- get_target_property(prop main_lib INTERFACE_COMPILE_DEFINITIONS) -foreach(gen_cdef ${prop}) - if(NOT ${gen_cdef} MATCHES "INCLUDE_GENERATED_DECLARATIONS") - list(APPEND gen_cflags "-D${gen_cdef}") - endif() -endforeach() +if(NOT "${prop}" STREQUAL "prop-NOTFOUND") + foreach(gen_cdef ${prop}) + if(NOT ${gen_cdef} MATCHES "INCLUDE_GENERATED_DECLARATIONS") + list(APPEND gen_cflags "-D${gen_cdef}") + endif() + endforeach() +endif() get_directory_property(targets BUILDSYSTEM_TARGETS) foreach(target ${targets}) get_target_property(prop ${target} INTERFACE_INCLUDE_DIRECTORIES) - foreach(gen_include ${prop}) - list(APPEND gen_cflags "-I${gen_include}") - endforeach() + if(NOT "${prop}" STREQUAL "prop-NOTFOUND") + message(STATUS "${target} props '${prop}'") + foreach(gen_include ${prop}) + list(APPEND gen_cflags "-I${gen_include}") + endforeach() + endif() endforeach() if(APPLE AND CMAKE_OSX_SYSROOT) -- cgit From 118fd8367c3953abb43800a7c1ea0bcc0221e9cd Mon Sep 17 00:00:00 2001 From: James McCoy Date: Sun, 10 Mar 2024 20:02:32 -0400 Subject: fix: deduplicate gen_cflags Since many of the targets have common include paths, `gen_cflags` accumulates a lot of duplicate flags. --- src/nvim/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 7aa7904286..7e22203aba 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -469,6 +469,8 @@ foreach(target ${targets}) endif() endforeach() +list(REMOVE_DUPLICATES gen_cflags) + if(APPLE AND CMAKE_OSX_SYSROOT) list(APPEND gen_cflags "-isysroot" "${CMAKE_OSX_SYSROOT}") endif() -- cgit From 141182d6c6c06ad56413b81a518ba9b777a0cbe0 Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Mon, 25 Dec 2023 20:41:09 -0800 Subject: vim-patch:9.1.0147: Cannot keep a buffer focused in a window Problem: Cannot keep a buffer focused in a window (Amit Levy) Solution: Add the 'winfixbuf' window-local option (Colin Kennedy) fixes: vim/vim#6445 closes: vim/vim#13903 https://github.com/vim/vim/commit/215703563757a4464907ead6fb9edaeb7f430bea N/A patch: vim-patch:58f1e5c0893a --- runtime/doc/message.txt | 7 + runtime/doc/news.txt | 2 + runtime/doc/options.txt | 11 + runtime/doc/quickref.txt | 1 + runtime/doc/tagsrch.txt | 29 +- runtime/lua/vim/_meta/options.lua | 14 + runtime/optwin.vim | 3 + src/nvim/api/vim.c | 5 + src/nvim/api/window.c | 6 + src/nvim/arglist.c | 10 +- src/nvim/buffer.c | 6 + src/nvim/buffer_defs.h | 2 + src/nvim/ex_cmds.c | 4 + src/nvim/ex_cmds.lua | 2 +- src/nvim/ex_cmds2.c | 21 + src/nvim/ex_docmd.c | 16 +- src/nvim/globals.h | 3 + src/nvim/insexpand.c | 2 +- src/nvim/normal.c | 7 +- src/nvim/option.c | 2 + src/nvim/options.lua | 19 + src/nvim/quickfix.c | 33 +- src/nvim/search.c | 8 +- src/nvim/tag.c | 8 + src/nvim/window.c | 31 +- test/functional/options/winfixbuf_spec.lua | 54 + test/old/testdir/test_winfixbuf.vim | 3131 ++++++++++++++++++++++++++++ 27 files changed, 3414 insertions(+), 23 deletions(-) create mode 100644 test/functional/options/winfixbuf_spec.lua create mode 100644 test/old/testdir/test_winfixbuf.vim diff --git a/runtime/doc/message.txt b/runtime/doc/message.txt index c3154fc372..16d88407d5 100644 --- a/runtime/doc/message.txt +++ b/runtime/doc/message.txt @@ -114,6 +114,13 @@ wiped out a buffer which contains a mark or is referenced in another way. You cannot have two buffers with exactly the same name. This includes the path leading to the file. + *E1513* > + Cannot edit buffer. 'winfixbuf' is enabled + +If a window has 'winfixbuf' enabled, you cannot change that window's current +buffer. You need to set 'nowinfixbuf' before continuing. You may use [!] to +force the window to switch buffers, if your command supports it. + *E72* > Close error on swap file diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 3029414500..3ba7c5e681 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -160,6 +160,8 @@ The following new APIs and features were added. • 'breakindent' performance is significantly improved for wrapped lines. • Cursor movement, insertion with [count] and |screenpos()| are now faster. +• |'winfixbuf'| keeps a window focused onto a specific buffer + • |vim.iter()| provides a generic iterator interface for tables and Lua iterators |for-in|. diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index edd5149621..f35700218c 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -6271,6 +6271,8 @@ A jump table for the options with a short description can be found at |Q_op|. "split" when both are present. uselast If included, jump to the previously used window when jumping to errors with |quickfix| commands. + If a window has 'winfixbuf' enabled, 'switchbuf' is currently not + applied to the split window. *'synmaxcol'* *'smc'* 'synmaxcol' 'smc' number (default 3000) @@ -7170,6 +7172,15 @@ A jump table for the options with a short description can be found at |Q_op|. Note: Do not confuse this with the height of the Vim window, use 'lines' for that. + *'winfixbuf'* *'wfb'* *'nowinfixbuf'* *'nowfb'* +'winfixbuf' 'wfb' boolean (default off) + local to window + If enabled, the buffer and any window that displays it are paired. + For example, attempting to change the buffer with |:edit| will fail. + Other commands which change a window's buffer such as |:cnext| will + also skip any window with 'winfixbuf' enabled. However if a command + has an "!" option, a window can be forced to switch buffers. + *'winfixheight'* *'wfh'* *'nowinfixheight'* *'nowfh'* 'winfixheight' 'wfh' boolean (default off) local to window |local-noglobal| diff --git a/runtime/doc/quickref.txt b/runtime/doc/quickref.txt index 572dc8a841..4ef4392f92 100644 --- a/runtime/doc/quickref.txt +++ b/runtime/doc/quickref.txt @@ -939,6 +939,7 @@ Short explanation of each option: *option-list* 'wildoptions' 'wop' specifies how command line completion is done 'winaltkeys' 'wak' when the windows system handles ALT keys 'window' 'wi' nr of lines to scroll for CTRL-F and CTRL-B +'winfixbuf' 'wfb' keep window focused on a single buffer 'winfixheight' 'wfh' keep window height when opening/closing windows 'winfixwidth' 'wfw' keep window width when opening/closing windows 'winheight' 'wh' minimum number of lines for the current window diff --git a/runtime/doc/tagsrch.txt b/runtime/doc/tagsrch.txt index 2b5b253a09..ac2bf9337b 100644 --- a/runtime/doc/tagsrch.txt +++ b/runtime/doc/tagsrch.txt @@ -402,17 +402,22 @@ If the tag is in the current file this will always work. Otherwise the performed actions depend on whether the current file was changed, whether a ! is added to the command and on the 'autowrite' option: - tag in file autowrite ~ -current file changed ! option action ~ - --------------------------------------------------------------------------- - yes x x x goto tag - no no x x read other file, goto tag - no yes yes x abandon current file, read other file, goto - tag - no yes no on write current file, read other file, goto - tag - no yes no off fail - --------------------------------------------------------------------------- + tag in file autowrite ~ +current file changed ! winfixbuf option action ~ + ----------------------------------------------------------------------------- + yes x x no x goto tag + no no x no x read other file, goto tag + no yes yes no x abandon current file, + read other file, goto tag + no yes no no on write current file, + read other file, goto tag + no yes no no off fail + yes x yes x x goto tag + no no no yes x fail + no yes no yes x fail + no yes no yes on fail + no yes no yes off fail + ----------------------------------------------------------------------------- - If the tag is in the current file, the command will always work. - If the tag is in another file and the current file was not changed, the @@ -428,6 +433,8 @@ current file changed ! option action ~ the changes, use the ":w" command and then use ":tag" without an argument. This works because the tag is put on the stack anyway. If you want to lose the changes you can use the ":tag!" command. +- If the tag is in another file and the window includes 'winfixbuf', the + command will fail. If the tag is in the same file then it may succeed. *tag-security* Note that Vim forbids some commands, for security reasons. This works like diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index 757720d8fb..e9ac2fe08f 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -6746,6 +6746,8 @@ vim.bo.swf = vim.bo.swapfile --- "split" when both are present. --- uselast If included, jump to the previously used window when --- jumping to errors with `quickfix` commands. +--- If a window has 'winfixbuf' enabled, 'switchbuf' is currently not +--- applied to the split window. --- --- @type string vim.o.switchbuf = "uselast" @@ -7874,6 +7876,18 @@ vim.o.wi = vim.o.window vim.go.window = vim.o.window vim.go.wi = vim.go.window +--- If enabled, the buffer and any window that displays it are paired. +--- For example, attempting to change the buffer with `:edit` will fail. +--- Other commands which change a window's buffer such as `:cnext` will +--- also skip any window with 'winfixbuf' enabled. However if a command +--- has an "!" option, a window can be forced to switch buffers. +--- +--- @type boolean +vim.o.winfixbuf = false +vim.o.wfb = vim.o.winfixbuf +vim.wo.winfixbuf = vim.o.winfixbuf +vim.wo.wfb = vim.wo.winfixbuf + --- Keep the window height when windows are opened or closed and --- 'equalalways' is set. Also for `CTRL-W_=`. Set by default for the --- `preview-window` and `quickfix-window`. diff --git a/runtime/optwin.vim b/runtime/optwin.vim index fc60f70335..5b5b33e4ad 100644 --- a/runtime/optwin.vim +++ b/runtime/optwin.vim @@ -444,6 +444,7 @@ if has("statusline") call AddOption("statusline", gettext("alternate format to be used for a status line")) call OptionG("stl", &stl) endif +call append("$", "\t" .. s:local_to_window) call AddOption("equalalways", gettext("make all windows the same size when adding/removing windows")) call BinOptionG("ea", &ea) call AddOption("eadirection", gettext("in which direction 'equalalways' works: \"ver\", \"hor\" or \"both\"")) @@ -452,6 +453,8 @@ call AddOption("winheight", gettext("minimal number of lines used for the c call append("$", " \tset wh=" . &wh) call AddOption("winminheight", gettext("minimal number of lines used for any window")) call append("$", " \tset wmh=" . &wmh) +call AddOption("winfixbuf", gettext("keep window focused on a single buffer")) +call OptionG("wfb", &wfb) call AddOption("winfixheight", gettext("keep the height of the window")) call append("$", "\t" .. s:local_to_window) call BinOptionL("wfh") diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 84a2f24dbc..24ad7d5fbc 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -876,6 +876,11 @@ void nvim_set_current_buf(Buffer buffer, Error *err) return; } + if (curwin->w_p_wfb) { + api_set_error(err, kErrorTypeException, "%s", e_winfixbuf_cannot_go_to_buffer); + return; + } + try_start(); int result = do_buffer(DOBUF_GOTO, DOBUF_FIRST, FORWARD, buf->b_fnum, 0); if (!try_end(err) && result == FAIL) { diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index ed51eedf1b..1a80e9ea16 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -61,6 +61,12 @@ void nvim_win_set_buf(Window window, Buffer buffer, Error *err) if (!win || !buf) { return; } + + if (win->w_p_wfb) { + api_set_error(err, kErrorTypeException, "%s", e_winfixbuf_cannot_go_to_buffer); + return; + } + if (win == cmdwin_win || win == cmdwin_old_curwin || buf == cmdwin_buf) { api_set_error(err, kErrorTypeException, "%s", e_cmdwin); return; diff --git a/src/nvim/arglist.c b/src/nvim/arglist.c index a02c22deae..4d493c9d03 100644 --- a/src/nvim/arglist.c +++ b/src/nvim/arglist.c @@ -623,6 +623,8 @@ void ex_argument(exarg_T *eap) /// Edit file "argn" of the argument lists. void do_argfile(exarg_T *eap, int argn) { + bool is_split_cmd = *eap->cmd == 's'; + int old_arg_idx = curwin->w_arg_idx; if (argn < 0 || argn >= ARGCOUNT) { @@ -637,10 +639,16 @@ void do_argfile(exarg_T *eap, int argn) return; } + if (!is_split_cmd + && (&ARGLIST[argn])->ae_fnum != curbuf->b_fnum + && !check_can_set_curbuf_forceit(eap->forceit)) { + return; + } + setpcmark(); // split window or create new tab page first - if (*eap->cmd == 's' || cmdmod.cmod_tab != 0) { + if (is_split_cmd || cmdmod.cmod_tab != 0) { if (win_split(0, 0) == FAIL) { return; } diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 7154be36be..e141706edd 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -1305,6 +1305,12 @@ int do_buffer(int action, int start, int dir, int count, int forceit) } return FAIL; } + + if (action == DOBUF_GOTO && buf != curbuf && !check_can_set_curbuf_forceit(forceit)) { + // disallow navigating to another buffer when 'winfixbuf' is applied + return FAIL; + } + if ((action == DOBUF_GOTO || action == DOBUF_SPLIT) && (buf->b_flags & BF_DUMMY)) { // disallow navigating to the dummy buffer semsg(_(e_nobufnr), count); diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 1e5086309c..7f7300706c 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -139,6 +139,8 @@ typedef struct { #define w_ve_flags w_onebuf_opt.wo_ve_flags // flags for 'virtualedit' OptInt wo_nuw; #define w_p_nuw w_onebuf_opt.wo_nuw // 'numberwidth' + int wo_wfb; +#define w_p_wfb w_onebuf_opt.wo_wfb // 'winfixbuf' int wo_wfh; #define w_p_wfh w_onebuf_opt.wo_wfh // 'winfixheight' int wo_wfw; diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 74ad8e95a2..14bd2b87e3 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -2008,6 +2008,10 @@ static int check_readonly(int *forceit, buf_T *buf) /// GETFILE_OPEN_OTHER for successfully opening another file. int getfile(int fnum, char *ffname_arg, char *sfname_arg, bool setpm, linenr_T lnum, bool forceit) { + if (!check_can_set_curbuf_forceit(forceit)) { + return GETFILE_ERROR; + } + char *ffname = ffname_arg; char *sfname = sfname_arg; bool other; diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index 1318eda5eb..e2196f99ec 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -812,7 +812,7 @@ module.cmds = { }, { command = 'drop', - flags = bit.bor(FILES, CMDARG, NEEDARG, ARGOPT, TRLBAR), + flags = bit.bor(BANG, FILES, CMDARG, NEEDARG, ARGOPT, TRLBAR), addr_type = 'ADDR_NONE', func = 'ex_drop', }, diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 8016e37ca7..3120868350 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -444,6 +444,27 @@ int buf_write_all(buf_T *buf, bool forceit) /// ":argdo", ":windo", ":bufdo", ":tabdo", ":cdo", ":ldo", ":cfdo" and ":lfdo" void ex_listdo(exarg_T *eap) { + if (curwin->w_p_wfb) { + if ((eap->cmdidx == CMD_ldo || eap->cmdidx == CMD_lfdo) && !eap->forceit) { + // Disallow :ldo if 'winfixbuf' is applied + semsg("%s", e_winfixbuf_cannot_go_to_buffer); + return; + } + + if (win_valid(prevwin)) { + // Change the current window to another because 'winfixbuf' is enabled + curwin = prevwin; + } else { + // Split the window, which will be 'nowinfixbuf', and set curwin to that + exarg_T new_eap = { + .cmdidx = CMD_split, + .cmd = "split", + .arg = "", + }; + ex_splitview(&new_eap); + } + } + char *save_ei = NULL; // Temporarily override SHM_OVER and SHM_OVERALL to avoid that file diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 2913f6d4e9..1b4e83d392 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -5334,6 +5334,10 @@ static void ex_resize(exarg_T *eap) /// ":find [+command] " command. static void ex_find(exarg_T *eap) { + if (!check_can_set_curbuf_forceit(eap->forceit)) { + return; + } + char *file_to_find = NULL; char *search_ctx = NULL; char *fname = find_file_in_path(eap->arg, strlen(eap->arg), @@ -5364,6 +5368,14 @@ static void ex_find(exarg_T *eap) /// ":edit", ":badd", ":balt", ":visual". static void ex_edit(exarg_T *eap) { + // Exclude commands which keep the window's current buffer + if (eap->cmdidx != CMD_badd + && eap->cmdidx != CMD_balt + // All other commands must obey 'winfixbuf' / ! rules + && !check_can_set_curbuf_forceit(eap->forceit)) { + return; + } + do_exedit(eap, NULL); } @@ -6670,7 +6682,7 @@ static void ex_checkpath(exarg_T *eap) { find_pattern_in_path(NULL, 0, 0, false, false, CHECK_PATH, 1, eap->forceit ? ACTION_SHOW_ALL : ACTION_SHOW, - 1, (linenr_T)MAXLNUM); + 1, (linenr_T)MAXLNUM, eap->forceit); } /// ":psearch" @@ -6729,7 +6741,7 @@ static void ex_findpat(exarg_T *eap) if (!eap->skip) { find_pattern_in_path(eap->arg, 0, strlen(eap->arg), whole, !eap->forceit, *eap->cmd == 'd' ? FIND_DEFINE : FIND_ANY, - n, action, eap->line1, eap->line2); + n, action, eap->line1, eap->line2, eap->forceit); } } diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 113985cb52..aecb9d1116 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -971,6 +971,9 @@ EXTERN const char e_val_too_large[] INIT(= N_("E1510: Value too large: %s")); EXTERN const char e_undobang_cannot_redo_or_move_branch[] INIT(= N_("E5767: Cannot use :undo! to redo or move to a different undo branch")); +EXTERN const char e_winfixbuf_cannot_go_to_buffer[] +INIT(= N_("E1513: Cannot edit buffer. 'winfixbuf' is enabled")); + EXTERN const char e_trustfile[] INIT(= N_("E5570: Cannot update trust file: %s")); EXTERN const char e_unknown_option2[] INIT(= N_("E355: Unknown option: %s")); diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index 41b964323e..d0cd24773f 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -3027,7 +3027,7 @@ static void get_next_include_file_completion(int compl_type) ((compl_type == CTRL_X_PATH_DEFINES && !(compl_cont_status & CONT_SOL)) ? FIND_DEFINE : FIND_ANY), - 1, ACTION_EXPAND, 1, MAXLNUM); + 1, ACTION_EXPAND, 1, MAXLNUM, false); } /// Get the next set of words matching "compl_pattern" in dictionary or diff --git a/src/nvim/normal.c b/src/nvim/normal.c index f586ad6704..aae9621d4a 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -3896,6 +3896,10 @@ static void nv_gotofile(cmdarg_T *cap) return; } + if (!check_can_set_curbuf_disabled()) { + return; + } + char *ptr = grab_file_name(cap->count1, &lnum); if (ptr != NULL) { @@ -4232,7 +4236,8 @@ static void nv_brackets(cmdarg_T *cap) (cap->cmdchar == ']' ? curwin->w_cursor.lnum + 1 : 1), - MAXLNUM); + MAXLNUM, + false); xfree(ptr); curwin->w_set_curswant = true; } diff --git a/src/nvim/option.c b/src/nvim/option.c index 0ac65ed95d..fcc5b5eb06 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -4629,6 +4629,8 @@ void *get_varp_from(vimoption_T *p, buf_T *buf, win_T *win) return &(win->w_p_rnu); case PV_NUW: return &(win->w_p_nuw); + case PV_WFB: + return &(win->w_p_wfb); case PV_WFH: return &(win->w_p_wfh); case PV_WFW: diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 72f9ff849d..5e8bc1361c 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -8406,6 +8406,8 @@ return { "split" when both are present. uselast If included, jump to the previously used window when jumping to errors with |quickfix| commands. + If a window has 'winfixbuf' enabled, 'switchbuf' is currently not + applied to the split window. ]=], expand_cb = 'expand_set_switchbuf', full_name = 'switchbuf', @@ -9816,6 +9818,23 @@ return { type = 'number', varname = 'p_window', }, + { + abbreviation = 'wfb', + defaults = { if_true = false }, + desc = [=[ + If enabled, the buffer and any window that displays it are paired. + For example, attempting to change the buffer with |:edit| will fail. + Other commands which change a window's buffer such as |:cnext| will + also skip any window with 'winfixbuf' enabled. However if a command + has an "!" option, a window can be forced to switch buffers. + ]=], + full_name = 'winfixbuf', + pv_name = 'p_wfb', + redraw = { 'current_window' }, + scope = { 'window' }, + short_desc = N_('pin a window to a specific buffer'), + type = 'boolean', + }, { abbreviation = 'wfh', defaults = { if_true = false }, diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 651ebc9f93..a88b781f32 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -2699,7 +2699,7 @@ static void qf_goto_win_with_qfl_file(int qf_fnum) // Didn't find it, go to the window before the quickfix // window, unless 'switchbuf' contains 'uselast': in this case we // try to jump to the previously used window first. - if ((swb_flags & SWB_USELAST) && win_valid(prevwin)) { + if ((swb_flags & SWB_USELAST) && !prevwin->w_p_wfb && win_valid(prevwin)) { win = prevwin; } else if (altwin != NULL) { win = altwin; @@ -2714,6 +2714,7 @@ static void qf_goto_win_with_qfl_file(int qf_fnum) // Remember a usable window. if (altwin == NULL && !win->w_p_pvw + && !win->w_p_wfb && bt_normal(win->w_buffer)) { altwin = win; } @@ -2802,6 +2803,25 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, int ECMD_HIDE + ECMD_SET_HELP, prev_winid == curwin->handle ? curwin : NULL); } else { + if (!forceit && curwin->w_p_wfb) { + if (qi->qfl_type == QFLT_LOCATION) { + // Location lists cannot split or reassign their window + // so 'winfixbuf' windows must fail + semsg("%s", e_winfixbuf_cannot_go_to_buffer); + return QF_ABORT; + } + + if (!win_valid(prevwin)) { + // Split the window, which will be 'nowinfixbuf', and set curwin to that + exarg_T new_eap = { + .cmdidx = CMD_split, + .cmd = "split", + .arg = "", + }; + ex_splitview(&new_eap); + } + } + retval = buflist_getfile(qf_ptr->qf_fnum, 1, GETF_SETMARK | GETF_SWITCH, forceit); } @@ -4297,6 +4317,11 @@ static void qf_jump_first(qf_info_T *qi, unsigned save_qfid, int forceit) if (qf_restore_list(qi, save_qfid) == FAIL) { return; } + + if (!check_can_set_curbuf_forceit(forceit)) { + return; + } + // Autocommands might have cleared the list, check for that if (!qf_list_empty(qf_get_curlist(qi))) { qf_jump(qi, 0, 0, forceit); @@ -5125,7 +5150,7 @@ void ex_cfile(exarg_T *eap) // This function is used by the :cfile, :cgetfile and :caddfile // commands. - // :cfile always creates a new quickfix list and jumps to the + // :cfile always creates a new quickfix list and may jump to the // first error. // :cgetfile creates a new quickfix list but doesn't jump to the // first error. @@ -5587,6 +5612,10 @@ theend: /// ":lvimgrepadd {pattern} file(s)" void ex_vimgrep(exarg_T *eap) { + if (!check_can_set_curbuf_forceit(eap->forceit)) { + return; + } + char *au_name = vgr_get_auname(eap->cmdidx); if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name, curbuf->b_fname, true, curbuf)) { diff --git a/src/nvim/search.c b/src/nvim/search.c index 48e41c290d..2fea28ba7c 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -3564,8 +3564,10 @@ static char *get_line_and_copy(linenr_T lnum, char *buf) /// @param action What to do when we find it /// @param start_lnum first line to start searching /// @param end_lnum last line for searching +/// @param forceit If true, always switch to the found path void find_pattern_in_path(char *ptr, Direction dir, size_t len, bool whole, bool skip_comments, - int type, int count, int action, linenr_T start_lnum, linenr_T end_lnum) + int type, int count, int action, linenr_T start_lnum, linenr_T end_lnum, + int forceit) { SearchedFile *files; // Stack of included files SearchedFile *bigger; // When we need more space @@ -4025,7 +4027,7 @@ search_line: break; } if (!GETFILE_SUCCESS(getfile(curwin_save->w_buffer->b_fnum, NULL, - NULL, true, lnum, false))) { + NULL, true, lnum, forceit))) { break; // failed to jump to file } } else { @@ -4035,7 +4037,7 @@ search_line: check_cursor(); } else { if (!GETFILE_SUCCESS(getfile(0, files[depth].name, NULL, true, - files[depth].lnum, false))) { + files[depth].lnum, forceit))) { break; // failed to jump to file } // autocommands may have changed the lnum, we don't diff --git a/src/nvim/tag.c b/src/nvim/tag.c index ab5bfc6773..776498fa29 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -290,6 +290,10 @@ void set_buflocal_tfu_callback(buf_T *buf) /// @param verbose print "tag not found" message void do_tag(char *tag, int type, int count, int forceit, bool verbose) { + if (postponed_split == 0 && !check_can_set_curbuf_forceit(forceit)) { + return; + } + taggy_T *tagstack = curwin->w_tagstack; int tagstackidx = curwin->w_tagstackidx; int tagstacklen = curwin->w_tagstacklen; @@ -2784,6 +2788,10 @@ static char *tag_full_fname(tagptrs_T *tagp) /// @return OK for success, NOTAGFILE when file not found, FAIL otherwise. static int jumpto_tag(const char *lbuf_arg, int forceit, bool keep_help) { + if (postponed_split == 0 && !check_can_set_curbuf_forceit(forceit)) { + return FAIL; + } + char *pbuf_end; char *tofree_fname = NULL; tagptrs_T tagp; diff --git a/src/nvim/window.c b/src/nvim/window.c index ff40a9adef..9f84713ee7 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -133,6 +133,35 @@ static void log_frame_layout(frame_T *frame) } #endif +/// Check if the current window is allowed to move to a different buffer. +/// +/// @return If the window has 'winfixbuf', or this function will return false. +bool check_can_set_curbuf_disabled(void) +{ + if (curwin->w_p_wfb) { + semsg("%s", e_winfixbuf_cannot_go_to_buffer); + return false; + } + + return true; +} + +/// Check if the current window is allowed to move to a different buffer. +/// +/// @param forceit If true, do not error. If false and 'winfixbuf' is enabled, error. +/// +/// @return If the window has 'winfixbuf', then forceit must be true +/// or this function will return false. +bool check_can_set_curbuf_forceit(int forceit) +{ + if (!forceit && curwin->w_p_wfb) { + semsg("%s", e_winfixbuf_cannot_go_to_buffer); + return false; + } + + return true; +} + /// @return the current window, unless in the cmdline window and "prevwin" is /// set, then return "prevwin". win_T *prevwin_curwin(void) @@ -597,7 +626,7 @@ wingotofile: ptr = xmemdupz(ptr, len); find_pattern_in_path(ptr, 0, len, true, Prenum == 0, - type, Prenum1, ACTION_SPLIT, 1, MAXLNUM); + type, Prenum1, ACTION_SPLIT, 1, MAXLNUM, false); xfree(ptr); curwin->w_set_curswant = true; break; diff --git a/test/functional/options/winfixbuf_spec.lua b/test/functional/options/winfixbuf_spec.lua new file mode 100644 index 0000000000..20407b9bb7 --- /dev/null +++ b/test/functional/options/winfixbuf_spec.lua @@ -0,0 +1,54 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear = helpers.clear +local exec_lua = helpers.exec_lua + +describe("Nvim API calls with 'winfixbuf'", function() + before_each(function() + clear() + end) + + it("Calling vim.api.nvim_win_set_buf with 'winfixbuf'", function() + local results = exec_lua([[ + local function _setup_two_buffers() + local buffer = vim.api.nvim_create_buf(true, true) + + vim.api.nvim_create_buf(true, true) -- Make another buffer + + local current_window = 0 + vim.api.nvim_set_option_value("winfixbuf", true, {win=current_window}) + + return buffer + end + + local other_buffer = _setup_two_buffers() + local current_window = 0 + local results, _ = pcall(vim.api.nvim_win_set_buf, current_window, other_buffer) + + return results + ]]) + + assert(results == false) + end) + + it("Calling vim.api.nvim_set_current_buf with 'winfixbuf'", function() + local results = exec_lua([[ + local function _setup_two_buffers() + local buffer = vim.api.nvim_create_buf(true, true) + + vim.api.nvim_create_buf(true, true) -- Make another buffer + + local current_window = 0 + vim.api.nvim_set_option_value("winfixbuf", true, {win=current_window}) + + return buffer + end + + local other_buffer = _setup_two_buffers() + local results, _ = pcall(vim.api.nvim_set_current_buf, other_buffer) + + return results + ]]) + + assert(results == false) + end) +end) diff --git a/test/old/testdir/test_winfixbuf.vim b/test/old/testdir/test_winfixbuf.vim new file mode 100644 index 0000000000..2948be7859 --- /dev/null +++ b/test/old/testdir/test_winfixbuf.vim @@ -0,0 +1,3131 @@ +" Test 'winfixbuf' + +source check.vim + +" Find the number of open windows in the current tab +func s:get_windows_count() + return tabpagewinnr(tabpagenr(), '$') +endfunc + +" Create some unnamed buffers. +func s:make_buffers_list() + enew + file first + let l:first = bufnr() + + enew + file middle + let l:middle = bufnr() + + enew + file last + let l:last = bufnr() + + set winfixbuf + + return [l:first, l:last] +endfunc + +" Create some unnamed buffers and add them to an args list +func s:make_args_list() + let [l:first, l:last] = s:make_buffers_list() + + args! first middle last + + return [l:first, l:last] +endfunc + +" Create two buffers and then set the window to 'winfixbuf' +func s:make_buffer_pairs(...) + let l:reversed = get(a:, 1, 0) + + if l:reversed == 1 + enew + file original + + set winfixbuf + + enew! + file other + let l:other = bufnr() + + return l:other + endif + + enew + file other + let l:other = bufnr() + + enew + file current + + set winfixbuf + + return l:other +endfunc + +" Create 3 quick buffers and set the window to 'winfixbuf' +func s:make_buffer_trio() + edit first + let l:first = bufnr() + edit second + let l:second = bufnr() + + set winfixbuf + + edit! third + let l:third = bufnr() + + execute ":buffer! " . l:second + + return [l:first, l:second, l:third] +endfunc + +" Create a location list with at least 2 entries + a 'winfixbuf' window. +func s:make_simple_location_list() + enew + file middle + let l:middle = bufnr() + call append(0, ["winfix search-term", "another line"]) + + enew! + file first + let l:first = bufnr() + call append(0, "first search-term") + + enew! + file last + let l:last = bufnr() + call append(0, "last search-term") + + call setloclist( + \ 0, + \ [ + \ { + \ "filename": "first", + \ "bufnr": l:first, + \ "lnum": 1, + \ }, + \ { + \ "filename": "middle", + \ "bufnr": l:middle, + \ "lnum": 1, + \ }, + \ { + \ "filename": "middle", + \ "bufnr": l:middle, + \ "lnum": 2, + \ }, + \ { + \ "filename": "last", + \ "bufnr": l:last, + \ "lnum": 1, + \ }, + \ ] + \) + + set winfixbuf + + return [l:first, l:middle, l:last] +endfunc + +" Create a quickfix with at least 2 entries that are in the current 'winfixbuf' window. +func s:make_simple_quickfix() + enew + file current + let l:current = bufnr() + call append(0, ["winfix search-term", "another line"]) + + enew! + file first + let l:first = bufnr() + call append(0, "first search-term") + + enew! + file last + let l:last = bufnr() + call append(0, "last search-term") + + call setqflist( + \ [ + \ { + \ "filename": "first", + \ "bufnr": l:first, + \ "lnum": 1, + \ }, + \ { + \ "filename": "current", + \ "bufnr": l:current, + \ "lnum": 1, + \ }, + \ { + \ "filename": "current", + \ "bufnr": l:current, + \ "lnum": 2, + \ }, + \ { + \ "filename": "last", + \ "bufnr": l:last, + \ "lnum": 1, + \ }, + \ ] + \) + + set winfixbuf + + return [l:current, l:last] +endfunc + +" Create a quickfix with at least 2 entries that are in the current 'winfixbuf' window. +func s:make_quickfix_windows() + let [l:current, _] = s:make_simple_quickfix() + execute "buffer! " . l:current + + split + let l:first_window = win_getid() + execute "normal \j" + let l:winfix_window = win_getid() + + " Open the quickfix in a separate split and go to it + copen + let l:quickfix_window = win_getid() + + return [l:first_window, l:winfix_window, l:quickfix_window] +endfunc + +" Revert all changes that occurred in any past test +func s:reset_all_buffers() + %bwipeout! + set nowinfixbuf + + call setqflist([]) + + for l:window_info in getwininfo() + call setloclist(l:window_info["winid"], []) + endfor + + delmarks A-Z0-9 +endfunc + +" Find and set the first quickfix entry that points to `buffer` +func s:set_quickfix_by_buffer(buffer) + let l:index = 1 " quickfix indices start at 1 + for l:entry in getqflist() + if l:entry["bufnr"] == a:buffer + execute l:index . "cc" + + return + endif + + let l:index += 1 + endfor + + echoerr 'No quickfix entry matching "' . a:buffer . '" could be found.' +endfunc + +" Fail to call :Next on a 'winfixbuf' window unless :Next! is used. +func Test_Next() + call s:reset_all_buffers() + + let [l:first, _] = s:make_args_list() + next! + + call assert_fails("Next", "E1513:") + call assert_notequal(l:first, bufnr()) + + Next! + call assert_equal(l:first, bufnr()) +endfunc + +" Call :argdo and choose the next available 'nowinfixbuf' window. +func Test_argdo_choose_available_window() + call s:reset_all_buffers() + + let [_, l:last] = s:make_args_list() + + " Make a split window that is 'nowinfixbuf' but make it the second-to-last + " window so that :argdo will first try the 'winfixbuf' window, pass over it, + " and prefer the other 'nowinfixbuf' window, instead. + " + " +-------------------+ + " | 'nowinfixbuf' | + " +-------------------+ + " | 'winfixbuf' | <-- Cursor is here + " +-------------------+ + split + let l:nowinfixbuf_window = win_getid() + " Move to the 'winfixbuf' window now + execute "normal \j" + let l:winfixbuf_window = win_getid() + let l:expected_windows = s:get_windows_count() + + argdo echo '' + call assert_equal(l:nowinfixbuf_window, win_getid()) + call assert_equal(l:last, bufnr()) + call assert_equal(l:expected_windows, s:get_windows_count()) +endfunc + +" Call :argdo and create a new split window if all available windows are 'winfixbuf'. +func Test_argdo_make_new_window() + call s:reset_all_buffers() + + let [l:first, l:last] = s:make_args_list() + let l:current = win_getid() + let l:current_windows = s:get_windows_count() + + argdo echo '' + call assert_notequal(l:current, win_getid()) + call assert_equal(l:last, bufnr()) + execute "normal \j" + call assert_equal(l:first, bufnr()) + call assert_equal(l:current_windows + 1, s:get_windows_count()) +endfunc + +" Fail :argedit but :argedit! is allowed +func Test_argedit() + call s:reset_all_buffers() + + args! first middle last + enew + file first + let l:first = bufnr() + + enew + file middle + let l:middle = bufnr() + + enew + file last + let l:last = bufnr() + + set winfixbuf + + let l:current = bufnr() + call assert_fails("argedit first middle last", "E1513:") + call assert_equal(l:current, bufnr()) + + argedit! first middle last + call assert_equal(l:first, bufnr()) +endfunc + +" Fail :arglocal but :arglocal! is allowed +func Test_arglocal() + call s:reset_all_buffers() + + let l:other = s:make_buffer_pairs() + let l:current = bufnr() + argglobal! other + execute "buffer! " . l:current + + call assert_fails("arglocal other", "E1513:") + call assert_equal(l:current, bufnr()) + + arglocal! other + call assert_equal(l:other, bufnr()) +endfunc + +" Fail :argglobal but :argglobal! is allowed +func Test_argglobal() + call s:reset_all_buffers() + + let l:other = s:make_buffer_pairs() + let l:current = bufnr() + + call assert_fails("argglobal other", "E1513:") + call assert_equal(l:current, bufnr()) + + argglobal! other + call assert_equal(l:other, bufnr()) +endfunc + +" Fail :args but :args! is allowed +func Test_args() + call s:reset_all_buffers() + + let [l:first, _] = s:make_buffers_list() + let l:current = bufnr() + + call assert_fails("args first middle last", "E1513:") + call assert_equal(l:current, bufnr()) + + args! first middle last + call assert_equal(l:first, bufnr()) +endfunc + +" Fail :bNext but :bNext! is allowed +func Test_bNext() + call s:reset_all_buffers() + + let l:other = s:make_buffer_pairs() + call assert_fails("bNext", "E1513:") + let l:current = bufnr() + + call assert_equal(l:current, bufnr()) + + bNext! + call assert_equal(l:other, bufnr()) +endfunc + +" Allow :badd because it doesn't actually change the current window's buffer +func Test_badd() + call s:reset_all_buffers() + + call s:make_buffer_pairs() + let l:current = bufnr() + + badd other + call assert_equal(l:current, bufnr()) +endfunc + +" Allow :balt because it doesn't actually change the current window's buffer +func Test_balt() + call s:reset_all_buffers() + + call s:make_buffer_pairs() + let l:current = bufnr() + + balt other + call assert_equal(l:current, bufnr()) +endfunc + +" Fail :bfirst but :bfirst! is allowed +func Test_bfirst() + call s:reset_all_buffers() + + let l:other = s:make_buffer_pairs() + let l:current = bufnr() + + call assert_fails("bfirst", "E1513:") + call assert_equal(l:current, bufnr()) + + bfirst! + call assert_equal(l:other, bufnr()) +endfunc + +" Fail :blast but :blast! is allowed +func Test_blast() + call s:reset_all_buffers() + + let l:other = s:make_buffer_pairs(1) + bfirst! + let l:current = bufnr() + + call assert_fails("blast", "E1513:") + call assert_equal(l:current, bufnr()) + + blast! + call assert_equal(l:other, bufnr()) +endfunc + +" Fail :bmodified but :bmodified! is allowed +func Test_bmodified() + call s:reset_all_buffers() + + let l:other = s:make_buffer_pairs() + let l:current = bufnr() + + execute "buffer! " . l:other + set modified + execute "buffer! " . l:current + + call assert_fails("bmodified", "E1513:") + call assert_equal(l:current, bufnr()) + + bmodified! + call assert_equal(l:other, bufnr()) +endfunc + +" Fail :bnext but :bnext! is allowed +func Test_bnext() + call s:reset_all_buffers() + + let l:other = s:make_buffer_pairs() + let l:current = bufnr() + + call assert_fails("bnext", "E1513:") + call assert_equal(l:current, bufnr()) + + bnext! + call assert_equal(l:other, bufnr()) +endfunc + +" Fail :bprevious but :bprevious! is allowed +func Test_bprevious() + call s:reset_all_buffers() + + let l:other = s:make_buffer_pairs() + let l:current = bufnr() + + call assert_fails("bprevious", "E1513:") + call assert_equal(l:current, bufnr()) + + bprevious! + call assert_equal(l:other, bufnr()) +endfunc + +" Fail :brewind but :brewind! is allowed +func Test_brewind() + call s:reset_all_buffers() + + let l:other = s:make_buffer_pairs() + let l:current = bufnr() + + call assert_fails("brewind", "E1513:") + call assert_equal(l:current, bufnr()) + + brewind! + call assert_equal(l:other, bufnr()) +endfunc + +" Fail :browse edit but :browse edit! is allowed +func Test_browse_edit_fail() + call s:reset_all_buffers() + + let l:other = s:make_buffer_pairs() + let l:current = bufnr() + + call assert_fails("browse edit other", "E1513:") + call assert_equal(l:current, bufnr()) + + browse edit! other + call assert_equal(l:other, bufnr()) +endfunc + +" Allow :browse w because it doesn't change the buffer in the current file +func Test_browse_edit_pass() + call s:reset_all_buffers() + + let l:other = s:make_buffer_pairs() + let l:current = bufnr() + + browse write other + + call delete("other") +endfunc + +" Call :bufdo and choose the next available 'nowinfixbuf' window. +func Test_bufdo_choose_available_window() + call s:reset_all_buffers() + + let l:other = s:make_buffer_pairs() + + " Make a split window that is 'nowinfixbuf' but make it the second-to-last + " window so that :bufdo will first try the 'winfixbuf' window, pass over it, + " and prefer the other 'nowinfixbuf' window, instead. + " + " +-------------------+ + " | 'nowinfixbuf' | + " +-------------------+ + " | 'winfixbuf' | <-- Cursor is here + " +-------------------+ + split + let l:nowinfixbuf_window = win_getid() + " Move to the 'winfixbuf' window now + execute "normal \j" + let l:winfixbuf_window = win_getid() + + let l:current = bufnr() + let l:expected_windows = s:get_windows_count() + + call assert_notequal(l:current, l:other) + + bufdo echo '' + call assert_equal(l:nowinfixbuf_window, win_getid()) + call assert_notequal(l:other, bufnr()) + call assert_equal(l:expected_windows, s:get_windows_count()) +endfunc + +" Call :bufdo and create a new split window if all available windows are 'winfixbuf'. +func Test_bufdo_make_new_window() + call s:reset_all_buffers() + + let [l:first, l:last] = s:make_buffers_list() + execute "buffer! " . l:first + let l:current = win_getid() + let l:current_windows = s:get_windows_count() + + bufdo echo '' + call assert_notequal(l:current, win_getid()) + call assert_equal(l:last, bufnr()) + execute "normal \j" + call assert_equal(l:first, bufnr()) + call assert_equal(l:current_windows + 1, s:get_windows_count()) +endfunc + +" Fail :buffer but :buffer! is allowed +func Test_buffer() + call s:reset_all_buffers() + + let l:other = s:make_buffer_pairs() + let l:current = bufnr() + + call assert_fails("buffer " . l:other, "E1513:") + call assert_equal(l:current, bufnr()) + + execute "buffer! " . l:other + call assert_equal(l:other, bufnr()) +endfunc + +" Allow :buffer on a 'winfixbuf' window if there is no change in buffer +func Test_buffer_same_buffer() + call s:reset_all_buffers() + + call s:make_buffer_pairs() + let l:current = bufnr() + + execute "buffer " . l:current + call assert_equal(l:current, bufnr()) + + execute "buffer! " . l:current + call assert_equal(l:current, bufnr()) +endfunc + +" Allow :cNext but the 'nowinfixbuf' window is selected, instead +func Test_cNext() + CheckFeature quickfix + call s:reset_all_buffers() + + let [l:first_window, l:winfix_window, l:quickfix_window] = s:make_quickfix_windows() + + " The call to `:cNext` succeeds but it selects the window with 'nowinfixbuf' instead + call s:set_quickfix_by_buffer(winbufnr(l:winfix_window)) + + " Make sure the previous window has 'winfixbuf' so we can test that our + " "skip 'winfixbuf' window" logic works. + call win_gotoid(l:winfix_window) + call win_gotoid(l:quickfix_window) + + cNext + call assert_equal(l:first_window, win_getid()) +endfunc + +" Allow :cNfile but the 'nowinfixbuf' window is selected, instead +func Test_cNfile() + CheckFeature quickfix + call s:reset_all_buffers() + + let [l:first_window, l:winfix_window, l:quickfix_window] = s:make_quickfix_windows() + + " The call to `:cNfile` succeeds but it selects the window with 'nowinfixbuf' instead + call s:set_quickfix_by_buffer(winbufnr(l:winfix_window)) + cnext! + + " Make sure the previous window has 'winfixbuf' so we can test that our + " "skip 'winfixbuf' window" logic works. + call win_gotoid(l:winfix_window) + call win_gotoid(l:quickfix_window) + + cNfile + call assert_equal(l:first_window, win_getid()) +endfunc + +" Allow :caddexpr because it doesn't change the current buffer +func Test_caddexpr() + CheckFeature quickfix + call s:reset_all_buffers() + + let l:file_path = tempname() + call writefile(["Error - bad-thing-found"], l:file_path) + execute "edit " . l:file_path + let l:file_buffer = bufnr() + let l:current = bufnr() + + edit first.unittest + call append(0, ["some-search-term bad-thing-found"]) + + edit! other.unittest + + set winfixbuf + + execute "buffer! " . l:file_buffer + + execute 'caddexpr expand("%") .. ":" .. line(".") .. ":" .. getline(".")' + call assert_equal(l:current, bufnr()) + + call delete(l:file_path) +endfunc + +" Fail :cbuffer but :cbuffer! is allowed +func Test_cbuffer() + CheckFeature quickfix + call s:reset_all_buffers() + + let l:file_path = tempname() + call writefile(["first.unittest:1:Error - bad-thing-found"], l:file_path) + execute "edit " . l:file_path + let l:file_buffer = bufnr() + let l:current = bufnr() + + edit first.unittest + call append(0, ["some-search-term bad-thing-found"]) + + edit! other.unittest + + set winfixbuf + + execute "buffer! " . l:file_buffer + + call assert_fails("cbuffer " . l:file_buffer) + call assert_equal(l:current, bufnr()) + + execute "cbuffer! " . l:file_buffer + call assert_equal("first.unittest", expand("%:t")) + + call delete(l:file_path) +endfunc + +" Allow :cc but the 'nowinfixbuf' window is selected, instead +func Test_cc() + CheckFeature quickfix + call s:reset_all_buffers() + + let [l:first_window, l:winfix_window, l:quickfix_window] = s:make_quickfix_windows() + + " The call to `:cnext` succeeds but it selects the window with 'nowinfixbuf' instead + call s:set_quickfix_by_buffer(winbufnr(l:winfix_window)) + + " Make sure the previous window has 'winfixbuf' so we can test that our + " "skip 'winfixbuf' window" logic works. + call win_gotoid(l:winfix_window) + call win_gotoid(l:quickfix_window) + " Go up one line in the quickfix window to an quickfix entry that doesn't + " point to a winfixbuf buffer + normal k + " Attempt to make the previous window, winfixbuf buffer, to go to the + " non-winfixbuf quickfix entry + .cc + + " Confirm that :.cc did not change the winfixbuf-enabled window + call assert_equal(l:first_window, win_getid()) +endfunc + +" Call :cdo and choose the next available 'nowinfixbuf' window. +func Test_cdo_choose_available_window() + CheckFeature quickfix + call s:reset_all_buffers() + + let [l:current, l:last] = s:make_simple_quickfix() + execute "buffer! " . l:current + + " Make a split window that is 'nowinfixbuf' but make it the second-to-last + " window so that :cdo will first try the 'winfixbuf' window, pass over it, + " and prefer the other 'nowinfixbuf' window, instead. + " + " +-------------------+ + " | 'nowinfixbuf' | + " +-------------------+ + " | 'winfixbuf' | <-- Cursor is here + " +-------------------+ + split + let l:nowinfixbuf_window = win_getid() + " Move to the 'winfixbuf' window now + execute "normal \j" + let l:winfixbuf_window = win_getid() + let l:expected_windows = s:get_windows_count() + + cdo echo '' + + call assert_equal(l:nowinfixbuf_window, win_getid()) + call assert_equal(l:last, bufnr()) + execute "normal \j" + call assert_equal(l:current, bufnr()) + call assert_equal(l:expected_windows, s:get_windows_count()) +endfunc + +" Call :cdo and create a new split window if all available windows are 'winfixbuf'. +func Test_cdo_make_new_window() + CheckFeature quickfix + call s:reset_all_buffers() + + let [l:current_buffer, l:last] = s:make_simple_quickfix() + execute "buffer! " . l:current_buffer + + let l:current_window = win_getid() + let l:current_windows = s:get_windows_count() + + cdo echo '' + call assert_notequal(l:current_window, win_getid()) + call assert_equal(l:last, bufnr()) + execute "normal \j" + call assert_equal(l:current_buffer, bufnr()) + call assert_equal(l:current_windows + 1, s:get_windows_count()) +endfunc + +" Fail :cexpr but :cexpr! is allowed +func Test_cexpr() + CheckFeature quickfix + call s:reset_all_buffers() + + let l:file = tempname() + let l:entry = '["' . l:file . ':1:bar"]' + let l:current = bufnr() + + set winfixbuf + + call assert_fails("cexpr " . l:entry) + call assert_equal(l:current, bufnr()) + + execute "cexpr! " . l:entry + call assert_equal(fnamemodify(l:file, ":t"), expand("%:t")) +endfunc + +" Call :cfdo and choose the next available 'nowinfixbuf' window. +func Test_cfdo_choose_available_window() + CheckFeature quickfix + call s:reset_all_buffers() + + let [l:current, l:last] = s:make_simple_quickfix() + execute "buffer! " . l:current + + " Make a split window that is 'nowinfixbuf' but make it the second-to-last + " window so that :cfdo will first try the 'winfixbuf' window, pass over it, + " and prefer the other 'nowinfixbuf' window, instead. + " + " +-------------------+ + " | 'nowinfixbuf' | + " +-------------------+ + " | 'winfixbuf' | <-- Cursor is here + " +-------------------+ + split + let l:nowinfixbuf_window = win_getid() + " Move to the 'winfixbuf' window now + execute "normal \j" + let l:winfixbuf_window = win_getid() + let l:expected_windows = s:get_windows_count() + + cfdo echo '' + + call assert_equal(l:nowinfixbuf_window, win_getid()) + call assert_equal(l:last, bufnr()) + execute "normal \j" + call assert_equal(l:current, bufnr()) + call assert_equal(l:expected_windows, s:get_windows_count()) +endfunc + +" Call :cfdo and create a new split window if all available windows are 'winfixbuf'. +func Test_cfdo_make_new_window() + CheckFeature quickfix + call s:reset_all_buffers() + + let [l:current_buffer, l:last] = s:make_simple_quickfix() + execute "buffer! " . l:current_buffer + + let l:current_window = win_getid() + let l:current_windows = s:get_windows_count() + + cfdo echo '' + call assert_notequal(l:current_window, win_getid()) + call assert_equal(l:last, bufnr()) + execute "normal \j" + call assert_equal(l:current_buffer, bufnr()) + call assert_equal(l:current_windows + 1, s:get_windows_count()) +endfunc + +" Fail :cfile but :cfile! is allowed +func Test_cfile() + CheckFeature quickfix + call s:reset_all_buffers() + + edit first.unittest + call append(0, ["some-search-term bad-thing-found"]) + write + let l:first = bufnr() + + edit! second.unittest + call append(0, ["some-search-term"]) + write + + let l:file = tempname() + call writefile(["first.unittest:1:Error - bad-thing-found was detected"], l:file) + + let l:current = bufnr() + + set winfixbuf + + call assert_fails(":cfile " . l:file) + call assert_equal(l:current, bufnr()) + + execute ":cfile! " . l:file + call assert_equal(l:first, bufnr()) + + call delete(l:file) + call delete("first.unittest") + call delete("second.unittest") +endfunc + +" Allow :cfirst but the 'nowinfixbuf' window is selected, instead +func Test_cfirst() + CheckFeature quickfix + call s:reset_all_buffers() + + let [l:first_window, l:winfix_window, l:quickfix_window] = s:make_quickfix_windows() + + " The call to `:cfirst` succeeds but it selects the window with 'nowinfixbuf' instead + call s:set_quickfix_by_buffer(winbufnr(l:winfix_window)) + + " Make sure the previous window has 'winfixbuf' so we can test that our + " "skip 'winfixbuf' window" logic works. + call win_gotoid(l:winfix_window) + call win_gotoid(l:quickfix_window) + + cfirst + call assert_equal(l:first_window, win_getid()) +endfunc + +" Allow :clast but the 'nowinfixbuf' window is selected, instead +func Test_clast() + CheckFeature quickfix + call s:reset_all_buffers() + + let [l:first_window, l:winfix_window, l:quickfix_window] = s:make_quickfix_windows() + + " The call to `:clast` succeeds but it selects the window with 'nowinfixbuf' instead + call s:set_quickfix_by_buffer(winbufnr(l:winfix_window)) + + " Make sure the previous window has 'winfixbuf' so we can test that our + " "skip 'winfixbuf' window" logic works. + call win_gotoid(l:winfix_window) + call win_gotoid(l:quickfix_window) + + clast + call assert_equal(l:first_window, win_getid()) +endfunc + +" Allow :cnext but the 'nowinfixbuf' window is selected, instead +" Make sure no new windows are created and previous windows are reused +func Test_cnext() + CheckFeature quickfix + call s:reset_all_buffers() + + let [l:first_window, l:winfix_window, l:quickfix_window] = s:make_quickfix_windows() + let l:expected = s:get_windows_count() + + " The call to `:cnext` succeeds but it selects the window with 'nowinfixbuf' instead + call s:set_quickfix_by_buffer(winbufnr(l:winfix_window)) + + cnext! + call assert_equal(l:expected, s:get_windows_count()) + + " Make sure the previous window has 'winfixbuf' so we can test that our + " "skip 'winfixbuf' window" logic works. + call win_gotoid(l:winfix_window) + call win_gotoid(l:quickfix_window) + + cnext + call assert_equal(l:first_window, win_getid()) + call assert_equal(l:expected, s:get_windows_count()) +endfunc + +" Make sure :cnext creates a split window if no previous window exists +func Test_cnext_no_previous_window() + CheckFeature quickfix + call s:reset_all_buffers() + + let [l:current, _] = s:make_simple_quickfix() + execute "buffer! " . l:current + + let l:expected = s:get_windows_count() + + " Open the quickfix in a separate split and go to it + copen + + call assert_equal(l:expected + 1, s:get_windows_count()) +endfunc + +" Allow :cnext and create a 'nowinfixbuf' window if none exists +func Test_cnext_make_new_window() + CheckFeature quickfix + call s:reset_all_buffers() + + let [l:current, _] = s:make_simple_quickfix() + let l:current = win_getid() + + cfirst! + + let l:windows = s:get_windows_count() + let l:expected = l:windows + 1 " We're about to create a new split window + + cnext + call assert_equal(l:expected, s:get_windows_count()) + + cnext! + call assert_equal(l:expected, s:get_windows_count()) +endfunc + +" Allow :cprevious but the 'nowinfixbuf' window is selected, instead +func Test_cprevious() + CheckFeature quickfix + call s:reset_all_buffers() + + let [l:first_window, l:winfix_window, l:quickfix_window] = s:make_quickfix_windows() + + " The call to `:cprevious` succeeds but it selects the window with 'nowinfixbuf' instead + call s:set_quickfix_by_buffer(winbufnr(l:winfix_window)) + + " Make sure the previous window has 'winfixbuf' so we can test that our + " "skip 'winfixbuf' window" logic works. + call win_gotoid(l:winfix_window) + call win_gotoid(l:quickfix_window) + + cprevious + call assert_equal(l:first_window, win_getid()) +endfunc + +" Allow :cnfile but the 'nowinfixbuf' window is selected, instead +func Test_cnfile() + CheckFeature quickfix + call s:reset_all_buffers() + + let [l:first_window, l:winfix_window, l:quickfix_window] = s:make_quickfix_windows() + + " The call to `:cnfile` succeeds but it selects the window with 'nowinfixbuf' instead + call s:set_quickfix_by_buffer(winbufnr(l:winfix_window)) + cnext! + + " Make sure the previous window has 'winfixbuf' so we can test that our + " "skip 'winfixbuf' window" logic works. + call win_gotoid(l:winfix_window) + call win_gotoid(l:quickfix_window) + + cnfile + call assert_equal(l:first_window, win_getid()) +endfunc + +" Allow :cpfile but the 'nowinfixbuf' window is selected, instead +func Test_cpfile() + CheckFeature quickfix + call s:reset_all_buffers() + + let [l:first_window, l:winfix_window, l:quickfix_window] = s:make_quickfix_windows() + + " The call to `:cpfile` succeeds but it selects the window with 'nowinfixbuf' instead + call s:set_quickfix_by_buffer(winbufnr(l:winfix_window)) + cnext! + + " Make sure the previous window has 'winfixbuf' so we can test that our + " "skip 'winfixbuf' window" logic works. + call win_gotoid(l:winfix_window) + call win_gotoid(l:quickfix_window) + + cpfile + call assert_equal(l:first_window, win_getid()) +endfunc + +" Allow :crewind but the 'nowinfixbuf' window is selected, instead +func Test_crewind() + CheckFeature quickfix + call s:reset_all_buffers() + + let [l:first_window, l:winfix_window, l:quickfix_window] = s:make_quickfix_windows() + + " The call to `:crewind` succeeds but it selects the window with 'nowinfixbuf' instead + call s:set_quickfix_by_buffer(winbufnr(l:winfix_window)) + cnext! + + " Make sure the previous window has 'winfixbuf' so we can test that our + " "skip 'winfixbuf' window" logic works. + call win_gotoid(l:winfix_window) + call win_gotoid(l:quickfix_window) + + crewind + call assert_equal(l:first_window, win_getid()) +endfunc + +" Allow f because it opens in a new split +func Test_ctrl_w_f() + call s:reset_all_buffers() + + enew + let l:file_name = tempname() + call writefile([], l:file_name) + let l:file_buffer = bufnr() + + enew + file other + let l:other_buffer = bufnr() + + set winfixbuf + + call setline(1, l:file_name) + let l:current_windows = s:get_windows_count() + execute "normal \f" + + call assert_equal(l:current_windows + 1, s:get_windows_count()) + + call delete(l:file_name) +endfunc + +" Fail :djump but :djump! is allowed +func Test_djump() + call s:reset_all_buffers() + + let l:include_file = tempname() . ".h" + call writefile(["min(1, 12);", + \ '#include "' . l:include_file . '"' + \ ], + \ "main.c") + call writefile(["#define min(X, Y) ((X) < (Y) ? (X) : (Y))"], l:include_file) + edit main.c + + set winfixbuf + + let l:current = bufnr() + + call assert_fails("djump 1 /min/", "E1513:") + call assert_equal(l:current, bufnr()) + + djump! 1 /min/ + call assert_notequal(l:current, bufnr()) + + call delete("main.c") + call delete(l:include_file) +endfunc + +" Fail :drop but :drop! is allowed +func Test_drop() + call s:reset_all_buffers() + + let l:other = s:make_buffer_pairs() + let l:current = bufnr() + + call assert_fails("drop other", "E1513:") + call assert_equal(l:current, bufnr()) + + drop! other + call assert_equal(l:other, bufnr()) +endfunc + +" Fail :edit but :edit! is allowed +func Test_edit() + call s:reset_all_buffers() + + let l:other = s:make_buffer_pairs() + let l:current = bufnr() + + call assert_fails("edit other", "E1513:") + call assert_equal(l:current, bufnr()) + + edit! other + call assert_equal(l:other, bufnr()) +endfunc + +" Fail :enew but :enew! is allowed +func Test_enew() + call s:reset_all_buffers() + + let l:other = s:make_buffer_pairs() + let l:current = bufnr() + + call assert_fails("enew", "E1513:") + call assert_equal(l:current, bufnr()) + + enew! + call assert_notequal(l:other, bufnr()) + call assert_notequal(3, bufnr()) +endfunc + +" Fail :ex but :ex! is allowed +func Test_ex() + call s:reset_all_buffers() + + let l:other = s:make_buffer_pairs() + let l:current = bufnr() + + call assert_fails("ex other", "E1513:") + call assert_equal(l:current, bufnr()) + + ex! other + call assert_equal(l:other, bufnr()) +endfunc + +" Fail :find but :find! is allowed +func Test_find() + call s:reset_all_buffers() + + let l:current = bufnr() + let l:file = tempname() + call writefile([], l:file) + let l:directory = fnamemodify(l:file, ":p:h") + let l:name = fnamemodify(l:file, ":p:t") + + let l:original_path = &path + execute "set path=" . l:directory + + set winfixbuf + + call assert_fails("execute 'find " . l:name . "'", "E1513:") + call assert_equal(l:current, bufnr()) + + execute "find! " . l:name + call assert_equal(l:file, expand("%:p")) + + execute "set path=" . l:original_path + call delete(l:file) +endfunc + +" Fail :first but :first! is allowed +func Test_first() + call s:reset_all_buffers() + + let [l:first, _] = s:make_args_list() + next! + + call assert_fails("first", "E1513:") + call assert_notequal(l:first, bufnr()) + + first! + call assert_equal(l:first, bufnr()) +endfunc + +" Fail :grep but :grep! is allowed +func Test_grep() + CheckFeature quickfix + call s:reset_all_buffers() + + edit first.unittest + call append(0, ["some-search-term"]) + write + let l:first = bufnr() + + edit current.unittest + call append(0, ["some-search-term"]) + write + let l:current = bufnr() + + edit! last.unittest + call append(0, ["some-search-term"]) + write + let l:last = bufnr() + + set winfixbuf + + buffer! current.unittest + + call assert_fails("silent! grep some-search-term *.unittest", "E1513:") + call assert_equal(l:current, bufnr()) + execute "edit! " . l:first + + silent! grep! some-search-term *.unittest + call assert_notequal(l:first, bufnr()) + + call delete("first.unittest") + call delete("current.unittest") + call delete("last.unittest") +endfunc + +" Fail :ijump but :ijump! is allowed +func Test_ijump() + call s:reset_all_buffers() + + let l:include_file = tempname() . ".h" + call writefile([ + \ '#include "' . l:include_file . '"' + \ ], + \ "main.c") + call writefile(["#define min(X, Y) ((X) < (Y) ? (X) : (Y))"], l:include_file) + edit main.c + + set winfixbuf + + let l:current = bufnr() + + set define=^\\s*#\\s*define + set include=^\\s*#\\s*include + set path=.,/usr/include,, + + call assert_fails("ijump /min/", "E1513:") + call assert_equal(l:current, bufnr()) + + set nowinfixbuf + + ijump! /min/ + call assert_notequal(l:current, bufnr()) + + set define& + set include& + set path& + call delete("main.c") + call delete(l:include_file) +endfunc + +" Fail :lNext but :lNext! is allowed +func Test_lNext() + CheckFeature quickfix + call s:reset_all_buffers() + + let [l:first, l:middle, _] = s:make_simple_location_list() + lnext! + + call assert_fails("lNext", "E1513:") + call assert_equal(l:middle, bufnr()) + + lnext! " Reset for the next test + + lNext! + call assert_equal(l:first, bufnr()) +endfunc + +" Fail :lNfile but :lNfile! is allowed +func Test_lNfile() + CheckFeature quickfix + call s:reset_all_buffers() + + let [l:first, l:current, _] = s:make_simple_location_list() + lnext! + + call assert_fails("lNfile", "E1513:") + call assert_equal(l:current, bufnr()) + + lnext! " Reset for the next test + + lNfile! + call assert_equal(l:first, bufnr()) +endfunc + +" Allow :laddexpr because it doesn't change the current buffer +func Test_laddexpr() + CheckFeature quickfix + call s:reset_all_buffers() + + let l:file_path = tempname() + call writefile(["Error - bad-thing-found"], l:file_path) + execute "edit " . l:file_path + let l:file_buffer = bufnr() + let l:current = bufnr() + + edit first.unittest + call append(0, ["some-search-term bad-thing-found"]) + + edit! other.unittest + + set winfixbuf + + execute "buffer! " . l:file_buffer + + execute 'laddexpr expand("%") .. ":" .. line(".") .. ":" .. getline(".")' + call assert_equal(l:current, bufnr()) + + call delete(l:file_path) +endfunc + +" Fail :last but :last! is allowed +func Test_last() + call s:reset_all_buffers() + + let [_, l:last] = s:make_args_list() + next! + + call assert_fails("last", "E1513:") + call assert_notequal(l:last, bufnr()) + + last! + call assert_equal(l:last, bufnr()) +endfunc + +" Fail :lbuffer but :lbuffer! is allowed +func Test_lbuffer() + CheckFeature quickfix + call s:reset_all_buffers() + + let l:file_path = tempname() + call writefile(["first.unittest:1:Error - bad-thing-found"], l:file_path) + execute "edit " . l:file_path + let l:file_buffer = bufnr() + let l:current = bufnr() + + edit first.unittest + call append(0, ["some-search-term bad-thing-found"]) + + edit! other.unittest + + set winfixbuf + + execute "buffer! " . l:file_buffer + + call assert_fails("lbuffer " . l:file_buffer) + call assert_equal(l:current, bufnr()) + + execute "lbuffer! " . l:file_buffer + call assert_equal("first.unittest", expand("%:t")) + + call delete(l:file_path) +endfunc + +" Fail :ldo but :ldo! is allowed +func Test_ldo() + CheckFeature quickfix + call s:reset_all_buffers() + + let [l:first, l:middle, l:last] = s:make_simple_location_list() + lnext! + + call assert_fails('execute "ldo buffer ' . l:first . '"', "E1513:") + call assert_equal(l:middle, bufnr()) + execute "ldo! buffer " . l:first + call assert_notequal(l:last, bufnr()) +endfunc + +" Fail :lfdo but :lfdo! is allowed +func Test_lexpr() + CheckFeature quickfix + call s:reset_all_buffers() + + let l:file = tempname() + let l:entry = '["' . l:file . ':1:bar"]' + let l:current = bufnr() + + set winfixbuf + + call assert_fails("lexpr " . l:entry) + call assert_equal(l:current, bufnr()) + + execute "lexpr! " . l:entry + call assert_equal(fnamemodify(l:file, ":t"), expand("%:t")) +endfunc + +" Fail :lfdo but :lfdo! is allowed +func Test_lfdo() + CheckFeature quickfix + call s:reset_all_buffers() + + let [l:first, l:middle, l:last] = s:make_simple_location_list() + lnext! + + call assert_fails('execute "lfdo buffer ' . l:first . '"', "E1513:") + call assert_equal(l:middle, bufnr()) + execute "lfdo! buffer " . l:first + call assert_notequal(l:last, bufnr()) +endfunc + +" Fail :lfile but :lfile! is allowed +func Test_lfile() + CheckFeature quickfix + call s:reset_all_buffers() + + edit first.unittest + call append(0, ["some-search-term bad-thing-found"]) + write + let l:first = bufnr() + + edit! second.unittest + call append(0, ["some-search-term"]) + write + + let l:file = tempname() + call writefile(["first.unittest:1:Error - bad-thing-found was detected"], l:file) + + let l:current = bufnr() + + set winfixbuf + + call assert_fails(":lfile " . l:file) + call assert_equal(l:current, bufnr()) + + execute ":lfile! " . l:file + call assert_equal(l:first, bufnr()) + + call delete(l:file) + call delete("first.unittest") + call delete("second.unittest") +endfunc + +" Fail :ll but :ll! is allowed +func Test_ll() + CheckFeature quickfix + call s:reset_all_buffers() + + let [l:first, l:middle, l:last] = s:make_simple_location_list() + lopen + lfirst! + execute "normal \j" + normal j + + call assert_fails(".ll", "E1513:") + execute "normal \k" + call assert_equal(l:first, bufnr()) + execute "normal \j" + .ll! + execute "normal \k" + call assert_equal(l:middle, bufnr()) +endfunc + +" Fail :llast but :llast! is allowed +func Test_llast() + CheckFeature quickfix + call s:reset_all_buffers() + + let [l:first, _, l:last] = s:make_simple_location_list() + lfirst! + + call assert_fails("llast", "E1513:") + call assert_equal(l:first, bufnr()) + + llast! + call assert_equal(l:last, bufnr()) +endfunc + +" Fail :lnext but :lnext! is allowed +func Test_lnext() + CheckFeature quickfix + call s:reset_all_buffers() + + let [l:first, l:middle, l:last] = s:make_simple_location_list() + ll! + + call assert_fails("lnext", "E1513:") + call assert_equal(l:first, bufnr()) + + lnext! + call assert_equal(l:middle, bufnr()) +endfunc + +" Fail :lnfile but :lnfile! is allowed +func Test_lnfile() + CheckFeature quickfix + call s:reset_all_buffers() + + let [_, l:current, l:last] = s:make_simple_location_list() + lnext! + + call assert_fails("lnfile", "E1513:") + call assert_equal(l:current, bufnr()) + + lprevious! " Reset for the next test call + + lnfile! + call assert_equal(l:last, bufnr()) +endfunc + +" Fail :lpfile but :lpfile! is allowed +func Test_lpfile() + CheckFeature quickfix + call s:reset_all_buffers() + + let [l:first, l:current, _] = s:make_simple_location_list() + lnext! + + call assert_fails("lpfile", "E1513:") + call assert_equal(l:current, bufnr()) + + lnext! " Reset for the next test call + + lpfile! + call assert_equal(l:first, bufnr()) +endfunc + +" Fail :lprevious but :lprevious! is allowed +func Test_lprevious() + CheckFeature quickfix + call s:reset_all_buffers() + + let [l:first, l:middle, _] = s:make_simple_location_list() + lnext! + + call assert_fails("lprevious", "E1513:") + call assert_equal(l:middle, bufnr()) + + lnext! " Reset for the next test call + + lprevious! + call assert_equal(l:first, bufnr()) +endfunc + +" Fail :lrewind but :lrewind! is allowed +func Test_lrewind() + CheckFeature quickfix + call s:reset_all_buffers() + + let [l:first, l:middle, _] = s:make_simple_location_list() + lnext! + + call assert_fails("lrewind", "E1513:") + call assert_equal(l:middle, bufnr()) + + lrewind! + call assert_equal(l:first, bufnr()) +endfunc + +" Fail :ltag but :ltag! is allowed +func Test_ltag() + call s:reset_all_buffers() + + set tags=Xtags + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "one\tXfile\t1", + \ "three\tXfile\t3", + \ "two\tXfile\t2"], + \ "Xtags") + call writefile(["one", "two", "three"], "Xfile") + call writefile(["one"], "Xother") + edit Xother + execute "normal \" + + set winfixbuf + + let l:current = bufnr() + + call assert_fails("ltag one", "E1513:") + + ltag! one + + set tags& + call delete("Xtags") + call delete("Xfile") + call delete("Xother") +endfunc + +" Fail vim.cmd if we try to change buffers while 'winfixbuf' is set +func Test_lua_command() + call s:reset_all_buffers() + + enew + file first + let l:previous = bufnr() + + enew + file second + let l:current = bufnr() + + set winfixbuf + + call assert_fails('lua vim.cmd("buffer " .. ' . l:previous . ')') + call assert_equal(l:current, bufnr()) + + execute 'lua vim.cmd("buffer! " .. ' . l:previous . ')' + call assert_equal(l:previous, bufnr()) +endfunc + +" Fail :lvimgrep but :lvimgrep! is allowed +func Test_lvimgrep() + CheckFeature quickfix + call s:reset_all_buffers() + + edit first.unittest + call append(0, ["some-search-term"]) + write + + edit winfix.unittest + call append(0, ["some-search-term"]) + write + let l:current = bufnr() + + set winfixbuf + + edit! last.unittest + call append(0, ["some-search-term"]) + write + let l:last = bufnr() + + buffer! winfix.unittest + + call assert_fails("lvimgrep /some-search-term/ *.unittest", "E1513:") + call assert_equal(l:current, bufnr()) + + lvimgrep! /some-search-term/ *.unittest + call assert_notequal(l:current, bufnr()) + + call delete("first.unittest") + call delete("winfix.unittest") + call delete("last.unittest") +endfunc + +" Fail :lvimgrepadd but :lvimgrepadd! is allowed +func Test_lvimgrepadd() + CheckFeature quickfix + call s:reset_all_buffers() + + edit first.unittest + call append(0, ["some-search-term"]) + write + + edit winfix.unittest + call append(0, ["some-search-term"]) + write + let l:current = bufnr() + + set winfixbuf + + edit! last.unittest + call append(0, ["some-search-term"]) + write + let l:last = bufnr() + + buffer! winfix.unittest + + call assert_fails("lvimgrepadd /some-search-term/ *.unittest") + call assert_equal(l:current, bufnr()) + + lvimgrepadd! /some-search-term/ *.unittest + call assert_notequal(l:current, bufnr()) + + call delete("first.unittest") + call delete("winfix.unittest") + call delete("last.unittest") +endfunc + +" Don't allow global marks to change the current 'winfixbuf' window +func Test_marks_mappings_fail() + call s:reset_all_buffers() + + let l:other = s:make_buffer_pairs() + let l:current = bufnr() + execute "buffer! " . l:other + normal mA + execute "buffer! " . l:current + normal mB + + call assert_fails("normal `A", "E1513:") + call assert_equal(l:current, bufnr()) + + call assert_fails("normal 'A", "E1513:") + call assert_equal(l:current, bufnr()) + + set nowinfixbuf + + normal `A + call assert_equal(l:other, bufnr()) +endfunc + +" Allow global marks in a 'winfixbuf' window if the jump is the same buffer +func Test_marks_mappings_pass_intra_move() + call s:reset_all_buffers() + + let l:current = bufnr() + call append(0, ["some line", "another line"]) + normal mA + normal j + normal mB + + set winfixbuf + + normal `A + call assert_equal(l:current, bufnr()) +endfunc + +" Fail :next but :next! is allowed +func Test_next() + call s:reset_all_buffers() + + let [l:first, _] = s:make_args_list() + first! + + call assert_fails("next", "E1513:") + call assert_equal(l:first, bufnr()) + + next! + call assert_notequal(l:first, bufnr()) +endfunc + +" Ensure :mksession saves 'winfixbuf' details +func Test_mksession() + CheckFeature mksession + call s:reset_all_buffers() + + set sessionoptions+=options + set winfixbuf + + mksession test_winfixbuf_Test_mksession.vim + + call s:reset_all_buffers() + let l:winfixbuf = &winfixbuf + call assert_equal(0, l:winfixbuf) + + source test_winfixbuf_Test_mksession.vim + + let l:winfixbuf = &winfixbuf + call assert_equal(1, l:winfixbuf) + + set sessionoptions& + call delete("test_winfixbuf_Test_mksession.vim") +endfunc + +" Allow :next if the next index is the same as the current buffer +func Test_next_same_buffer() + call s:reset_all_buffers() + + enew + file foo + enew + file bar + enew + file fizz + enew + file buzz + args foo foo bar fizz buzz + + edit foo + set winfixbuf + let l:current = bufnr() + + " Allow :next because the args list is `[foo] foo bar fizz buzz + next + call assert_equal(l:current, bufnr()) + + " Fail :next because the args list is `foo [foo] bar fizz buzz + " and the next buffer would be bar, which is a different buffer + call assert_fails("next", "E1513:") + call assert_equal(l:current, bufnr()) +endfunc + +" Fail to jump to a tag with g if 'winfixbuf' is enabled +func Test_normal_g_ctrl_square_bracket_right() + call s:reset_all_buffers() + + set tags=Xtags + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "one\tXfile\t1", + \ "three\tXfile\t3", + \ "two\tXfile\t2"], + \ "Xtags") + call writefile(["one", "two", "three"], "Xfile") + call writefile(["one"], "Xother") + edit Xother + + set winfixbuf + + let l:current = bufnr() + + call assert_fails("normal g\", "E1513:") + call assert_equal(l:current, bufnr()) + + set tags& + call delete("Xtags") + call delete("Xfile") + call delete("Xother") +endfunc + +" Fail to jump to a tag with g if 'winfixbuf' is enabled +func Test_normal_g_rightmouse() + call s:reset_all_buffers() + set mouse=n + + set tags=Xtags + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "one\tXfile\t1", + \ "three\tXfile\t3", + \ "two\tXfile\t2"], + \ "Xtags") + call writefile(["one", "two", "three"], "Xfile") + call writefile(["one"], "Xother") + edit Xother + execute "normal \" + + set winfixbuf + + let l:current = bufnr() + + call assert_fails("normal g\", "E1513:") + call assert_equal(l:current, bufnr()) + + set tags& + set mouse& + call delete("Xtags") + call delete("Xfile") + call delete("Xother") +endfunc + +" Fail to jump to a tag with g] if 'winfixbuf' is enabled +func Test_normal_g_square_bracket_right() + call s:reset_all_buffers() + + set tags=Xtags + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "one\tXfile\t1", + \ "three\tXfile\t3", + \ "two\tXfile\t2"], + \ "Xtags") + call writefile(["one", "two", "three"], "Xfile") + call writefile(["one"], "Xother") + edit Xother + + set winfixbuf + + let l:current = bufnr() + + call assert_fails("normal g]", "E1513:") + call assert_equal(l:current, bufnr()) + + set tags& + call delete("Xtags") + call delete("Xfile") + call delete("Xother") +endfunc + +" Fail to jump to a tag with if 'winfixbuf' is enabled +func Test_normal_ctrl_rightmouse() + call s:reset_all_buffers() + set mouse=n + + set tags=Xtags + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "one\tXfile\t1", + \ "three\tXfile\t3", + \ "two\tXfile\t2"], + \ "Xtags") + call writefile(["one", "two", "three"], "Xfile") + call writefile(["one"], "Xother") + edit Xother + execute "normal \" + + set winfixbuf + + let l:current = bufnr() + + call assert_fails("normal \", "E1513:") + call assert_equal(l:current, bufnr()) + + set tags& + set mouse& + call delete("Xtags") + call delete("Xfile") + call delete("Xother") +endfunc + +" Fail to jump to a tag with if 'winfixbuf' is enabled +func Test_normal_ctrl_t() + call s:reset_all_buffers() + + set tags=Xtags + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "one\tXfile\t1", + \ "three\tXfile\t3", + \ "two\tXfile\t2"], + \ "Xtags") + call writefile(["one", "two", "three"], "Xfile") + call writefile(["one"], "Xother") + edit Xother + execute "normal \" + + set winfixbuf + + let l:current = bufnr() + + call assert_fails("normal \", "E1513:") + call assert_equal(l:current, bufnr()) + + set tags& + call delete("Xtags") + call delete("Xfile") + call delete("Xother") +endfunc + +" Disallow in 'winfixbuf' windows +func Test_normal_ctrl_hat() + call s:reset_all_buffers() + clearjumps + + enew + file first + let l:first = bufnr() + + enew + file current + let l:current = bufnr() + + set winfixbuf + + call assert_fails("normal \", "E1513:") + call assert_equal(l:current, bufnr()) +endfunc + +" Allow in 'winfixbuf' windows if the movement stays within the buffer +func Test_normal_ctrl_i_pass() + call s:reset_all_buffers() + clearjumps + + enew + file first + let l:first = bufnr() + + enew! + file current + let l:current = bufnr() + " Add some lines so we can populate a jumplist" + call append(0, ["some line", "another line"]) + " Add an entry to the jump list + " Go up another line + normal m` + normal k + execute "normal \" + + set winfixbuf + + let l:line = getcurpos()[1] + execute "normal 1\" + call assert_notequal(l:line, getcurpos()[1]) +endfunc + +" Disallow in 'winfixbuf' windows if it would cause the buffer to switch +func Test_normal_ctrl_o_fail() + call s:reset_all_buffers() + clearjumps + + enew + file first + let l:first = bufnr() + + enew + file current + let l:current = bufnr() + + set winfixbuf + + call assert_fails("normal \", "E1513:") + call assert_equal(l:current, bufnr()) +endfunc + +" Allow in 'winfixbuf' windows if the movement stays within the buffer +func Test_normal_ctrl_o_pass() + call s:reset_all_buffers() + clearjumps + + enew + file first + let l:first = bufnr() + + enew! + file current + let l:current = bufnr() + " Add some lines so we can populate a jumplist + call append(0, ["some line", "another line"]) + " Add an entry to the jump list + " Go up another line + normal m` + normal k + + set winfixbuf + + execute "normal \" + call assert_equal(l:current, bufnr()) +endfunc + +" Fail to jump to a tag with if 'winfixbuf' is enabled +func Test_normal_ctrl_square_bracket_right() + call s:reset_all_buffers() + + set tags=Xtags + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "one\tXfile\t1", + \ "three\tXfile\t3", + \ "two\tXfile\t2"], + \ "Xtags") + call writefile(["one", "two", "three"], "Xfile") + call writefile(["one"], "Xother") + edit Xother + + set winfixbuf + + let l:current = bufnr() + + call assert_fails("normal \", "E1513:") + call assert_equal(l:current, bufnr()) + + set tags& + call delete("Xtags") + call delete("Xfile") + call delete("Xother") +endfunc + +" Allow with 'winfixbuf' enabled because it runs in a new, split window +func Test_normal_ctrl_w_ctrl_square_bracket_right() + call s:reset_all_buffers() + + set tags=Xtags + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "one\tXfile\t1", + \ "three\tXfile\t3", + \ "two\tXfile\t2"], + \ "Xtags") + call writefile(["one", "two", "three"], "Xfile") + call writefile(["one"], "Xother") + edit Xother + + set winfixbuf + + let l:current_windows = s:get_windows_count() + execute "normal \\" + call assert_equal(l:current_windows + 1, s:get_windows_count()) + + set tags& + call delete("Xtags") + call delete("Xfile") + call delete("Xother") +endfunc + +" Allow g with 'winfixbuf' enabled because it runs in a new, split window +func Test_normal_ctrl_w_g_ctrl_square_bracket_right() + call s:reset_all_buffers() + + set tags=Xtags + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "one\tXfile\t1", + \ "three\tXfile\t3", + \ "two\tXfile\t2"], + \ "Xtags") + call writefile(["one", "two", "three"], "Xfile") + call writefile(["one"], "Xother") + edit Xother + + set winfixbuf + + let l:current_windows = s:get_windows_count() + execute "normal \g\" + call assert_equal(l:current_windows + 1, s:get_windows_count()) + + set tags& + call delete("Xtags") + call delete("Xfile") + call delete("Xother") +endfunc + +" Fail to jump to a tag with if 'winfixbuf' is enabled +func Test_normal_gt() + call s:reset_all_buffers() + + set tags=Xtags + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "one\tXfile\t1", + \ "three\tXfile\t3", + \ "two\tXfile\t2"], + \ "Xtags") + call writefile(["one", "two", "three"], "Xfile") + call writefile(["one", "two", "three"], "Xother") + edit Xother + + set winfixbuf + + let l:current = bufnr() + + call assert_fails("normal \", "E1513:") + call assert_equal(l:current, bufnr()) + + set tags& + call delete("Xtags") + call delete("Xfile") + call delete("Xother") +endfunc + +" Prevent gF from switching a 'winfixbuf' window's buffer +func Test_normal_gF() + call s:reset_all_buffers() + + let l:file = tempname() + call append(0, [l:file]) + call writefile([], l:file) + " Place the cursor onto the line that has `l:file` + normal gg + " Prevent Vim from erroring with "No write since last change @ command + " line" when we try to call gF, later. + set hidden + + set winfixbuf + + let l:buffer = bufnr() + + call assert_fails("normal gF", "E1513:") + call assert_equal(l:buffer, bufnr()) + + set nowinfixbuf + + normal gF + call assert_notequal(l:buffer, bufnr()) + + call delete(l:file) +endfunc + +" Prevent gf from switching a 'winfixbuf' window's buffer +func Test_normal_gf() + call s:reset_all_buffers() + + let l:file = tempname() + call append(0, [l:file]) + call writefile([], l:file) + " Place the cursor onto the line that has `l:file` + normal gg + " Prevent Vim from erroring with "No write since last change @ command + " line" when we try to call gf, later. + set hidden + + set winfixbuf + + let l:buffer = bufnr() + + call assert_fails("normal gf", "E1513:") + call assert_equal(l:buffer, bufnr()) + + set nowinfixbuf + + normal gf + call assert_notequal(l:buffer, bufnr()) + + call delete(l:file) +endfunc + +" Fail "goto file under the cursor" (using [f, which is the same as `:normal gf`) +func Test_normal_square_bracket_left_f() + call s:reset_all_buffers() + + let l:file = tempname() + call append(0, [l:file]) + call writefile([], l:file) + " Place the cursor onto the line that has `l:file` + normal gg + " Prevent Vim from erroring with "No write since last change @ command + " line" when we try to call gf, later. + set hidden + + set winfixbuf + + let l:buffer = bufnr() + + call assert_fails("normal [f", "E1513:") + call assert_equal(l:buffer, bufnr()) + + set nowinfixbuf + + normal [f + call assert_notequal(l:buffer, bufnr()) + + call delete(l:file) +endfunc + +" Fail to go to a C macro with [ if 'winfixbuf' is enabled +func Test_normal_square_bracket_left_ctrl_d() + call s:reset_all_buffers() + + let l:include_file = tempname() . ".h" + call writefile(["min(1, 12);", + \ '#include "' . l:include_file . '"' + \ ], + \ "main.c") + call writefile(["#define min(X, Y) ((X) < (Y) ? (X) : (Y))"], l:include_file) + edit main.c + normal ]\ + + set winfixbuf + + let l:current = bufnr() + + call assert_fails("normal [\", "E1513:") + call assert_equal(l:current, bufnr()) + + set nowinfixbuf + + execute "normal [\" + call assert_notequal(l:current, bufnr()) + + call delete("main.c") + call delete(l:include_file) +endfunc + +" Fail to go to a C macro with ] if 'winfixbuf' is enabled +func Test_normal_square_bracket_right_ctrl_d() + call s:reset_all_buffers() + + let l:include_file = tempname() . ".h" + call writefile(["min(1, 12);", + \ '#include "' . l:include_file . '"' + \ ], + \ "main.c") + call writefile(["#define min(X, Y) ((X) < (Y) ? (X) : (Y))"], l:include_file) + edit main.c + + set winfixbuf + + let l:current = bufnr() + + call assert_fails("normal ]\", "E1513:") + call assert_equal(l:current, bufnr()) + + set nowinfixbuf + + execute "normal ]\" + call assert_notequal(l:current, bufnr()) + + call delete("main.c") + call delete(l:include_file) +endfunc + +" Fail to go to a C macro with [ if 'winfixbuf' is enabled +func Test_normal_square_bracket_left_ctrl_i() + call s:reset_all_buffers() + + let l:include_file = tempname() . ".h" + call writefile(['#include "' . l:include_file . '"', + \ "min(1, 12);", + \ ], + \ "main.c") + call writefile(["#define min(X, Y) ((X) < (Y) ? (X) : (Y))"], l:include_file) + edit main.c + " Move to the line with `min(1, 12);` on it" + normal j + + set define=^\\s*#\\s*define + set include=^\\s*#\\s*include + set path=.,/usr/include,, + + let l:current = bufnr() + + set winfixbuf + + call assert_fails("normal [\", "E1513:") + + set nowinfixbuf + + execute "normal [\" + call assert_notequal(l:current, bufnr()) + + set define& + set include& + set path& + call delete("main.c") + call delete(l:include_file) +endfunc + +" Fail to go to a C macro with ] if 'winfixbuf' is enabled +func Test_normal_square_bracket_right_ctrl_i() + call s:reset_all_buffers() + + let l:include_file = tempname() . ".h" + call writefile(["min(1, 12);", + \ '#include "' . l:include_file . '"' + \ ], + \ "main.c") + call writefile(["#define min(X, Y) ((X) < (Y) ? (X) : (Y))"], l:include_file) + edit main.c + + set winfixbuf + + set define=^\\s*#\\s*define + set include=^\\s*#\\s*include + set path=.,/usr/include,, + + let l:current = bufnr() + + call assert_fails("normal ]\", "E1513:") + call assert_equal(l:current, bufnr()) + + set nowinfixbuf + + execute "normal ]\" + call assert_notequal(l:current, bufnr()) + + set define& + set include& + set path& + call delete("main.c") + call delete(l:include_file) +endfunc + +" Fail "goto file under the cursor" (using ]f, which is the same as `:normal gf`) +func Test_normal_square_bracket_right_f() + call s:reset_all_buffers() + + let l:file = tempname() + call append(0, [l:file]) + call writefile([], l:file) + " Place the cursor onto the line that has `l:file` + normal gg + " Prevent Vim from erroring with "No write since last change @ command + " line" when we try to call gf, later. + set hidden + + set winfixbuf + + let l:buffer = bufnr() + + call assert_fails("normal ]f", "E1513:") + call assert_equal(l:buffer, bufnr()) + + set nowinfixbuf + + normal ]f + call assert_notequal(l:buffer, bufnr()) + + call delete(l:file) +endfunc + +" Fail to jump to a tag with v if 'winfixbuf' is enabled +func Test_normal_v_ctrl_square_bracket_right() + call s:reset_all_buffers() + + set tags=Xtags + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "one\tXfile\t1", + \ "three\tXfile\t3", + \ "two\tXfile\t2"], + \ "Xtags") + call writefile(["one", "two", "three"], "Xfile") + call writefile(["one"], "Xother") + edit Xother + + set winfixbuf + + let l:current = bufnr() + + call assert_fails("normal v\", "E1513:") + call assert_equal(l:current, bufnr()) + + set tags& + call delete("Xtags") + call delete("Xfile") + call delete("Xother") +endfunc + +" Fail to jump to a tag with vg if 'winfixbuf' is enabled +func Test_normal_v_g_ctrl_square_bracket_right() + call s:reset_all_buffers() + + set tags=Xtags + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "one\tXfile\t1", + \ "three\tXfile\t3", + \ "two\tXfile\t2"], + \ "Xtags") + call writefile(["one", "two", "three"], "Xfile") + call writefile(["one"], "Xother") + edit Xother + + set winfixbuf + + let l:current = bufnr() + + call assert_fails("normal vg\", "E1513:") + call assert_equal(l:current, bufnr()) + + set tags& + call delete("Xtags") + call delete("Xfile") + call delete("Xother") +endfunc + +" Allow :pedit because, unlike :edit, it uses a separate window +func Test_pedit() + call s:reset_all_buffers() + + let l:other = s:make_buffer_pairs() + + pedit other + + execute "normal \w" + call assert_equal(l:other, bufnr()) +endfunc + +" Fail :pop but :pop! is allowed +func Test_pop() + call s:reset_all_buffers() + + set tags=Xtags + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "thesame\tXfile\t1;\"\td\tfile:", + \ "thesame\tXfile\t2;\"\td\tfile:", + \ "thesame\tXfile\t3;\"\td\tfile:", + \ ], + \ "Xtags") + call writefile(["thesame one", "thesame two", "thesame three"], "Xfile") + call writefile(["thesame one"], "Xother") + edit Xother + + tag thesame + + set winfixbuf + + let l:current = bufnr() + + call assert_fails("pop", "E1513:") + call assert_equal(l:current, bufnr()) + + pop! + call assert_notequal(l:current, bufnr()) + + set tags& + call delete("Xtags") + call delete("Xfile") + call delete("Xother") +endfunc + +" Fail :previous but :previous! is allowed +func Test_previous() + call s:reset_all_buffers() + + let [l:first, _] = s:make_args_list() + next! + + call assert_fails("previous", "E1513:") + call assert_notequal(l:first, bufnr()) + + previous! + call assert_equal(l:first, bufnr()) +endfunc + +" Fail pydo if it changes a window with 'winfixbuf' is set +func Test_python_pydo() + CheckFeature pythonx + call s:reset_all_buffers() + + enew + file first + let g:_previous_buffer = bufnr() + + enew + file second + + set winfixbuf + + python << EOF +import vim + +def test_winfixbuf_Test_python_pydo_set_buffer(): + buffer = vim.vars['_previous_buffer'] + vim.current.buffer = vim.buffers[buffer] +EOF + + try + pydo test_winfixbuf_Test_python_pydo_set_buffer() + catch /pynvim\.api\.common\.NvimError: E1513: Cannot edit buffer\. 'winfixbuf' is enabled/ + let l:caught = 1 + endtry + + call assert_equal(1, l:caught) + + unlet g:_previous_buffer +endfunc + +" Fail pyfile if it changes a window with 'winfixbuf' is set +func Test_python_pyfile() + CheckFeature pythonx + call s:reset_all_buffers() + + enew + file first + let g:_previous_buffer = bufnr() + + enew + file second + + set winfixbuf + + call writefile(["import vim", + \ "buffer = vim.vars['_previous_buffer']", + \ "vim.current.buffer = vim.buffers[buffer]", + \ ], + \ "file.py") + + try + pyfile file.py + catch /pynvim\.api\.common\.NvimError: E1513: Cannot edit buffer\. 'winfixbuf' is enabled/ + let l:caught = 1 + endtry + + call assert_equal(1, l:caught) + + call delete("file.py") + unlet g:_previous_buffer +endfunc + +" Fail vim.current.buffer if 'winfixbuf' is set +func Test_python_vim_current_buffer() + CheckFeature pythonx + call s:reset_all_buffers() + + enew + file first + let g:_previous_buffer = bufnr() + + enew + file second + + let l:caught = 0 + + set winfixbuf + + try + python << EOF +import vim + +buffer = vim.vars["_previous_buffer"] +vim.current.buffer = vim.buffers[buffer] +EOF + catch /pynvim\.api\.common\.NvimError: E1513: Cannot edit buffer\. 'winfixbuf' is enabled/ + let l:caught = 1 + endtry + + call assert_equal(1, l:caught) + unlet g:_previous_buffer +endfunc + +" Ensure remapping to a disabled action still triggers failures +func Test_remap_key_fail() + call s:reset_all_buffers() + + enew + file first + let l:first = bufnr() + + enew + file current + let l:current = bufnr() + + set winfixbuf + + nnoremap g + + call assert_fails("normal g", "E1513:") + call assert_equal(l:current, bufnr()) + + nunmap g +endfunc + +" Ensure remapping a disabled key to something valid does trigger any failures +func Test_remap_key_pass() + call s:reset_all_buffers() + + enew + file first + let l:first = bufnr() + + enew + file current + let l:current = bufnr() + + set winfixbuf + + call assert_fails("normal \", "E1513:") + call assert_equal(l:current, bufnr()) + + " Disallow by default but allow it if the command does something else + nnoremap :echo "hello!" + + execute "normal \" + call assert_equal(l:current, bufnr()) + + nunmap +endfunc + +" Fail :rewind but :rewind! is allowed +func Test_rewind() + call s:reset_all_buffers() + + let [l:first, _] = s:make_args_list() + next! + + call assert_fails("rewind", "E1513:") + call assert_notequal(l:first, bufnr()) + + rewind! + call assert_equal(l:first, bufnr()) +endfunc + +" Allow :sblast because it opens the buffer in a new, split window +func Test_sblast() + call s:reset_all_buffers() + + let l:other = s:make_buffer_pairs(1) + bfirst! + let l:current = bufnr() + + sblast + call assert_equal(l:other, bufnr()) +endfunc + +" Fail :sbprevious but :sbprevious! is allowed +func Test_sbprevious() + call s:reset_all_buffers() + + let l:other = s:make_buffer_pairs() + let l:current = bufnr() + + sbprevious + call assert_equal(l:other, bufnr()) +endfunc + +" Make sure 'winfixbuf' can be set using 'winfixbuf' or 'wfb' +func Test_short_option() + call s:reset_all_buffers() + + call s:make_buffer_pairs() + + set winfixbuf + call assert_fails("edit something_else", "E1513") + + set nowinfixbuf + set wfb + call assert_fails("edit another_place", "E1513") + + set nowfb + edit last_place +endfunc + +" Allow :snext because it makes a new window +func Test_snext() + call s:reset_all_buffers() + + let [l:first, _] = s:make_args_list() + first! + + let l:current_window = win_getid() + + snext + call assert_notequal(l:current_window, win_getid()) + call assert_notequal(l:first, bufnr()) +endfunc + +" Ensure the first has 'winfixbuf' and a new split window is 'nowinfixbuf' +func Test_split_window() + call s:reset_all_buffers() + + split + execute "normal \j" + + set winfixbuf + + let l:winfix_window_1 = win_getid() + vsplit + let l:winfix_window_2 = win_getid() + + call assert_equal(1, getwinvar(l:winfix_window_1, "&winfixbuf")) + call assert_equal(0, getwinvar(l:winfix_window_2, "&winfixbuf")) +endfunc + +" Fail :tNext but :tNext! is allowed +func Test_tNext() + call s:reset_all_buffers() + + set tags=Xtags + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "thesame\tXfile\t1;\"\td\tfile:", + \ "thesame\tXfile\t2;\"\td\tfile:", + \ "thesame\tXfile\t3;\"\td\tfile:", + \ ], + \ "Xtags") + call writefile(["thesame one", "thesame two", "thesame three"], "Xfile") + call writefile(["thesame one"], "Xother") + edit Xother + + tag thesame + execute "normal \" + tnext! + + set winfixbuf + + let l:current = bufnr() + + call assert_fails("tNext", "E1513:") + call assert_equal(l:current, bufnr()) + + tNext! + + set tags& + call delete("Xtags") + call delete("Xfile") + call delete("Xother") +endfunc + +" Call :tabdo and choose the next available 'nowinfixbuf' window. +func Test_tabdo_choose_available_window() + call s:reset_all_buffers() + + let [l:first, _] = s:make_args_list() + + " Make a split window that is 'nowinfixbuf' but make it the second-to-last + " window so that :tabdo will first try the 'winfixbuf' window, pass over it, + " and prefer the other 'nowinfixbuf' window, instead. + " + " +-------------------+ + " | 'nowinfixbuf' | + " +-------------------+ + " | 'winfixbuf' | <-- Cursor is here + " +-------------------+ + split + let l:nowinfixbuf_window = win_getid() + " Move to the 'winfixbuf' window now + execute "normal \j" + let l:winfixbuf_window = win_getid() + + let l:expected_windows = s:get_windows_count() + tabdo echo '' + call assert_equal(l:nowinfixbuf_window, win_getid()) + call assert_equal(l:first, bufnr()) + call assert_equal(l:expected_windows, s:get_windows_count()) +endfunc + +" Call :tabdo and create a new split window if all available windows are 'winfixbuf'. +func Test_tabdo_make_new_window() + call s:reset_all_buffers() + + let [l:first, _] = s:make_buffers_list() + execute "buffer! " . l:first + + let l:current = win_getid() + let l:current_windows = s:get_windows_count() + + tabdo echo '' + call assert_notequal(l:current, win_getid()) + call assert_equal(l:first, bufnr()) + execute "normal \j" + call assert_equal(l:first, bufnr()) + call assert_equal(l:current_windows + 1, s:get_windows_count()) +endfunc + +" Fail :tag but :tag! is allowed +func Test_tag() + call s:reset_all_buffers() + + set tags=Xtags + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "one\tXfile\t1", + \ "three\tXfile\t3", + \ "two\tXfile\t2"], + \ "Xtags") + call writefile(["one", "two", "three"], "Xfile") + call writefile(["one"], "Xother") + edit Xother + + set winfixbuf + + let l:current = bufnr() + + call assert_fails("tag one", "E1513:") + call assert_equal(l:current, bufnr()) + + tag! one + call assert_notequal(l:current, bufnr()) + + set tags& + call delete("Xtags") + call delete("Xfile") + call delete("Xother") +endfunc + + +" Fail :tfirst but :tfirst! is allowed +func Test_tfirst() + call s:reset_all_buffers() + + set tags=Xtags + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "one\tXfile\t1", + \ "three\tXfile\t3", + \ "two\tXfile\t2"], + \ "Xtags") + call writefile(["one", "two", "three"], "Xfile") + call writefile(["one"], "Xother") + edit Xother + + set winfixbuf + + let l:current = bufnr() + + call assert_fails("tfirst", "E1513:") + call assert_equal(l:current, bufnr()) + + tfirst! + call assert_notequal(l:current, bufnr()) + + set tags& + call delete("Xtags") + call delete("Xfile") + call delete("Xother") +endfunc + +" Fail :tjump but :tjump! is allowed +func Test_tjump() + call s:reset_all_buffers() + + set tags=Xtags + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "one\tXfile\t1", + \ "three\tXfile\t3", + \ "two\tXfile\t2"], + \ "Xtags") + call writefile(["one", "two", "three"], "Xfile") + call writefile(["one"], "Xother") + edit Xother + + set winfixbuf + + let l:current = bufnr() + + call assert_fails("tjump one", "E1513:") + call assert_equal(l:current, bufnr()) + + tjump! one + call assert_notequal(l:current, bufnr()) + + set tags& + call delete("Xtags") + call delete("Xfile") + call delete("Xother") +endfunc + +" Fail :tlast but :tlast! is allowed +func Test_tlast() + call s:reset_all_buffers() + + set tags=Xtags + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "one\tXfile\t1", + \ "three\tXfile\t3", + \ "two\tXfile\t2"], + \ "Xtags") + call writefile(["one", "two", "three"], "Xfile") + edit Xfile + tjump one + edit Xfile + + set winfixbuf + + let l:current = bufnr() + + call assert_fails("tlast", "E1513:") + call assert_equal(l:current, bufnr()) + + tlast! + call assert_equal(l:current, bufnr()) + + set tags& + call delete("Xtags") + call delete("Xfile") +endfunc + +" Fail :tnext but :tnext! is allowed +func Test_tnext() + call s:reset_all_buffers() + + set tags=Xtags + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "thesame\tXfile\t1;\"\td\tfile:", + \ "thesame\tXfile\t2;\"\td\tfile:", + \ "thesame\tXfile\t3;\"\td\tfile:", + \ ], + \ "Xtags") + call writefile(["thesame one", "thesame two", "thesame three"], "Xfile") + call writefile(["thesame one"], "Xother") + edit Xother + + tag thesame + execute "normal \" + + set winfixbuf + + let l:current = bufnr() + + call assert_fails("tnext", "E1513:") + call assert_equal(l:current, bufnr()) + + tnext! + call assert_notequal(l:current, bufnr()) + + set tags& + call delete("Xtags") + call delete("Xfile") + call delete("Xother") +endfunc + +" Fail :tprevious but :tprevious! is allowed +func Test_tprevious() + call s:reset_all_buffers() + + set tags=Xtags + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "thesame\tXfile\t1;\"\td\tfile:", + \ "thesame\tXfile\t2;\"\td\tfile:", + \ "thesame\tXfile\t3;\"\td\tfile:", + \ ], + \ "Xtags") + call writefile(["thesame one", "thesame two", "thesame three"], "Xfile") + call writefile(["thesame one"], "Xother") + edit Xother + + tag thesame + execute "normal \" + tnext! + + set winfixbuf + + let l:current = bufnr() + + call assert_fails("tprevious", "E1513:") + call assert_equal(l:current, bufnr()) + + tprevious! + + set tags& + call delete("Xtags") + call delete("Xfile") + call delete("Xother") +endfunc + +" Fail :view but :view! is allowed +func Test_view() + call s:reset_all_buffers() + + let l:other = s:make_buffer_pairs() + let l:current = bufnr() + + call assert_fails("view other", "E1513:") + call assert_equal(l:current, bufnr()) + + view! other + call assert_equal(l:other, bufnr()) +endfunc + +" Fail :visual but :visual! is allowed +func Test_visual() + call s:reset_all_buffers() + + let l:other = s:make_buffer_pairs() + let l:current = bufnr() + + call assert_fails("visual other", "E1513:") + call assert_equal(l:current, bufnr()) + + visual! other + call assert_equal(l:other, bufnr()) +endfunc + +" Fail :vimgrep but :vimgrep! is allowed +func Test_vimgrep() + CheckFeature quickfix + call s:reset_all_buffers() + + edit first.unittest + call append(0, ["some-search-term"]) + write + + edit winfix.unittest + call append(0, ["some-search-term"]) + write + let l:current = bufnr() + + set winfixbuf + + edit! last.unittest + call append(0, ["some-search-term"]) + write + let l:last = bufnr() + + buffer! winfix.unittest + + call assert_fails("vimgrep /some-search-term/ *.unittest") + call assert_equal(l:current, bufnr()) + + " Don't error and also do swap to the first match because ! was included + vimgrep! /some-search-term/ *.unittest + call assert_notequal(l:current, bufnr()) + + call delete("first.unittest") + call delete("winfix.unittest") + call delete("last.unittest") +endfunc + +" Fail :vimgrepadd but ::vimgrepadd! is allowed +func Test_vimgrepadd() + CheckFeature quickfix + call s:reset_all_buffers() + + edit first.unittest + call append(0, ["some-search-term"]) + write + + edit winfix.unittest + call append(0, ["some-search-term"]) + write + let l:current = bufnr() + + set winfixbuf + + edit! last.unittest + call append(0, ["some-search-term"]) + write + let l:last = bufnr() + + buffer! winfix.unittest + + call assert_fails("vimgrepadd /some-search-term/ *.unittest") + call assert_equal(l:current, bufnr()) + + vimgrepadd! /some-search-term/ *.unittest + call assert_notequal(l:current, bufnr()) + call delete("first.unittest") + call delete("winfix.unittest") + call delete("last.unittest") +endfunc + +" Fail :wNext but :wNext! is allowed +func Test_wNext() + call s:reset_all_buffers() + + let [l:first, _] = s:make_args_list() + next! + + call assert_fails("wNext", "E1513:") + call assert_notequal(l:first, bufnr()) + + wNext! + call assert_equal(l:first, bufnr()) + + call delete("first") + call delete("middle") + call delete("last") +endfunc + +" Allow :windo unless `:windo foo` would change a 'winfixbuf' window's buffer +func Test_windo() + call s:reset_all_buffers() + + let l:current_window = win_getid() + let l:current_buffer = bufnr() + split + enew + file some_other_buffer + + set winfixbuf + + let l:current = win_getid() + + windo echo '' + call assert_equal(l:current_window, win_getid()) + + call assert_fails('execute "windo buffer ' . l:current_buffer . '"', "E1513:") + call assert_equal(l:current_window, win_getid()) + + execute "windo buffer! " . l:current_buffer + call assert_equal(l:current_window, win_getid()) +endfunc + +" Fail :wnext but :wnext! is allowed +func Test_wnext() + call s:reset_all_buffers() + + let [_, l:last] = s:make_args_list() + next! + + call assert_fails("wnext", "E1513:") + call assert_notequal(l:last, bufnr()) + + wnext! + call assert_equal(l:last, bufnr()) + + call delete("first") + call delete("middle") + call delete("last") +endfunc + +" Fail :wprevious but :wprevious! is allowed +func Test_wprevious() + call s:reset_all_buffers() + + let [l:first, _] = s:make_args_list() + next! + + call assert_fails("wprevious", "E1513:") + call assert_notequal(l:first, bufnr()) + + wprevious! + call assert_equal(l:first, bufnr()) + + call delete("first") + call delete("middle") + call delete("last") +endfunc -- cgit From e8bc23db62c37e91d0c277b4bddf652db6a9d5f1 Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Mon, 4 Mar 2024 21:25:15 -0800 Subject: vim-patch:9.1.0149: null pointer member access when accessing 'winfixbuf' property Problem: qf_goto_win_with_qfl_file may check if prevwin has 'winfixbuf' set without checking if it's valid first. Solution: Reverse the condition. Add a test, a modeline, and a missing CheckFeature. (Searn Dewar) closes: vim/vim#14140 https://github.com/vim/vim/commit/5131f224da93f2e042a4b22545ef62b1b2ab8460 Co-authored-by: Sean Dewar <6256228+seandewar@users.noreply.github.com> --- src/nvim/quickfix.c | 2 +- test/old/testdir/test_winfixbuf.vim | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index a88b781f32..3dd4e35f65 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -2699,7 +2699,7 @@ static void qf_goto_win_with_qfl_file(int qf_fnum) // Didn't find it, go to the window before the quickfix // window, unless 'switchbuf' contains 'uselast': in this case we // try to jump to the previously used window first. - if ((swb_flags & SWB_USELAST) && !prevwin->w_p_wfb && win_valid(prevwin)) { + if ((swb_flags & SWB_USELAST) && win_valid(prevwin) && !prevwin->w_p_wfb) { win = prevwin; } else if (altwin != NULL) { win = altwin; diff --git a/test/old/testdir/test_winfixbuf.vim b/test/old/testdir/test_winfixbuf.vim index 2948be7859..5412fecd07 100644 --- a/test/old/testdir/test_winfixbuf.vim +++ b/test/old/testdir/test_winfixbuf.vim @@ -1576,6 +1576,7 @@ endfunc " Fail vim.cmd if we try to change buffers while 'winfixbuf' is set func Test_lua_command() + " CheckFeature lua call s:reset_all_buffers() enew @@ -3129,3 +3130,21 @@ func Test_wprevious() call delete("middle") call delete("last") endfunc + +func Test_quickfix_switchbuf_invalid_prevwin() + call s:reset_all_buffers() + + let [l:first, _] = s:make_simple_quickfix() + call assert_notequal(l:first, bufnr()) + call assert_equal(1, winnr('$')) + + set switchbuf=uselast + split + copen + execute winnr('#') 'quit' + + call assert_fails('cfirst', 'E1513:') + set switchbuf& +endfunc + +" vim: shiftwidth=2 sts=2 expandtab -- cgit From 5931f2bc4ac319e5fa617b36cbe5305228125c11 Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Tue, 5 Mar 2024 23:39:30 -0800 Subject: vim-patch:9.1.0150: Several minor 'winfixbuf' issues Problem: several minor 'winfixbuf' issues exist, mostly relating to the quickfix list Solution: address them and adjust tests. Retab and reflow a few things too. (Sean Dewar) Things touched include: - Replace the semsgs with gettext'd emsgs. - Handle window switching in ex_listdo properly, so curbuf and curwin are kept in-sync and trigger autocommands; handle those properly. - Don't change the list entry index in qf_jump_edit_buffer if we fail due to 'wfb' (achieved by returning FAIL; QF_ABORT should only be used if the list was changed). - Make qf_jump_edit_buffer actually switch to prevwin when using `:cXX` commands **outside** of the list window if 'wfb' is set in curwin. Handle autocommands properly in case they mess with the list. NOTE: previously, it seemed to split if 'wfb' was set, but do nothing and fail if prevwin is *valid*. This behaviour seemed strange, and maybe unintentional? Now it aligns more with what's described for the `:cXX` commands in the original PR description when used outside a list window, I think. - In both functions, only consider prevwin if 'wfb' isn't set for it; fallback to splitting otherwise. - Use win_split to split. Not sure if there was a specific reason for using ex_splitview. win_split is simpler and respects modifiers like :vertical that may have been used. Plus, its return value can be checked for setting opened_window in qf code (technically win_split_ins autocmds could immediately close it or change windows, in which the qf code might close some other window on failure; it's already the case elsewhere, though). closes: vim/vim#14142 https://github.com/vim/vim/commit/4bb505e28cac0389561fff78d8bbe0319c2bcf2f Co-authored-by: Sean Dewar <6256228+seandewar@users.noreply.github.com> --- src/nvim/ex_cmds2.c | 25 ++++---- src/nvim/quickfix.c | 44 ++++++++----- src/nvim/window.c | 4 +- test/old/testdir/test_winfixbuf.vim | 121 +++++++++++++++++++++++++++++++++--- 4 files changed, 157 insertions(+), 37 deletions(-) diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 3120868350..dacdb27b08 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -447,21 +447,24 @@ void ex_listdo(exarg_T *eap) if (curwin->w_p_wfb) { if ((eap->cmdidx == CMD_ldo || eap->cmdidx == CMD_lfdo) && !eap->forceit) { // Disallow :ldo if 'winfixbuf' is applied - semsg("%s", e_winfixbuf_cannot_go_to_buffer); + emsg(_(e_winfixbuf_cannot_go_to_buffer)); return; } - if (win_valid(prevwin)) { - // Change the current window to another because 'winfixbuf' is enabled - curwin = prevwin; - } else { + if (win_valid(prevwin) && !prevwin->w_p_wfb) { + // 'winfixbuf' is set; attempt to change to a window without it. + win_goto(prevwin); + } + if (curwin->w_p_wfb) { // Split the window, which will be 'nowinfixbuf', and set curwin to that - exarg_T new_eap = { - .cmdidx = CMD_split, - .cmd = "split", - .arg = "", - }; - ex_splitview(&new_eap); + win_split(0, 0); + + if (curwin->w_p_wfb) { + // Autocommands set 'winfixbuf' or sent us to another window + // with it set. Give up. + emsg(_(e_winfixbuf_cannot_go_to_buffer)); + return; + } } } diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 3dd4e35f65..28691914bb 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -2699,7 +2699,8 @@ static void qf_goto_win_with_qfl_file(int qf_fnum) // Didn't find it, go to the window before the quickfix // window, unless 'switchbuf' contains 'uselast': in this case we // try to jump to the previously used window first. - if ((swb_flags & SWB_USELAST) && win_valid(prevwin) && !prevwin->w_p_wfb) { + if ((swb_flags & SWB_USELAST) && win_valid(prevwin) + && !prevwin->w_p_wfb) { win = prevwin; } else if (altwin != NULL) { win = altwin; @@ -2803,27 +2804,42 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, int ECMD_HIDE + ECMD_SET_HELP, prev_winid == curwin->handle ? curwin : NULL); } else { - if (!forceit && curwin->w_p_wfb) { + int fnum = qf_ptr->qf_fnum; + + if (!forceit && curwin->w_p_wfb && curbuf->b_fnum != fnum) { if (qi->qfl_type == QFLT_LOCATION) { // Location lists cannot split or reassign their window // so 'winfixbuf' windows must fail - semsg("%s", e_winfixbuf_cannot_go_to_buffer); - return QF_ABORT; + emsg(_(e_winfixbuf_cannot_go_to_buffer)); + return FAIL; } - if (!win_valid(prevwin)) { - // Split the window, which will be 'nowinfixbuf', and set curwin to that - exarg_T new_eap = { - .cmdidx = CMD_split, - .cmd = "split", - .arg = "", - }; - ex_splitview(&new_eap); + if (win_valid(prevwin) && !prevwin->w_p_wfb + && !bt_quickfix(prevwin->w_buffer)) { + // 'winfixbuf' is set; attempt to change to a window without it + // that isn't a quickfix/location list window. + win_goto(prevwin); + } + if (curwin->w_p_wfb) { + // Split the window, which will be 'nowinfixbuf', and set curwin + // to that + if (win_split(0, 0) == OK) { + *opened_window = true; + } + if (curwin->w_p_wfb) { + // Autocommands set 'winfixbuf' or sent us to another window + // with it set. Give up, but don't return immediately, as + // they may have messed with the list. + emsg(_(e_winfixbuf_cannot_go_to_buffer)); + retval = FAIL; + } } } - retval = buflist_getfile(qf_ptr->qf_fnum, 1, - GETF_SETMARK | GETF_SWITCH, forceit); + if (retval == OK) { + retval = buflist_getfile(fnum, 1, + GETF_SETMARK | GETF_SWITCH, forceit); + } } // If a location list, check whether the associated window is still // present. diff --git a/src/nvim/window.c b/src/nvim/window.c index 9f84713ee7..521699f2f0 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -139,7 +139,7 @@ static void log_frame_layout(frame_T *frame) bool check_can_set_curbuf_disabled(void) { if (curwin->w_p_wfb) { - semsg("%s", e_winfixbuf_cannot_go_to_buffer); + emsg(_(e_winfixbuf_cannot_go_to_buffer)); return false; } @@ -155,7 +155,7 @@ bool check_can_set_curbuf_disabled(void) bool check_can_set_curbuf_forceit(int forceit) { if (!forceit && curwin->w_p_wfb) { - semsg("%s", e_winfixbuf_cannot_go_to_buffer); + emsg(_(e_winfixbuf_cannot_go_to_buffer)); return false; } diff --git a/test/old/testdir/test_winfixbuf.vim b/test/old/testdir/test_winfixbuf.vim index 5412fecd07..eb6d35f0af 100644 --- a/test/old/testdir/test_winfixbuf.vim +++ b/test/old/testdir/test_winfixbuf.vim @@ -1254,14 +1254,27 @@ func Test_lNext() call s:reset_all_buffers() let [l:first, l:middle, _] = s:make_simple_location_list() + call assert_equal(1, getloclist(0, #{idx: 0}).idx) + lnext! + call assert_equal(2, getloclist(0, #{idx: 0}).idx) + call assert_equal(l:middle, bufnr()) call assert_fails("lNext", "E1513:") + " Ensure the entry didn't change. + call assert_equal(2, getloclist(0, #{idx: 0}).idx) + call assert_equal(l:middle, bufnr()) + + lnext! + call assert_equal(3, getloclist(0, #{idx: 0}).idx) call assert_equal(l:middle, bufnr()) - lnext! " Reset for the next test + lNext! + call assert_equal(2, getloclist(0, #{idx: 0}).idx) + call assert_equal(l:middle, bufnr()) lNext! + call assert_equal(1, getloclist(0, #{idx: 0}).idx) call assert_equal(l:first, bufnr()) endfunc @@ -1271,14 +1284,23 @@ func Test_lNfile() call s:reset_all_buffers() let [l:first, l:current, _] = s:make_simple_location_list() + call assert_equal(1, getloclist(0, #{idx: 0}).idx) + lnext! + call assert_equal(2, getloclist(0, #{idx: 0}).idx) + call assert_equal(l:current, bufnr()) call assert_fails("lNfile", "E1513:") + " Ensure the entry didn't change. + call assert_equal(2, getloclist(0, #{idx: 0}).idx) call assert_equal(l:current, bufnr()) - lnext! " Reset for the next test + lnext! + call assert_equal(3, getloclist(0, #{idx: 0}).idx) + call assert_equal(l:current, bufnr()) lNfile! + call assert_equal(1, getloclist(0, #{idx: 0}).idx) call assert_equal(l:first, bufnr()) endfunc @@ -1485,14 +1507,18 @@ func Test_lnfile() call s:reset_all_buffers() let [_, l:current, l:last] = s:make_simple_location_list() + call assert_equal(1, getloclist(0, #{idx: 0}).idx) + lnext! + call assert_equal(2, getloclist(0, #{idx: 0}).idx) + call assert_equal(l:current, bufnr()) call assert_fails("lnfile", "E1513:") + call assert_equal(2, getloclist(0, #{idx: 0}).idx) call assert_equal(l:current, bufnr()) - lprevious! " Reset for the next test call - lnfile! + call assert_equal(4, getloclist(0, #{idx: 0}).idx) call assert_equal(l:last, bufnr()) endfunc @@ -1519,14 +1545,19 @@ func Test_lprevious() call s:reset_all_buffers() let [l:first, l:middle, _] = s:make_simple_location_list() + call assert_equal(1, getloclist(0, #{idx: 0}).idx) + lnext! + call assert_equal(2, getloclist(0, #{idx: 0}).idx) + call assert_equal(l:middle, bufnr()) call assert_fails("lprevious", "E1513:") + " Ensure the entry didn't change. + call assert_equal(2, getloclist(0, #{idx: 0}).idx) call assert_equal(l:middle, bufnr()) - lnext! " Reset for the next test call - lprevious! + call assert_equal(1, getloclist(0, #{idx: 0}).idx) call assert_equal(l:first, bufnr()) endfunc @@ -3134,17 +3165,87 @@ endfunc func Test_quickfix_switchbuf_invalid_prevwin() call s:reset_all_buffers() - let [l:first, _] = s:make_simple_quickfix() - call assert_notequal(l:first, bufnr()) - call assert_equal(1, winnr('$')) + call s:make_simple_quickfix() + call assert_equal(1, getqflist(#{idx: 0}).idx) set switchbuf=uselast split copen execute winnr('#') 'quit' + call assert_equal(2, winnr('$')) + + cnext " Would've triggered a null pointer member access + call assert_equal(2, getqflist(#{idx: 0}).idx) - call assert_fails('cfirst', 'E1513:') set switchbuf& endfunc +func Test_listdo_goto_prevwin() + call s:reset_all_buffers() + call s:make_buffers_list() + + new + call assert_equal(0, &winfixbuf) + wincmd p + call assert_equal(1, &winfixbuf) + call assert_notequal(bufnr(), bufnr('#')) + + augroup ListDoGotoPrevwin + au! + au BufLeave * let s:triggered = 1 + \| call assert_equal(bufnr(), winbufnr(winnr())) + augroup END + " Should correctly switch to the window without 'winfixbuf', and curbuf should + " be consistent with curwin->w_buffer for autocommands. + bufdo " + call assert_equal(0, &winfixbuf) + call assert_equal(1, s:triggered) + unlet! s:triggered + au! ListDoGotoPrevwin + + set winfixbuf + wincmd p + call assert_equal(2, winnr('$')) + " Both curwin and prevwin have 'winfixbuf' set, so should split a new window + " without it set. + bufdo " + call assert_equal(0, &winfixbuf) + call assert_equal(3, winnr('$')) + + quit + call assert_equal(2, winnr('$')) + call assert_equal(1, &winfixbuf) + augroup ListDoGotoPrevwin + au! + au WinEnter * ++once set winfixbuf + augroup END + " Same as before, but naughty autocommands set 'winfixbuf' for the new window. + " :bufdo should give up in this case. + call assert_fails('bufdo "', 'E1513:') + + au! ListDoGotoPrevwin + augroup! ListDoGotoPrevwin +endfunc + +func Test_quickfix_changed_split_failed() + call s:reset_all_buffers() + + call s:make_simple_quickfix() + call assert_equal(1, winnr('$')) + + " Quickfix code will open a split in an attempt to get a 'nowinfixbuf' window + " to switch buffers in. Interfere with things by setting 'winfixbuf' in it. + augroup QfChanged + au! + au WinEnter * ++once call assert_equal(2, winnr('$')) + \| set winfixbuf | call setqflist([], 'f') + augroup END + call assert_fails('cnext', ['E1513:', 'E925:']) + " Check that the split was automatically closed. + call assert_equal(1, winnr('$')) + + au! QfChanged + augroup! QfChanged +endfunc + " vim: shiftwidth=2 sts=2 expandtab -- cgit From d71791a11a260ca81067d63d69b5970078fffb6d Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 11 Mar 2024 11:25:00 +0800 Subject: vim-patch:9.1.0152: Coverity complains about ignoring return value Problem: Coverity complains about ignoring return value of win_split() (after v9.1.150) Solution: Check if win_split() failed, add winfixbuf.res to Makefile https://github.com/vim/vim/commit/af7ae8160041e2d17c56945381e9370e7178e596 Co-authored-by: Christian Brabandt --- src/nvim/ex_cmds2.c | 5 +++-- test/old/testdir/test_winfixbuf.vim | 11 +++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index dacdb27b08..732631b678 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -457,8 +457,9 @@ void ex_listdo(exarg_T *eap) } if (curwin->w_p_wfb) { // Split the window, which will be 'nowinfixbuf', and set curwin to that - win_split(0, 0); - + if (win_split(0, 0) == FAIL) { + return; // error message already given + } if (curwin->w_p_wfb) { // Autocommands set 'winfixbuf' or sent us to another window // with it set. Give up. diff --git a/test/old/testdir/test_winfixbuf.vim b/test/old/testdir/test_winfixbuf.vim index eb6d35f0af..3eb221023c 100644 --- a/test/old/testdir/test_winfixbuf.vim +++ b/test/old/testdir/test_winfixbuf.vim @@ -3248,4 +3248,15 @@ func Test_quickfix_changed_split_failed() augroup! QfChanged endfunc +func Test_bufdo_splitwin_fails() + call s:reset_all_buffers() + let other = s:make_buffer_pairs() + " Make sure there is not enough room to + " split the winfixedbuf window + let &winheight=&lines + let &winminheight=&lines-2 + call assert_fails(':bufdo echo 1', 'E36:') + set winminheight&vim winheight&vim +endfunc + " vim: shiftwidth=2 sts=2 expandtab -- cgit From 3b3511c4d9f1855d4240da0d844ce7875176c607 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 11 Mar 2024 11:26:02 +0800 Subject: vim-patch:9.1.0156: Make 'wfb' failing to split still report E1513 Problem: may not be clear why failing to split causes an ":Xdo" command to abort if 'wfb' is set. Solution: do not return immediately if win_split fails, so E1513 is still given. Expect both errors in the test. Also fix tests to pass CI. (Sean Dewar) closes: vim/vim#14152 https://github.com/vim/vim/commit/769eb2d0c3614f9ea6fffa82329558f1a4af384f Co-authored-by: Sean Dewar <6256228+seandewar@users.noreply.github.com> --- src/nvim/ex_cmds2.c | 7 ++--- src/nvim/quickfix.c | 5 ++-- test/old/testdir/test_winfixbuf.vim | 56 ++++++++++++++++++++++++++----------- 3 files changed, 46 insertions(+), 22 deletions(-) diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 732631b678..12687d0ea8 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -457,12 +457,11 @@ void ex_listdo(exarg_T *eap) } if (curwin->w_p_wfb) { // Split the window, which will be 'nowinfixbuf', and set curwin to that - if (win_split(0, 0) == FAIL) { - return; // error message already given - } + (void)win_split(0, 0); + if (curwin->w_p_wfb) { // Autocommands set 'winfixbuf' or sent us to another window - // with it set. Give up. + // with it set, or we failed to split the window. Give up. emsg(_(e_winfixbuf_cannot_go_to_buffer)); return; } diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 28691914bb..0a4427f3c1 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -2828,8 +2828,9 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, int } if (curwin->w_p_wfb) { // Autocommands set 'winfixbuf' or sent us to another window - // with it set. Give up, but don't return immediately, as - // they may have messed with the list. + // with it set, or we failed to split the window. Give up, + // but don't return immediately, as they may have messed + // with the list. emsg(_(e_winfixbuf_cannot_go_to_buffer)); retval = FAIL; } diff --git a/test/old/testdir/test_winfixbuf.vim b/test/old/testdir/test_winfixbuf.vim index 3eb221023c..112eccf3ef 100644 --- a/test/old/testdir/test_winfixbuf.vim +++ b/test/old/testdir/test_winfixbuf.vim @@ -479,6 +479,9 @@ endfunc " Fail :browse edit but :browse edit! is allowed func Test_browse_edit_fail() + " A GUI dialog may stall the test. + CheckNotGui + call s:reset_all_buffers() let l:other = s:make_buffer_pairs() @@ -487,18 +490,31 @@ func Test_browse_edit_fail() call assert_fails("browse edit other", "E1513:") call assert_equal(l:current, bufnr()) - browse edit! other - call assert_equal(l:other, bufnr()) + try + browse edit! other + call assert_equal(l:other, bufnr()) + catch /E338:/ + " Ignore E338, which occurs if console Vim is built with +browse. + " Console Vim without +browse will treat this as a regular :edit. + endtry endfunc " Allow :browse w because it doesn't change the buffer in the current file func Test_browse_edit_pass() + " A GUI dialog may stall the test. + CheckNotGui + call s:reset_all_buffers() let l:other = s:make_buffer_pairs() let l:current = bufnr() - browse write other + try + browse write other + catch /E338:/ + " Ignore E338, which occurs if console Vim is built with +browse. + " Console Vim without +browse will treat this as a regular :write. + endtry call delete("other") endfunc @@ -1145,6 +1161,7 @@ func Test_find() let l:current = bufnr() let l:file = tempname() call writefile([], l:file) + let l:file = fnamemodify(l:file, ':p') " In case it's Windows 8.3-style. let l:directory = fnamemodify(l:file, ":p:h") let l:name = fnamemodify(l:file, ":p:t") @@ -1514,6 +1531,7 @@ func Test_lnfile() call assert_equal(l:current, bufnr()) call assert_fails("lnfile", "E1513:") + " Ensure the entry didn't change. call assert_equal(2, getloclist(0, #{idx: 0}).idx) call assert_equal(l:current, bufnr()) @@ -2490,8 +2508,8 @@ func Test_previous() call assert_equal(l:first, bufnr()) endfunc -" Fail pydo if it changes a window with 'winfixbuf' is set -func Test_python_pydo() +" Fail pyxdo if it changes a window with 'winfixbuf' is set +func Test_pythonx_pyxdo() CheckFeature pythonx call s:reset_all_buffers() @@ -2504,16 +2522,16 @@ func Test_python_pydo() set winfixbuf - python << EOF + pythonx << EOF import vim -def test_winfixbuf_Test_python_pydo_set_buffer(): +def test_winfixbuf_Test_pythonx_pyxdo_set_buffer(): buffer = vim.vars['_previous_buffer'] vim.current.buffer = vim.buffers[buffer] EOF try - pydo test_winfixbuf_Test_python_pydo_set_buffer() + pyxdo test_winfixbuf_Test_pythonx_pyxdo_set_buffer() catch /pynvim\.api\.common\.NvimError: E1513: Cannot edit buffer\. 'winfixbuf' is enabled/ let l:caught = 1 endtry @@ -2523,8 +2541,8 @@ EOF unlet g:_previous_buffer endfunc -" Fail pyfile if it changes a window with 'winfixbuf' is set -func Test_python_pyfile() +" Fail pyxfile if it changes a window with 'winfixbuf' is set +func Test_pythonx_pyxfile() CheckFeature pythonx call s:reset_all_buffers() @@ -2544,7 +2562,7 @@ func Test_python_pyfile() \ "file.py") try - pyfile file.py + pyxfile file.py catch /pynvim\.api\.common\.NvimError: E1513: Cannot edit buffer\. 'winfixbuf' is enabled/ let l:caught = 1 endtry @@ -2556,7 +2574,7 @@ func Test_python_pyfile() endfunc " Fail vim.current.buffer if 'winfixbuf' is set -func Test_python_vim_current_buffer() +func Test_pythonx_vim_current_buffer() CheckFeature pythonx call s:reset_all_buffers() @@ -2572,7 +2590,7 @@ func Test_python_vim_current_buffer() set winfixbuf try - python << EOF + pythonx << EOF import vim buffer = vim.vars["_previous_buffer"] @@ -3248,14 +3266,20 @@ func Test_quickfix_changed_split_failed() augroup! QfChanged endfunc -func Test_bufdo_splitwin_fails() +func Test_bufdo_cnext_splitwin_fails() call s:reset_all_buffers() - let other = s:make_buffer_pairs() + call s:make_simple_quickfix() + call assert_equal(1, getqflist(#{idx: 0}).idx) " Make sure there is not enough room to " split the winfixedbuf window let &winheight=&lines let &winminheight=&lines-2 - call assert_fails(':bufdo echo 1', 'E36:') + " Still want E1513, or it may not be clear why a split was attempted and why + " it failing caused the commands to abort. + call assert_fails(':bufdo echo 1', ['E36:', 'E1513:']) + call assert_fails(':cnext', ['E36:', 'E1513:']) + " Ensure the entry didn't change. + call assert_equal(1, getqflist(#{idx: 0}).idx) set winminheight&vim winheight&vim endfunc -- cgit From b72931e7040794f8c6adf6c0a446758f14107dda Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Mon, 11 Mar 2024 07:13:48 +0100 Subject: feat(ui): allow non-zero 'cmdheight' with ext_messages Problem: Arbitrary restriction on 'cmdheight' with ext_messages. The 'cmdheight'-area may be desirable for the replacing cmdline. Solution: Allow non-zero 'cmdheight' with ext_messages. --- src/nvim/option.c | 3 --- test/functional/ui/messages_spec.lua | 10 ++++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/nvim/option.c b/src/nvim/option.c index fcc5b5eb06..4f1ec59e77 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -2024,9 +2024,6 @@ static const char *did_set_cmdheight(optset_T *args) { OptInt old_value = args->os_oldval.number; - if (ui_has(kUIMessages)) { - p_ch = 0; - } if (p_ch > Rows - min_rows() + 1) { p_ch = Rows - min_rows() + 1; } diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index 31b1464589..c18a07fef0 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -844,7 +844,7 @@ describe('ui/ext_messages', function() } end) - it('implies ext_cmdline and ignores cmdheight', function() + it("implies ext_cmdline but allows changing 'cmdheight'", function() eq(0, eval('&cmdheight')) feed(':set cmdheight=1') screen:expect { @@ -864,15 +864,17 @@ describe('ui/ext_messages', function() feed('') screen:expect([[ ^ | - {1:~ }|*4 + {1:~ }|*3 + | ]]) - eq(0, eval('&cmdheight')) + eq(1, eval('&cmdheight')) feed(':set cmdheight=0') screen:expect { grid = [[ ^ | - {1:~ }|*4 + {1:~ }|*3 + | ]], cmdline = { { -- cgit From f879a6545574c6d2eef7b4df2f5c5d96f5e34589 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Mon, 11 Mar 2024 08:20:49 +0800 Subject: build(deps): bump luajit to HEAD - d06beb048 --- cmake.deps/deps.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake.deps/deps.txt b/cmake.deps/deps.txt index de212460c7..870569b58c 100644 --- a/cmake.deps/deps.txt +++ b/cmake.deps/deps.txt @@ -4,8 +4,8 @@ LIBUV_SHA256 8c253adb0f800926a6cbd1c6576abae0bc8eb86a4f891049b72f9e5b7dc58f33 MSGPACK_URL https://github.com/msgpack/msgpack-c/archive/c-6.0.0.tar.gz MSGPACK_SHA256 af6f3cf25edb220aa2140b09bb5bdd73ddf00938194bd94ebe5c92090cccb466 -LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/0d313b243194a0b8d2399d8b549ca5a0ff234db5.tar.gz -LUAJIT_SHA256 53731880dbc4adbbf82ba69a85b5dbe15266032b8b94a077c0835bc10ec75f12 +LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/d06beb0480c5d1eb53b3343e78063950275aa281.tar.gz +LUAJIT_SHA256 6abd146a1dfa240a965748f63221633446affa2a715e3eb03879136e3efb95f4 LUA_URL https://www.lua.org/ftp/lua-5.1.5.tar.gz LUA_SHA256 2640fc56a795f29d28ef15e13c34a47e223960b0240e8cb0a82d9b0738695333 -- cgit From cdbc3e3f3e95a1df9de180ee4ac52d460e5c6905 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Mon, 11 Mar 2024 09:47:26 +0000 Subject: fix(editorconfig): syntax error regression --- runtime/lua/editorconfig.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/runtime/lua/editorconfig.lua b/runtime/lua/editorconfig.lua index 6c5c820b0c..c93c928339 100644 --- a/runtime/lua/editorconfig.lua +++ b/runtime/lua/editorconfig.lua @@ -273,6 +273,9 @@ end local M = {} +-- Exposed for use in syntax/editorconfig.vim` +M.properties = properties + --- @private --- Configure the given buffer with options from an `.editorconfig` file --- @param bufnr integer Buffer number to configure @@ -303,7 +306,7 @@ function M.config(bufnr) local applied = {} --- @type table for opt, val in pairs(opts) do if val ~= 'unset' then - local func = properties[opt] + local func = M.properties[opt] if func then --- @type boolean, string? local ok, err = pcall(func, bufnr, val, opts) -- cgit From 0f20b7d803779950492c2838e2b042a38f4ee22f Mon Sep 17 00:00:00 2001 From: Tomas Slusny Date: Mon, 11 Mar 2024 02:02:52 +0100 Subject: docs: adjust fswatch overflow message to mention docs with info - Add :h fswatch-limitations that notifies user about default inotify limitations on linux and how to adjust them - Check for Event queue overflow message from fswatch and refer user to new documentation Signed-off-by: Tomas Slusny --- runtime/doc/lua.txt | 10 ++++++++++ runtime/lua/vim/_watch.lua | 3 +++ 2 files changed, 13 insertions(+) diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index e1e3f88a1d..135a1b42de 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -530,6 +530,16 @@ Example: File-change detection *watch-file* vim.api.nvim_command( "command! -nargs=1 Watch call luaeval('watch_file(_A)', expand(''))") < + *fswatch-limitations* +When on Linux and using fswatch, you may need to increase the maximum number +of `inotify` watches and queued events as the default limit can be too low. To +increase the limit, run: >sh + sysctl fs.inotify.max_user_watches=100000 + sysctl fs.inotify.max_queued_events=100000 +< +This will increase the limit to 100000 watches and queued events. These lines +can be added to `/etc/sysctl.conf` to make the changes persistent. + Example: TCP echo-server *tcp-server* 1. Save this code to a file. 2. Execute it with ":luafile %". diff --git a/runtime/lua/vim/_watch.lua b/runtime/lua/vim/_watch.lua index cf2689861a..542e770246 100644 --- a/runtime/lua/vim/_watch.lua +++ b/runtime/lua/vim/_watch.lua @@ -289,6 +289,9 @@ function M.fswatch(path, opts, callback) end if data and #vim.trim(data) > 0 then + if vim.fn.has('linux') == 1 and vim.startswith(data, 'Event queue overflow') then + data = 'inotify(7) limit reached, see :h fswatch-limitations for more info.' + end vim.schedule(function() vim.notify('fswatch: ' .. data, vim.log.levels.ERROR) end) -- cgit From 1da0f3494eb042c84ae5f00654878f7f8cedf3b7 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 11 Mar 2024 22:23:14 +0800 Subject: test: correct order of arguments to eq() (#27816) --- test/functional/api/vim_spec.lua | 4 ++-- test/functional/autocmd/textyankpost_spec.lua | 6 +++--- test/functional/ex_cmds/cmd_map_spec.lua | 4 ++-- test/functional/legacy/memory_usage_spec.lua | 2 +- test/functional/lua/buffer_updates_spec.lua | 6 +++--- test/functional/lua/diagnostic_spec.lua | 2 +- test/functional/lua/thread_spec.lua | 4 ++-- test/functional/lua/vim_spec.lua | 30 +++++++++++++------------- test/functional/options/defaults_spec.lua | 6 +++--- test/functional/options/num_options_spec.lua | 2 +- test/functional/plugin/lsp/diagnostic_spec.lua | 10 ++++----- test/functional/plugin/lsp/inlay_hint_spec.lua | 14 ++++++------ test/functional/plugin/lsp_spec.lua | 14 ++++++------ test/functional/shada/merging_spec.lua | 10 ++++----- test/functional/treesitter/parser_spec.lua | 8 +++---- test/functional/ui/float_spec.lua | 2 +- test/functional/ui/inccommand_spec.lua | 4 ++-- test/unit/msgpack_spec.lua | 6 +++--- test/unit/os/env_spec.lua | 4 ++-- test/unit/os/shell_spec.lua | 24 ++++++++++----------- test/unit/profile_spec.lua | 4 ++-- test/unit/tempfile_spec.lua | 4 ++-- 22 files changed, 85 insertions(+), 85 deletions(-) diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 9a4a457637..09a45242ec 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -707,13 +707,13 @@ describe('API', function() it('works', function() api.nvim_set_current_dir('Xtestdir') - eq(fn.getcwd(), start_dir .. helpers.get_pathsep() .. 'Xtestdir') + eq(start_dir .. helpers.get_pathsep() .. 'Xtestdir', fn.getcwd()) end) it('sets previous directory', function() api.nvim_set_current_dir('Xtestdir') command('cd -') - eq(fn.getcwd(), start_dir) + eq(start_dir, fn.getcwd()) end) end) diff --git a/test/functional/autocmd/textyankpost_spec.lua b/test/functional/autocmd/textyankpost_spec.lua index 29cd62f586..17eb7695f2 100644 --- a/test/functional/autocmd/textyankpost_spec.lua +++ b/test/functional/autocmd/textyankpost_spec.lua @@ -72,19 +72,19 @@ describe('TextYankPost', function() command('set debug=msg') -- the regcontents should not be changed without copy. local status, err = pcall(command, 'call extend(g:event.regcontents, ["more text"])') - eq(status, false) + eq(false, status) neq(nil, string.find(err, ':E742:')) -- can't mutate keys inside the autocommand command('autocmd! TextYankPost * let v:event.regcontents = 0') status, err = pcall(command, 'normal yy') - eq(status, false) + eq(false, status) neq(nil, string.find(err, ':E46:')) -- can't add keys inside the autocommand command('autocmd! TextYankPost * let v:event.mykey = 0') status, err = pcall(command, 'normal yy') - eq(status, false) + eq(false, status) neq(nil, string.find(err, ':E742:')) end) diff --git a/test/functional/ex_cmds/cmd_map_spec.lua b/test/functional/ex_cmds/cmd_map_spec.lua index cb7d7340e2..4499b2172d 100644 --- a/test/functional/ex_cmds/cmd_map_spec.lua +++ b/test/functional/ex_cmds/cmd_map_spec.lua @@ -505,7 +505,7 @@ describe('mappings with ', function() feed('"bd') expect([[ soest text]]) - eq(fn.getreg('b', 1, 1), { 'me short lines', 'of t' }) + eq({ 'me short lines', 'of t' }, fn.getreg('b', 1, 1)) -- startinsert aborts operator feed('d') @@ -561,7 +561,7 @@ describe('mappings with ', function() of stuff test text]]) feed('') - eq(fn.getreg('a', 1, 1), { 'deed some short little lines', 'of stuff t' }) + eq({ 'deed some short little lines', 'of stuff t' }, fn.getreg('a', 1, 1)) -- still in insert screen:expect([[ diff --git a/test/functional/legacy/memory_usage_spec.lua b/test/functional/legacy/memory_usage_spec.lua index a05e9fdf57..3a6eb2c31c 100644 --- a/test/functional/legacy/memory_usage_spec.lua +++ b/test/functional/legacy/memory_usage_spec.lua @@ -59,7 +59,7 @@ local monitor_memory_usage = { end table.remove(self.hist, 1) self.last = self.hist[#self.hist] - eq(#result, 1) + eq(1, #result) end) end, dump = function(self) diff --git a/test/functional/lua/buffer_updates_spec.lua b/test/functional/lua/buffer_updates_spec.lua index 714e1b951f..1f9f61ebf7 100644 --- a/test/functional/lua/buffer_updates_spec.lua +++ b/test/functional/lua/buffer_updates_spec.lua @@ -312,15 +312,15 @@ describe('lua buffer event callbacks: on_lines', function() feed('G0') feed('p') -- Is the last arg old_byte_size correct? Doesn't matter for this PR - eq(api.nvim_get_var('linesev'), { 'lines', 1, 4, 2, 3, 5, 4 }) + eq({ 'lines', 1, 4, 2, 3, 5, 4 }, api.nvim_get_var('linesev')) feed('2G0') feed('p') - eq(api.nvim_get_var('linesev'), { 'lines', 1, 5, 1, 4, 4, 8 }) + eq({ 'lines', 1, 5, 1, 4, 4, 8 }, api.nvim_get_var('linesev')) feed('1G0') feed('P') - eq(api.nvim_get_var('linesev'), { 'lines', 1, 6, 0, 3, 3, 9 }) + eq({ 'lines', 1, 6, 0, 3, 3, 9 }, api.nvim_get_var('linesev')) end) it( diff --git a/test/functional/lua/diagnostic_spec.lua b/test/functional/lua/diagnostic_spec.lua index 5802925339..716c1e0f30 100644 --- a/test/functional/lua/diagnostic_spec.lua +++ b/test/functional/lua/diagnostic_spec.lua @@ -205,7 +205,7 @@ describe('vim.diagnostic', function() diag[1].col = 10000 return vim.diagnostic.get()[1].col == 10000 ]] - eq(result, false) + eq(false, result) end) it('resolves buffer number 0 to the current buffer', function() diff --git a/test/functional/lua/thread_spec.lua b/test/functional/lua/thread_spec.lua index c1981e19d4..9bbee73e27 100644 --- a/test/functional/lua/thread_spec.lua +++ b/test/functional/lua/thread_spec.lua @@ -166,7 +166,7 @@ describe('thread', function() ]] local msg = next_msg() - eq(msg[1], 'notification') + eq('notification', msg[1]) assert(tonumber(msg[2]) >= 72961) end) @@ -327,7 +327,7 @@ describe('threadpool', function() ]] local msg = next_msg() - eq(msg[1], 'notification') + eq('notification', msg[1]) assert(tonumber(msg[2]) >= 72961) end) diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index add3df6d8a..7ab009659b 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -2016,7 +2016,7 @@ describe('lua stdlib', function() vim.opt.scrolloff = 10 return vim.o.scrolloff ]] - eq(scrolloff, 10) + eq(10, scrolloff) end) pending('should handle STUPID window things', function() @@ -2037,7 +2037,7 @@ describe('lua stdlib', function() vim.opt.wildignore = { 'hello', 'world' } return vim.o.wildignore ]] - eq(wildignore, 'hello,world') + eq('hello,world', wildignore) end) it('should allow setting tables with shortnames', function() @@ -2045,7 +2045,7 @@ describe('lua stdlib', function() vim.opt.wig = { 'hello', 'world' } return vim.o.wildignore ]] - eq(wildignore, 'hello,world') + eq('hello,world', wildignore) end) it('should error when you attempt to set string values to numeric options', function() @@ -2451,13 +2451,13 @@ describe('lua stdlib', function() vim.opt.wildignore = 'foo' return vim.o.wildignore ]] - eq(wildignore, 'foo') + eq('foo', wildignore) wildignore = exec_lua [[ vim.opt.wildignore = vim.opt.wildignore + { 'bar', 'baz' } return vim.o.wildignore ]] - eq(wildignore, 'foo,bar,baz') + eq('foo,bar,baz', wildignore) end) it('should handle adding duplicates', function() @@ -2465,19 +2465,19 @@ describe('lua stdlib', function() vim.opt.wildignore = 'foo' return vim.o.wildignore ]] - eq(wildignore, 'foo') + eq('foo', wildignore) wildignore = exec_lua [[ vim.opt.wildignore = vim.opt.wildignore + { 'bar', 'baz' } return vim.o.wildignore ]] - eq(wildignore, 'foo,bar,baz') + eq('foo,bar,baz', wildignore) wildignore = exec_lua [[ vim.opt.wildignore = vim.opt.wildignore + { 'bar', 'baz' } return vim.o.wildignore ]] - eq(wildignore, 'foo,bar,baz') + eq('foo,bar,baz', wildignore) end) it('should allow adding multiple times', function() @@ -2486,7 +2486,7 @@ describe('lua stdlib', function() vim.opt.wildignore = vim.opt.wildignore + 'bar' + 'baz' return vim.o.wildignore ]] - eq(wildignore, 'foo,bar,baz') + eq('foo,bar,baz', wildignore) end) it('should remove values when you use minus', function() @@ -2494,19 +2494,19 @@ describe('lua stdlib', function() vim.opt.wildignore = 'foo' return vim.o.wildignore ]] - eq(wildignore, 'foo') + eq('foo', wildignore) wildignore = exec_lua [[ vim.opt.wildignore = vim.opt.wildignore + { 'bar', 'baz' } return vim.o.wildignore ]] - eq(wildignore, 'foo,bar,baz') + eq('foo,bar,baz', wildignore) wildignore = exec_lua [[ vim.opt.wildignore = vim.opt.wildignore - 'bar' return vim.o.wildignore ]] - eq(wildignore, 'foo,baz') + eq('foo,baz', wildignore) end) it('should prepend values when using ^', function() @@ -2521,7 +2521,7 @@ describe('lua stdlib', function() vim.opt.wildignore = vim.opt.wildignore ^ 'super_first' return vim.o.wildignore ]] - eq(wildignore, 'super_first,first,foo') + eq('super_first,first,foo', wildignore) end) it('should not remove duplicates from wildmode: #14708', function() @@ -2530,7 +2530,7 @@ describe('lua stdlib', function() return vim.o.wildmode ]] - eq(wildmode, 'full,list,full') + eq('full,list,full', wildmode) end) describe('option types', function() @@ -2738,7 +2738,7 @@ describe('lua stdlib', function() return vim.go.whichwrap ]] - eq(ww, 'b,s') + eq('b,s', ww) eq( 'b,s,<,>,[,]', exec_lua [[ diff --git a/test/functional/options/defaults_spec.lua b/test/functional/options/defaults_spec.lua index d27fa375ee..a7cdc5f9fa 100644 --- a/test/functional/options/defaults_spec.lua +++ b/test/functional/options/defaults_spec.lua @@ -1243,11 +1243,11 @@ describe('autocommands', function() command('au BufEnter * let g:n = g:n + 1') command('terminal') - eq(eval('get(g:, "n", 0)'), 1) + eq(1, eval('get(g:, "n", 0)')) helpers.retry(nil, 1000, function() - neq(api.nvim_get_option_value('buftype', { buf = 0 }), 'terminal') - eq(eval('get(g:, "n", 0)'), 2) + neq('terminal', api.nvim_get_option_value('buftype', { buf = 0 })) + eq(2, eval('get(g:, "n", 0)')) end) end) end) diff --git a/test/functional/options/num_options_spec.lua b/test/functional/options/num_options_spec.lua index 0614bcf814..3b411b109c 100644 --- a/test/functional/options/num_options_spec.lua +++ b/test/functional/options/num_options_spec.lua @@ -12,7 +12,7 @@ local function should_fail(opt, value, errmsg) eq(errmsg, eval('v:errmsg'):match('E%d*')) feed_command('let v:errmsg = ""') local status, err = pcall(api.nvim_set_option_value, opt, value, {}) - eq(status, false) + eq(false, status) eq(errmsg, err:match('E%d*')) eq('', eval('v:errmsg')) end diff --git a/test/functional/plugin/lsp/diagnostic_spec.lua b/test/functional/plugin/lsp/diagnostic_spec.lua index 705c182df7..72531db021 100644 --- a/test/functional/plugin/lsp/diagnostic_spec.lua +++ b/test/functional/plugin/lsp/diagnostic_spec.lua @@ -257,7 +257,7 @@ describe('vim.lsp.diagnostic', function() }, {client_id=client_id}) return vim.fn.bufnr(vim.uri_to_fname("file:///fake/uri2")) ]] - eq(bufnr, -1) + eq(-1, bufnr) -- Create buffer on diagnostics bufnr = exec_lua [[ @@ -269,8 +269,8 @@ describe('vim.lsp.diagnostic', function() }, {client_id=client_id}) return vim.fn.bufnr(vim.uri_to_fname("file:///fake/uri2")) ]] - neq(bufnr, -1) - eq(exec_lua([[return #vim.diagnostic.get(...)]], bufnr), 1) + neq(-1, bufnr) + eq(1, exec_lua([[return #vim.diagnostic.get(...)]], bufnr)) -- Clear diagnostics after buffer was created bufnr = exec_lua [[ @@ -280,8 +280,8 @@ describe('vim.lsp.diagnostic', function() }, {client_id=client_id}) return vim.fn.bufnr(vim.uri_to_fname("file:///fake/uri2")) ]] - neq(bufnr, -1) - eq(exec_lua([[return #vim.diagnostic.get(...)]], bufnr), 0) + neq(-1, bufnr) + eq(0, exec_lua([[return #vim.diagnostic.get(...)]], bufnr)) end) end) diff --git a/test/functional/plugin/lsp/inlay_hint_spec.lua b/test/functional/plugin/lsp/inlay_hint_spec.lua index 192797b312..864394d7e0 100644 --- a/test/functional/plugin/lsp/inlay_hint_spec.lua +++ b/test/functional/plugin/lsp/inlay_hint_spec.lua @@ -169,12 +169,12 @@ describe('vim.lsp.inlay_hint', function() --- @type vim.lsp.inlay_hint.get.ret local res = exec_lua([[return vim.lsp.inlay_hint.get()]]) - eq(res, { + eq({ { bufnr = 1, client_id = 1, inlay_hint = expected[1] }, { bufnr = 1, client_id = 1, inlay_hint = expected[2] }, { bufnr = 1, client_id = 1, inlay_hint = expected[3] }, { bufnr = 1, client_id = 2, inlay_hint = expected2 }, - }) + }, res) --- @type vim.lsp.inlay_hint.get.ret res = exec_lua([[return vim.lsp.inlay_hint.get({ @@ -183,9 +183,9 @@ describe('vim.lsp.inlay_hint', function() ["end"] = { line = 2, character = 10 }, }, })]]) - eq(res, { + eq({ { bufnr = 1, client_id = 2, inlay_hint = expected2 }, - }) + }, res) --- @type vim.lsp.inlay_hint.get.ret res = exec_lua([[return vim.lsp.inlay_hint.get({ @@ -195,16 +195,16 @@ describe('vim.lsp.inlay_hint', function() ["end"] = { line = 5, character = 17 }, }, })]]) - eq(res, { + eq({ { bufnr = 1, client_id = 1, inlay_hint = expected[2] }, { bufnr = 1, client_id = 1, inlay_hint = expected[3] }, - }) + }, res) --- @type vim.lsp.inlay_hint.get.ret res = exec_lua([[return vim.lsp.inlay_hint.get({ bufnr = vim.api.nvim_get_current_buf() + 1, })]]) - eq(res, {}) + eq({}, res) end) end) end) diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 4826153edb..0cb2b88948 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -641,11 +641,11 @@ describe('LSP', function() vim.lsp.stop_client(client_id) return server.messages ]]) - eq(#messages, 4) - eq(messages[1].method, 'initialize') - eq(messages[2].method, 'initialized') - eq(messages[3].method, 'shutdown') - eq(messages[4].method, 'exit') + eq(4, #messages) + eq('initialize', messages[1].method) + eq('initialized', messages[2].method) + eq('shutdown', messages[3].method) + eq('exit', messages[4].method) end) it('BufWritePre sends willSave / willSaveWaitUntil, applies textEdits', function() @@ -4221,7 +4221,7 @@ describe('LSP', function() server:shutdown() return vim.json.decode(init) ]] - eq(result.method, 'initialize') + eq('initialize', result.method) end) it('can connect to lsp server via rpc.domain_socket_connect', function() local tmpfile @@ -4257,7 +4257,7 @@ describe('LSP', function() ]], tmpfile ) - eq(result.method, 'initialize') + eq('initialize', result.method) end) end) diff --git a/test/functional/shada/merging_spec.lua b/test/functional/shada/merging_spec.lua index 1b5c0eab5d..ff81ce4eb9 100644 --- a/test/functional/shada/merging_spec.lua +++ b/test/functional/shada/merging_spec.lua @@ -1003,7 +1003,7 @@ describe('ShaDa jumps support code', function() eq(jumps[found].line, v.value.l) end end - eq(found, #jumps) + eq(#jumps, found) end) it('merges JUMPLISTSIZE jumps when writing', function() @@ -1041,7 +1041,7 @@ describe('ShaDa jumps support code', function() eq(jumps[found].line, v.value.l) end end - eq(found, 100) + eq(100, found) end) end) @@ -1132,7 +1132,7 @@ describe('ShaDa changes support code', function() eq(changes[found].line, v.value.l or 1) end end - eq(found, #changes) + eq(#changes, found) end) it('merges JUMPLISTSIZE changes when writing', function() @@ -1170,7 +1170,7 @@ describe('ShaDa changes support code', function() eq(changes[found].line, v.value.l) end end - eq(found, 100) + eq(100, found) end) it('merges JUMPLISTSIZE changes when writing, with new items between old', function() @@ -1213,6 +1213,6 @@ describe('ShaDa changes support code', function() eq(changes[found].line, v.value.l) end end - eq(found, 100) + eq(100, found) end) end) diff --git a/test/functional/treesitter/parser_spec.lua b/test/functional/treesitter/parser_spec.lua index 875b772fec..24f395c1ef 100644 --- a/test/functional/treesitter/parser_spec.lua +++ b/test/functional/treesitter/parser_spec.lua @@ -828,7 +828,7 @@ end]] return parser:included_regions() ]] - eq(range_tbl, { { { 0, 0, 0, 17, 1, 508 } } }) + eq({ { { 0, 0, 0, 17, 1, 508 } } }, range_tbl) end) it('allows to set complex ranges', function() @@ -1111,7 +1111,7 @@ int x = INT_MAX; return sub_tree == parser:children().c ]]) - eq(result, true) + eq(true, result) end) end) @@ -1135,7 +1135,7 @@ int x = INT_MAX; return result ]]) - eq(result, 'value') + eq('value', result) end) describe('when setting a key on a capture', function() @@ -1158,7 +1158,7 @@ int x = INT_MAX; end ]]) - eq(result, 'value') + eq('value', result) end) it('it should not overwrite the nested table', function() diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index 65a7d359af..361ea3e778 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -6257,7 +6257,7 @@ describe('float window', function() run(on_request, nil, on_setup) os.remove('Xtest_written') os.remove('Xtest_written2') - eq(exited, true) + eq(true, exited) end) it(':quit two floats in a row', function() diff --git a/test/functional/ui/inccommand_spec.lua b/test/functional/ui/inccommand_spec.lua index 29c8c43ca1..d143c594f5 100644 --- a/test/functional/ui/inccommand_spec.lua +++ b/test/functional/ui/inccommand_spec.lua @@ -204,8 +204,8 @@ describe(":substitute, 'inccommand' preserves", function() feed(':%s/as/glork/') poke_eventloop() feed('') - eq(api.nvim_get_option_value('undolevels', { scope = 'global' }), 139) - eq(api.nvim_get_option_value('undolevels', { buf = 0 }), 34) + eq(139, api.nvim_get_option_value('undolevels', { scope = 'global' })) + eq(34, api.nvim_get_option_value('undolevels', { buf = 0 })) end) end diff --git a/test/unit/msgpack_spec.lua b/test/unit/msgpack_spec.lua index bd663a3c75..f9fde00a85 100644 --- a/test/unit/msgpack_spec.lua +++ b/test/unit/msgpack_spec.lua @@ -51,11 +51,11 @@ describe('msgpack', function() unpacker_goto(unpacker, payload, payload:len() - 1) local finished = unpacker_advance(unpacker) - eq(finished, false) + eq(false, finished) unpacker[0].read_size = unpacker[0].read_size + 1 finished = unpacker_advance(unpacker) - eq(finished, true) + eq(true, finished) end ) @@ -73,7 +73,7 @@ describe('msgpack', function() '\x93\x02\xa6\x72\x65\x64\x72\x61\x77\x91\x92\xa9\x67\x72\x69\x64\x5f\x6c\x69\x6e\x65\x95\x02\x00\x00\x90\xc2' ) local finished = unpacker_advance(unpacker) - eq(finished, true) + eq(true, finished) end) end) end) diff --git a/test/unit/os/env_spec.lua b/test/unit/os/env_spec.lua index 2c638fcb37..310201b8c3 100644 --- a/test/unit/os/env_spec.lua +++ b/test/unit/os/env_spec.lua @@ -306,9 +306,9 @@ describe('env.c', function() -- expand_env_esc SHOULD NOT expand the variable if there is not enough space to -- contain the result for i = 0, 3 do - eq(output[i], input[i]) + eq(input[i], output[i]) end - eq(output[4], 0) + eq(0, output[4]) end) end) end) diff --git a/test/unit/os/shell_spec.lua b/test/unit/os/shell_spec.lua index ae162f2317..05f965585a 100644 --- a/test/unit/os/shell_spec.lua +++ b/test/unit/os/shell_spec.lua @@ -125,9 +125,9 @@ describe('shell functions', function() cimported.p_sxe = to_cstr('"&|<>()@^') local argv = ffi.cast('char**', cimported.shell_build_argv(to_cstr('echo &|<>()@^'), nil)) - eq(ffi.string(argv[0]), '/bin/sh') - eq(ffi.string(argv[1]), '-c') - eq(ffi.string(argv[2]), '(echo ^&^|^<^>^(^)^@^^)') + eq('/bin/sh', ffi.string(argv[0])) + eq('-c', ffi.string(argv[1])) + eq('(echo ^&^|^<^>^(^)^@^^)', ffi.string(argv[2])) eq(nil, argv[3]) end) @@ -136,9 +136,9 @@ describe('shell functions', function() cimported.p_sxe = to_cstr('"&|<>()@^') local argv = ffi.cast('char**', cimported.shell_build_argv(to_cstr('echo -n some text'), nil)) - eq(ffi.string(argv[0]), '/bin/sh') - eq(ffi.string(argv[1]), '-c') - eq(ffi.string(argv[2]), '"(echo -n some text)"') + eq('/bin/sh', ffi.string(argv[0])) + eq('-c', ffi.string(argv[1])) + eq('"(echo -n some text)"', ffi.string(argv[2])) eq(nil, argv[3]) end) @@ -147,17 +147,17 @@ describe('shell functions', function() cimported.p_sxe = to_cstr('') local argv = ffi.cast('char**', cimported.shell_build_argv(to_cstr('echo -n some text'), nil)) - eq(ffi.string(argv[0]), '/bin/sh') - eq(ffi.string(argv[1]), '-c') - eq(ffi.string(argv[2]), '"echo -n some text"') + eq('/bin/sh', ffi.string(argv[0])) + eq('-c', ffi.string(argv[1])) + eq('"echo -n some text"', ffi.string(argv[2])) eq(nil, argv[3]) end) itp('with empty shellxquote/shellxescape', function() local argv = ffi.cast('char**', cimported.shell_build_argv(to_cstr('echo -n some text'), nil)) - eq(ffi.string(argv[0]), '/bin/sh') - eq(ffi.string(argv[1]), '-c') - eq(ffi.string(argv[2]), 'echo -n some text') + eq('/bin/sh', ffi.string(argv[0])) + eq('-c', ffi.string(argv[1])) + eq('echo -n some text', ffi.string(argv[2])) eq(nil, argv[3]) end) end) diff --git a/test/unit/profile_spec.lua b/test/unit/profile_spec.lua index 011d3632d5..c7dc7db189 100644 --- a/test/unit/profile_spec.lua +++ b/test/unit/profile_spec.lua @@ -229,7 +229,7 @@ describe('profiling related functions', function() describe('profile_msg', function() itp('prints the zero time as 0.00000', function() local str = trim(profile_msg(profile_zero())) - eq(str, '0.000000') + eq('0.000000', str) end) itp('prints the time passed, in seconds.microsends', function() @@ -245,7 +245,7 @@ describe('profiling related functions', function() -- zero seconds have passed (if this is not true, either LuaJIT is too -- slow or the profiling functions are too slow and need to be fixed) - eq(s, '0') + eq('0', s) -- more or less the same goes for the microsecond part, if it doesn't -- start with 0, it's too slow. diff --git a/test/unit/tempfile_spec.lua b/test/unit/tempfile_spec.lua index e35490a561..bb0e56d640 100644 --- a/test/unit/tempfile_spec.lua +++ b/test/unit/tempfile_spec.lua @@ -28,7 +28,7 @@ describe('tempfile related functions', function() assert.True(dir ~= nil and dir:len() > 0) -- os_file_is_writable returns 2 for a directory which we have rights -- to write into. - eq(lib.os_file_is_writable(helpers.to_cstr(dir)), 2) + eq(2, lib.os_file_is_writable(helpers.to_cstr(dir))) for entry in vim.fs.dir(dir) do assert.True(entry == '.' or entry == '..') end @@ -57,7 +57,7 @@ describe('tempfile related functions', function() itp('generate file name in Nvim own temp directory', function() local dir = vim_gettempdir() local file = vim_tempname() - eq(string.sub(file, 1, string.len(dir)), dir) + eq(dir, string.sub(file, 1, string.len(dir))) end) end) end) -- cgit From 9cc755ad6a60e2b028d61c1dca62f8fe20f652d7 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 12 Mar 2024 05:39:21 +0800 Subject: vim-patch:0049a495c8d4 (#27817) runtime(doc): improve 'winfixbuf' docs (vim/vim#14180) - Make it not sound like a buffer option. - "!" is called a modifier, not an option. https://github.com/vim/vim/commit/0049a495c8d4a597773587f622d8cc8573c2eb75 --- runtime/doc/options.txt | 7 ++++--- runtime/lua/vim/_meta/options.lua | 6 +++--- src/nvim/options.lua | 6 +++--- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index f35700218c..271652f8ae 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -270,6 +270,7 @@ created, thus they behave slightly differently: Option Reason ~ 'previewwindow' there can only be a single one 'scroll' specific to existing window + 'winfixbuf' specific to existing window 'winfixheight' specific to existing window 'winfixwidth' specific to existing window @@ -7175,11 +7176,11 @@ A jump table for the options with a short description can be found at |Q_op|. *'winfixbuf'* *'wfb'* *'nowinfixbuf'* *'nowfb'* 'winfixbuf' 'wfb' boolean (default off) local to window - If enabled, the buffer and any window that displays it are paired. + If enabled, the window and the buffer it is displaying are paired. For example, attempting to change the buffer with |:edit| will fail. Other commands which change a window's buffer such as |:cnext| will - also skip any window with 'winfixbuf' enabled. However if a command - has an "!" option, a window can be forced to switch buffers. + also skip any window with 'winfixbuf' enabled. However if an Ex + command has a "!" modifier, it can force switching buffers. *'winfixheight'* *'wfh'* *'nowinfixheight'* *'nowfh'* 'winfixheight' 'wfh' boolean (default off) diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index e9ac2fe08f..cba52f0afa 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -7876,11 +7876,11 @@ vim.o.wi = vim.o.window vim.go.window = vim.o.window vim.go.wi = vim.go.window ---- If enabled, the buffer and any window that displays it are paired. +--- If enabled, the window and the buffer it is displaying are paired. --- For example, attempting to change the buffer with `:edit` will fail. --- Other commands which change a window's buffer such as `:cnext` will ---- also skip any window with 'winfixbuf' enabled. However if a command ---- has an "!" option, a window can be forced to switch buffers. +--- also skip any window with 'winfixbuf' enabled. However if an Ex +--- command has a "!" modifier, it can force switching buffers. --- --- @type boolean vim.o.winfixbuf = false diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 5e8bc1361c..411acbcc82 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -9822,11 +9822,11 @@ return { abbreviation = 'wfb', defaults = { if_true = false }, desc = [=[ - If enabled, the buffer and any window that displays it are paired. + If enabled, the window and the buffer it is displaying are paired. For example, attempting to change the buffer with |:edit| will fail. Other commands which change a window's buffer such as |:cnext| will - also skip any window with 'winfixbuf' enabled. However if a command - has an "!" option, a window can be forced to switch buffers. + also skip any window with 'winfixbuf' enabled. However if an Ex + command has a "!" modifier, it can force switching buffers. ]=], full_name = 'winfixbuf', pv_name = 'p_wfb', -- cgit From cf156377e80232aa904b92e4af29dd6c61952401 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 12 Mar 2024 06:56:06 +0800 Subject: vim-patch:8.2.4944: text properties are wrong after "cc" (#27821) Problem: Text properties are wrong after "cc". (Axel Forsman) Solution: Pass the deleted byte count to inserted_bytes(). (closes vim/vim#10412, closes vim/vim#7737, closes vim/vim#5763) https://github.com/vim/vim/commit/d0b1a09f44654bb5e29b09de1311845200f17d90 Co-authored-by: LemonBoy --- src/nvim/change.c | 11 ++++------- src/nvim/ops.c | 11 ++--------- test/functional/lua/buffer_updates_spec.lua | 2 +- 3 files changed, 7 insertions(+), 17 deletions(-) diff --git a/src/nvim/change.c b/src/nvim/change.c index b914bc29fe..3d06c6956e 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -1935,19 +1935,16 @@ theend: /// If "fixpos" is true fix the cursor position when done. void truncate_line(int fixpos) { - char *newp; linenr_T lnum = curwin->w_cursor.lnum; colnr_T col = curwin->w_cursor.col; + char *old_line = ml_get(lnum); + char *newp = col == 0 ? xstrdup("") : xstrnsave(old_line, (size_t)col); + int deleted = (int)strlen(old_line) - col; - if (col == 0) { - newp = xstrdup(""); - } else { - newp = xstrnsave(ml_get(lnum), (size_t)col); - } ml_replace(lnum, newp, false); // mark the buffer as changed and prepare for displaying - changed_bytes(lnum, curwin->w_cursor.col); + inserted_bytes(lnum, curwin->w_cursor.col, deleted, 0); // If "fixpos" is true we don't want to end up positioned at the NUL. if (fixpos && curwin->w_cursor.col > 0) { diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 5a0ef66e91..4e27c44262 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -1613,15 +1613,8 @@ int op_delete(oparg_T *oap) } else { beginline(0); // cursor in column 0 } - - int old_len = (int)strlen(ml_get(curwin->w_cursor.lnum)); - truncate_line(false); // delete the rest of the line - - extmark_splice_cols(curbuf, - (int)curwin->w_cursor.lnum - 1, curwin->w_cursor.col, - old_len - curwin->w_cursor.col, 0, kExtmarkUndo); - - // leave cursor past last char in line + truncate_line(false); // delete the rest of the line, + // leave cursor past last char in line if (oap->line_count > 1) { u_clearline(curbuf); // "U" command not possible after "2cc" } diff --git a/test/functional/lua/buffer_updates_spec.lua b/test/functional/lua/buffer_updates_spec.lua index 1f9f61ebf7..2479a433e6 100644 --- a/test/functional/lua/buffer_updates_spec.lua +++ b/test/functional/lua/buffer_updates_spec.lua @@ -541,7 +541,7 @@ describe('lua: nvim_buf_attach on_bytes', function() feed 'cc' check_events { - { 'test1', 'bytes', 1, 4, 0, 0, 0, 0, 15, 15, 0, 0, 0 }, + { 'test1', 'bytes', 1, 3, 0, 0, 0, 0, 15, 15, 0, 0, 0 }, } feed '' -- cgit From 6481da3015fd6cf136e752c9123078223c50d91c Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 12 Mar 2024 07:19:30 +0800 Subject: vim-patch:9.1.0166: Internal error with blockwise getregion() in another buffer (#27819) Problem: Internal error with blockwise getregion() in another buffer Solution: Also change curwin->w_buffer when changing curbuf (zeertzjq) closes: vim/vim#14179 https://github.com/vim/vim/commit/5406eb8722bddb6a04876956f9a53c1752994851 --- src/nvim/eval/funcs.c | 22 ++++++++-------------- test/old/testdir/test_visual.vim | 8 ++++---- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index f37542890b..ab92aa7b34 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -2863,16 +2863,10 @@ static void f_getregion(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) return; } - buf_T *const save_curbuf = curbuf; - buf_T *findbuf = curbuf; - - if (fnum1 != 0) { - findbuf = buflist_findnr(fnum1); - // buffer not loaded - if (findbuf == NULL || findbuf->b_ml.ml_mfp == NULL) { - emsg(_(e_buffer_is_not_loaded)); - return; - } + buf_T *findbuf = fnum1 != 0 ? buflist_findnr(fnum1) : curbuf; + if (findbuf == NULL || findbuf->b_ml.ml_mfp == NULL) { + emsg(_(e_buffer_is_not_loaded)); + return; } if (p1.lnum < 1 || p1.lnum > findbuf->b_ml.ml_line_count) { @@ -2892,7 +2886,9 @@ static void f_getregion(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) return; } + buf_T *const save_curbuf = curbuf; curbuf = findbuf; + curwin->w_buffer = curbuf; const TriState save_virtual = virtual_op; virtual_op = virtual_active(); @@ -2975,10 +2971,8 @@ static void f_getregion(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) tv_list_append_allocated_string(rettv->vval.v_list, akt); } - if (curbuf != save_curbuf) { - curbuf = save_curbuf; - } - + curbuf = save_curbuf; + curwin->w_buffer = curbuf; virtual_op = save_virtual; } diff --git a/test/old/testdir/test_visual.vim b/test/old/testdir/test_visual.vim index 74742abc5d..d952400367 100644 --- a/test/old/testdir/test_visual.vim +++ b/test/old/testdir/test_visual.vim @@ -1772,11 +1772,11 @@ func Test_visual_getregion() for type in ['v', 'V', "\"] for exclusive in [v:false, v:true] call assert_equal(range(10)->mapnew('string(v:val)'), - \ getregion([g:buf, 1, 1, 0], [g:buf, 10, 2, 0]), - \ {'type': type, 'exclusive': exclusive }) + \ getregion([g:buf, 1, 1, 0], [g:buf, 10, 2, 0], + \ {'type': type, 'exclusive': exclusive })) call assert_equal(range(10)->mapnew('string(v:val)'), - \ getregion([g:buf, 10, 2, 0], [g:buf, 1, 1, 0]), - \ {'type': type, 'exclusive': exclusive }) + \ getregion([g:buf, 10, 2, 0], [g:buf, 1, 1, 0], + \ {'type': type, 'exclusive': exclusive })) endfor endfor -- cgit From e20e5ecf0afe91c67dc4646df8c9bc286d202bf4 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 12 Mar 2024 07:19:47 +0800 Subject: vim-patch:9.1.0167: Changing buffer in another window causes it to show matchparen (#27820) Problem: Changing buffer in another window using win_execute() causes it to show matchparen (after 9.0.0969). Solution: Delay highlighting with SafeState in BufWinEnter. (zeertzjq) closes: vim/vim#14177 https://github.com/vim/vim/commit/49ffb6b428e1e053446ec0209558a8f9d0963ae7 --- runtime/plugin/matchparen.vim | 3 ++- test/functional/legacy/matchparen_spec.lua | 43 +++++++++++++++++++++++++++--- test/old/testdir/test_matchparen.vim | 25 +++++++++++++++++ 3 files changed, 66 insertions(+), 5 deletions(-) diff --git a/runtime/plugin/matchparen.vim b/runtime/plugin/matchparen.vim index 4235a0d39b..96c54ee6d8 100644 --- a/runtime/plugin/matchparen.vim +++ b/runtime/plugin/matchparen.vim @@ -22,7 +22,8 @@ let s:has_matchaddpos = exists('*matchaddpos') augroup matchparen " Replace all matchparen autocommands - autocmd! CursorMoved,CursorMovedI,WinEnter,BufWinEnter,WinScrolled * call s:Highlight_Matching_Pair() + autocmd! CursorMoved,CursorMovedI,WinEnter,WinScrolled * call s:Highlight_Matching_Pair() + autocmd! BufWinEnter * autocmd SafeState * ++once call s:Highlight_Matching_Pair() autocmd! WinLeave,BufLeave * call s:Remove_Matches() if exists('##TextChanged') autocmd! TextChanged,TextChangedI * call s:Highlight_Matching_Pair() diff --git a/test/functional/legacy/matchparen_spec.lua b/test/functional/legacy/matchparen_spec.lua index b03107deb0..137448acbd 100644 --- a/test/functional/legacy/matchparen_spec.lua +++ b/test/functional/legacy/matchparen_spec.lua @@ -61,13 +61,15 @@ describe('matchparen', function() set hidden call setline(1, ['()']) normal 0 + + func OtherBuffer() + enew + exe "normal iaa\0" + endfunc ]]) screen:expect(screen1) - exec([[ - enew - exe "normal iaa\0" - ]]) + exec('call OtherBuffer()') screen:expect(screen2) feed('') @@ -77,6 +79,39 @@ describe('matchparen', function() screen:expect(screen2) end) + -- oldtest: Test_matchparen_win_execute() + it('matchparen highlight when switching buffer in win_execute()', function() + local screen = Screen.new(20, 5) + screen:set_default_attr_ids({ + [1] = { background = Screen.colors.Cyan }, + [2] = { reverse = true, bold = true }, + [3] = { reverse = true }, + }) + screen:attach() + + exec([[ + source $VIMRUNTIME/plugin/matchparen.vim + let s:win = win_getid() + call setline(1, '{}') + split + + func SwitchBuf() + call win_execute(s:win, 'enew | buffer #') + endfunc + ]]) + screen:expect([[ + {1:^{}} | + {2:[No Name] [+] }| + {} | + {3:[No Name] [+] }| + | + ]]) + + -- Switching buffer away and back shouldn't change matchparen highlight. + exec('call SwitchBuf()') + screen:expect_unchanged() + end) + -- oldtest: Test_matchparen_pum_clear() it('is cleared when completion popup is shown', function() local screen = Screen.new(30, 9) diff --git a/test/old/testdir/test_matchparen.vim b/test/old/testdir/test_matchparen.vim index 3138180c66..ab425b046a 100644 --- a/test/old/testdir/test_matchparen.vim +++ b/test/old/testdir/test_matchparen.vim @@ -61,6 +61,31 @@ func Test_matchparen_clear_highlight() call StopVimInTerminal(buf) endfunc +" Test for matchparen highlight when switching buffer in win_execute() +func Test_matchparen_win_execute() + CheckScreendump + + let lines =<< trim END + source $VIMRUNTIME/plugin/matchparen.vim + let s:win = win_getid() + call setline(1, '{}') + split + + func SwitchBuf() + call win_execute(s:win, 'enew | buffer #') + endfunc + END + call writefile(lines, 'XMatchparenWinExecute', 'D') + let buf = RunVimInTerminal('-S XMatchparenWinExecute', #{rows: 5}) + call VerifyScreenDump(buf, 'Test_matchparen_win_execute_1', {}) + + " Switching buffer away and back shouldn't change matchparen highlight. + call term_sendkeys(buf, ":call SwitchBuf()\:\") + call VerifyScreenDump(buf, 'Test_matchparen_win_execute_1', {}) + + call StopVimInTerminal(buf) +endfunc + " Test for scrolling that modifies buffer during visual block func Test_matchparen_pum_clear() CheckScreendump -- cgit From 59e3bcfb00f18ce5ee6643f0c6d303afb6c7c046 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 12 Mar 2024 07:20:04 +0800 Subject: vim-patch:5cd86c6cff94 (#27822) runtime(vim): Update base-syntax, improve number matching (vim/vim#14175) - Limit blob literals to an even number of hexadecimal digits and correctly located dots. - Match octal numbers. The current version unsuccessfully attempts to match a leading '-' as part of the float literal. It's actually parsed as part of the literal for all numbers but the syntax file hasn't matched it like that for a long time and highlights negative numbers as UNARY-MINUS NUMBER. This will be fixed when better expression matching is implemented. https://github.com/vim/vim/commit/5cd86c6cff94256ed2db872c46b57da259a648ac Co-authored-by: dkearns --- runtime/syntax/vim.vim | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim index 6d535c5e7e..3141e806d1 100644 --- a/runtime/syntax/vim.vim +++ b/runtime/syntax/vim.vim @@ -145,13 +145,14 @@ endif " Numbers {{{2 " ======= -syn match vimNumber '\<\d\+\%(\.\d\+\%([eE][+-]\=\d\+\)\=\)\=' skipwhite nextgroup=vimGlobal,vimSubst1,vimCommand,vimComment,vim9Comment -syn match vimNumber '-\d\+\%(\.\d\+\%([eE][+-]\=\d\+\)\=\)\=' skipwhite nextgroup=vimGlobal,vimSubst1,vimCommand,vimComment,vim9Comment -syn match vimNumber '\<0[xX]\x\+' skipwhite nextgroup=vimGlobal,vimSubst1,vimCommand,vimComment,vim9Comment -syn match vimNumber '\%(^\|\A\)\zs#\x\{6}' skipwhite nextgroup=vimGlobal,vimSubst1,vimCommand,vimComment,vim9Comment -syn match vimNumber '\<0[zZ][a-zA-Z0-9.]\+' skipwhite nextgroup=vimGlobal,vimSubst1,vimCommand,vimComment,vim9Comment -syn match vimNumber '0[0-7]\+' skipwhite nextgroup=vimGlobal,vimSubst1,vimCommand,vimComment,vim9Comment -syn match vimNumber '0[bB][01]\+' skipwhite nextgroup=vimGlobal,vimSubst1,vimCommand,vimComment,vim9Comment +syn case ignore +syn match vimNumber '\<\d\+\%(\.\d\+\%(e[+-]\=\d\+\)\=\)\=' skipwhite nextgroup=vimGlobal,vimSubst1,vimCommand,vimComment,vim9Comment +syn match vimNumber '\<0b[01]\+' skipwhite nextgroup=vimGlobal,vimSubst1,vimCommand,vimComment,vim9Comment +syn match vimNumber '\<0o\=\o\+' skipwhite nextgroup=vimGlobal,vimSubst1,vimCommand,vimComment,vim9Comment +syn match vimNumber '\<0x\x\+' skipwhite nextgroup=vimGlobal,vimSubst1,vimCommand,vimComment,vim9Comment +syn match vimNumber '\<0z\%(\x\x\)\+\%(\.\%(\x\x\)\+\)*' skipwhite nextgroup=vimGlobal,vimSubst1,vimCommand,vimComment,vim9Comment +syn match vimNumber '\%(^\|\A\)\zs#\x\{6}' skipwhite nextgroup=vimGlobal,vimSubst1,vimCommand,vimComment,vim9Comment +syn case match " All vimCommands are contained by vimIsCommand. {{{2 syn cluster vimCmdList contains=vimAbb,vimAddress,vimAutoCmd,vimAugroup,vimBehave,vimEcho,vimEchoHL,vimExecute,vimIsCommand,vimExtCmd,vimFunction,vimGlobal,vimHighlight,vimLet,vimMap,vimMark,vimNotFunc,vimNorm,vimSet,vimSyntax,vimUnlet,vimUnmap,vimUserCmd,vimMenu,vimMenutranslate -- cgit From b02a4d8ac39bafdbfd490bfbab35e3202e6f709c Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 12 Mar 2024 07:20:22 +0800 Subject: vim-patch:9.1.0168: too many STRLEN() calls (#27823) Problem: too many STRLEN() calls Solution: Make use of ml_get_len() calls instead (John Marriott) closes: vim/vim#14123 https://github.com/vim/vim/commit/bfcc895482c717c9f6d86890d789ec739c3016b4 Co-authored-by: John Marriott --- src/nvim/change.c | 15 ++++++++------- src/nvim/edit.c | 2 +- src/nvim/eval.c | 4 ++-- src/nvim/eval/funcs.c | 20 +++++++++----------- 4 files changed, 20 insertions(+), 21 deletions(-) diff --git a/src/nvim/change.c b/src/nvim/change.c index 3d06c6956e..8b1e7587de 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -714,7 +714,7 @@ void ins_char_bytes(char *buf, size_t charlen) size_t col = (size_t)curwin->w_cursor.col; linenr_T lnum = curwin->w_cursor.lnum; char *oldp = ml_get(lnum); - size_t linelen = strlen(oldp) + 1; // length of old line including NUL + size_t linelen = (size_t)ml_get_len(lnum) + 1; // length of old line including NUL // The lengths default to the values for when not replacing. size_t oldlen = 0; // nr of bytes inserted @@ -821,7 +821,7 @@ void ins_str(char *s) colnr_T col = curwin->w_cursor.col; char *oldp = ml_get(lnum); - int oldlen = (int)strlen(oldp); + int oldlen = ml_get_len(lnum); char *newp = xmalloc((size_t)oldlen + (size_t)newlen + 1); if (col > 0) { @@ -879,7 +879,7 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) colnr_T col = curwin->w_cursor.col; bool fixpos = fixpos_arg; char *oldp = ml_get(lnum); - colnr_T oldlen = (colnr_T)strlen(oldp); + colnr_T oldlen = ml_get_len(lnum); // Can't do anything when the cursor is on the NUL after the line. if (col >= oldlen) { @@ -1117,7 +1117,7 @@ bool open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) colnr_T mincol = curwin->w_cursor.col + 1; // make a copy of the current line so we can mess with it - char *saved_line = xstrdup(get_cursor_line_ptr()); + char *saved_line = xstrnsave(get_cursor_line_ptr(), (size_t)get_cursor_line_len()); if (State & VREPLACE_FLAG) { // With MODE_VREPLACE we make a copy of the next line, which we will be @@ -1128,7 +1128,8 @@ bool open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) // the line, replacing what was there before and pushing the right // stuff onto the replace stack. -- webb. if (curwin->w_cursor.lnum < orig_line_count) { - next_line = xstrdup(ml_get(curwin->w_cursor.lnum + 1)); + next_line = xstrnsave(ml_get(curwin->w_cursor.lnum + 1), + (size_t)ml_get_len(curwin->w_cursor.lnum + 1)); } else { next_line = xstrdup(""); } @@ -1908,7 +1909,7 @@ bool open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) // stuff onto the replace stack (via ins_char()). if (State & VREPLACE_FLAG) { // Put new line in p_extra - p_extra = xstrdup(get_cursor_line_ptr()); + p_extra = xstrnsave(get_cursor_line_ptr(), (size_t)get_cursor_line_len()); // Put back original line ml_replace(curwin->w_cursor.lnum, next_line, false); @@ -1939,7 +1940,7 @@ void truncate_line(int fixpos) colnr_T col = curwin->w_cursor.col; char *old_line = ml_get(lnum); char *newp = col == 0 ? xstrdup("") : xstrnsave(old_line, (size_t)col); - int deleted = (int)strlen(old_line) - col; + int deleted = ml_get_len(lnum) - col; ml_replace(lnum, newp, false); diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 54deb0f1c3..a0d6f7125e 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -4331,7 +4331,7 @@ static bool ins_tab(void) if (State & VREPLACE_FLAG) { pos = curwin->w_cursor; cursor = &pos; - saved_line = xstrdup(get_cursor_line_ptr()); + saved_line = xstrnsave(get_cursor_line_ptr(), (size_t)get_cursor_line_len()); ptr = saved_line + pos.col; } else { ptr = get_cursor_pos_ptr(); diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 3d224bfa0f..e4ee254193 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -6699,7 +6699,7 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret if (charcol) { len = mb_charlen(ml_get(pos.lnum)); } else { - len = (int)strlen(ml_get(pos.lnum)); + len = ml_get_len(pos.lnum); } // We accept "$" for the column number: last column. @@ -6789,7 +6789,7 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret if (charcol) { pos.col = (colnr_T)mb_charlen(get_cursor_line_ptr()); } else { - pos.col = (colnr_T)strlen(get_cursor_line_ptr()); + pos.col = get_cursor_line_len(); } } return &pos; diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index ab92aa7b34..1d5835c9bf 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -738,7 +738,7 @@ static void get_col(typval_T *argvars, typval_T *rettv, bool charcol) if (fp->col == MAXCOL) { // '> can be MAXCOL, get the length of the line then if (fp->lnum <= curbuf->b_ml.ml_line_count) { - col = (colnr_T)strlen(ml_get(fp->lnum)) + 1; + col = ml_get_len(fp->lnum) + 1; } else { col = MAXCOL; } @@ -8688,7 +8688,7 @@ static void f_synID(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) int id = 0; if (!transerr && lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count - && col >= 0 && (size_t)col < strlen(ml_get(lnum))) { + && col >= 0 && col < ml_get_len(lnum)) { id = syn_get_id(curwin, lnum, col, trans, NULL, false); } @@ -8811,8 +8811,8 @@ static void f_synconcealed(typval_T *argvars, typval_T *rettv, EvalFuncData fptr CLEAR_FIELD(str); - if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count && col >= 0 - && (size_t)col <= strlen(ml_get(lnum)) && curwin->w_p_cole > 0) { + if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count + && col >= 0 && col <= ml_get_len(lnum) && curwin->w_p_cole > 0) { syn_get_id(curwin, lnum, col, false, NULL, false); syntax_flags = get_syntax_info(&matchid); @@ -8845,10 +8845,8 @@ static void f_synstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) const linenr_T lnum = tv_get_lnum(argvars); const colnr_T col = (colnr_T)tv_get_number(&argvars[1]) - 1; - if (lnum >= 1 - && lnum <= curbuf->b_ml.ml_line_count - && col >= 0 - && (size_t)col <= strlen(ml_get(lnum))) { + if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count + && col >= 0 && col <= ml_get_len(lnum)) { tv_list_alloc_ret(rettv, kListLenMayKnow); syn_get_id(curwin, lnum, col, false, NULL, true); @@ -9218,9 +9216,9 @@ static void f_virtcol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) if (fp->col < 0) { fp->col = 0; } else { - const size_t len = strlen(ml_get(fp->lnum)); - if (fp->col > (colnr_T)len) { - fp->col = (colnr_T)len; + const colnr_T len = ml_get_len(fp->lnum); + if (fp->col > len) { + fp->col = len; } } getvvcol(curwin, fp, &vcol_start, NULL, &vcol_end); -- cgit From ac8cd5368db83cced9bc049ceb50c21cb8a4f743 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 12 Mar 2024 10:44:53 +0800 Subject: refactor: use ml_get_buf_len() in API code (#27825) --- src/nvim/api/buffer.c | 29 ++++++++++++++--------------- src/nvim/api/extmark.c | 16 ++++++++-------- src/nvim/api/private/helpers.c | 8 ++++---- src/nvim/lua/executor.c | 4 ++-- src/nvim/lua/stdlib.c | 6 +++--- src/nvim/lua/treesitter.c | 2 +- 6 files changed, 32 insertions(+), 33 deletions(-) diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 7f195de959..035e36a2dd 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -529,18 +529,18 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In // Another call to ml_get_buf() may free the lines, so we make copies char *str_at_start = ml_get_buf(buf, (linenr_T)start_row); - size_t len_at_start = strlen(str_at_start); - str_at_start = arena_memdupz(arena, str_at_start, len_at_start); - start_col = start_col < 0 ? (int64_t)len_at_start + start_col + 1 : start_col; - VALIDATE_RANGE((start_col >= 0 && (size_t)start_col <= len_at_start), "start_col", { + colnr_T len_at_start = ml_get_buf_len(buf, (linenr_T)start_row); + str_at_start = arena_memdupz(arena, str_at_start, (size_t)len_at_start); + start_col = start_col < 0 ? len_at_start + start_col + 1 : start_col; + VALIDATE_RANGE((start_col >= 0 && start_col <= len_at_start), "start_col", { return; }); char *str_at_end = ml_get_buf(buf, (linenr_T)end_row); - size_t len_at_end = strlen(str_at_end); - str_at_end = arena_memdupz(arena, str_at_end, len_at_end); - end_col = end_col < 0 ? (int64_t)len_at_end + end_col + 1 : end_col; - VALIDATE_RANGE((end_col >= 0 && (size_t)end_col <= len_at_end), "end_col", { + colnr_T len_at_end = ml_get_buf_len(buf, (linenr_T)end_row); + str_at_end = arena_memdupz(arena, str_at_end, (size_t)len_at_end); + end_col = end_col < 0 ? len_at_end + end_col + 1 : end_col; + VALIDATE_RANGE((end_col >= 0 && end_col <= len_at_end), "end_col", { return; }); @@ -563,12 +563,10 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In if (start_row == end_row) { old_byte = (bcount_t)end_col - start_col; } else { - old_byte += (bcount_t)len_at_start - start_col; + old_byte += len_at_start - start_col; for (int64_t i = 1; i < end_row - start_row; i++) { int64_t lnum = start_row + i; - - const char *bufline = ml_get_buf(buf, (linenr_T)lnum); - old_byte += (bcount_t)(strlen(bufline)) + 1; + old_byte += ml_get_buf_len(buf, (linenr_T)lnum) + 1; } old_byte += (bcount_t)end_col + 1; } @@ -577,7 +575,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In String last_item = replacement.items[replacement.size - 1].data.string; size_t firstlen = (size_t)start_col + first_item.size; - size_t last_part_len = len_at_end - (size_t)end_col; + size_t last_part_len = (size_t)len_at_end - (size_t)end_col; if (replacement.size == 1) { firstlen += last_part_len; } @@ -1324,7 +1322,7 @@ static void fix_cursor_cols(win_T *win, linenr_T start_row, colnr_T start_col, l // it already (in case virtualedit is active) // column might be additionally adjusted below // to keep it inside col range if needed - colnr_T len = (colnr_T)strlen(ml_get_buf(win->w_buffer, new_end_row)); + colnr_T len = ml_get_buf_len(win->w_buffer, new_end_row); if (win->w_cursor.col < len) { win->w_cursor.col = len; } @@ -1424,6 +1422,7 @@ void buf_collect_lines(buf_T *buf, size_t n, linenr_T start, int start_idx, bool for (size_t i = 0; i < n; i++) { linenr_T lnum = start + (linenr_T)i; char *bufstr = ml_get_buf(buf, lnum); - push_linestr(lstate, l, bufstr, strlen(bufstr), start_idx + (int)i, replace_nl, arena); + size_t bufstrlen = (size_t)ml_get_buf_len(buf, lnum); + push_linestr(lstate, l, bufstr, bufstrlen, start_idx + (int)i, replace_nl, arena); } } diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index 1b03a97edb..a21cf5b337 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -682,7 +682,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer goto error; }); - size_t len = 0; + colnr_T len = 0; if (HAS_KEY(opts, set_extmark, spell)) { hl.flags |= (opts->spell) ? kSHSpellOn : kSHSpellOff; @@ -712,16 +712,16 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer }); line = buf->b_ml.ml_line_count; } else if (line < buf->b_ml.ml_line_count) { - len = opts->ephemeral ? MAXCOL : strlen(ml_get_buf(buf, (linenr_T)line + 1)); + len = opts->ephemeral ? MAXCOL : ml_get_buf_len(buf, (linenr_T)line + 1); } if (col == -1) { - col = (Integer)len; - } else if (col > (Integer)len) { + col = len; + } else if (col > len) { VALIDATE_RANGE(!strict, "col", { goto error; }); - col = (Integer)len; + col = len; } else if (col < -1) { VALIDATE_RANGE(false, "col", { goto error; @@ -730,7 +730,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer if (col2 >= 0) { if (line2 >= 0 && line2 < buf->b_ml.ml_line_count) { - len = opts->ephemeral ? MAXCOL : strlen(ml_get_buf(buf, (linenr_T)line2 + 1)); + len = opts->ephemeral ? MAXCOL : ml_get_buf_len(buf, (linenr_T)line2 + 1); } else if (line2 == buf->b_ml.ml_line_count) { // We are trying to add an extmark past final newline len = 0; @@ -738,11 +738,11 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer // reuse len from before line2 = (int)line; } - if (col2 > (Integer)len) { + if (col2 > len) { VALIDATE_RANGE(!strict, "end_col", { goto error; }); - col2 = (int)len; + col2 = len; } } else if (line2 >= 0) { col2 = 0; diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 1cd98aa0c4..a17e78cc31 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -524,10 +524,10 @@ String buf_get_text(buf_T *buf, int64_t lnum, int64_t start_col, int64_t end_col } char *bufstr = ml_get_buf(buf, (linenr_T)lnum); - size_t line_length = strlen(bufstr); + colnr_T line_length = ml_get_buf_len(buf, (linenr_T)lnum); - start_col = start_col < 0 ? (int64_t)line_length + start_col + 1 : start_col; - end_col = end_col < 0 ? (int64_t)line_length + end_col + 1 : end_col; + start_col = start_col < 0 ? line_length + start_col + 1 : start_col; + end_col = end_col < 0 ? line_length + end_col + 1 : end_col; if (start_col >= MAXCOL || end_col >= MAXCOL) { api_set_error(err, kErrorTypeValidation, "Column index is too high"); @@ -539,7 +539,7 @@ String buf_get_text(buf_T *buf, int64_t lnum, int64_t start_col, int64_t end_col return rv; } - if ((size_t)start_col >= line_length) { + if (start_col >= line_length) { return rv; } diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 1a9bd026b5..08677b77b0 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -1767,7 +1767,7 @@ void ex_luado(exarg_T *const eap) lua_pushvalue(lstate, -1); const char *const old_line = ml_get_buf(curbuf, l); // Get length of old_line here as calling Lua code may free it. - const size_t old_line_len = strlen(old_line); + const colnr_T old_line_len = ml_get_buf_len(curbuf, l); lua_pushstring(lstate, old_line); lua_pushnumber(lstate, (lua_Number)l); if (nlua_pcall(lstate, 2, 1)) { @@ -1791,7 +1791,7 @@ void ex_luado(exarg_T *const eap) } } ml_replace(l, new_line_transformed, false); - inserted_bytes(l, 0, (int)old_line_len, (int)new_line_len); + inserted_bytes(l, 0, old_line_len, (int)new_line_len); } lua_pop(lstate, 1); } diff --git a/src/nvim/lua/stdlib.c b/src/nvim/lua/stdlib.c index 8f58fd1a1a..a5262efcfa 100644 --- a/src/nvim/lua/stdlib.c +++ b/src/nvim/lua/stdlib.c @@ -107,15 +107,15 @@ static int regex_match_line(lua_State *lstate) } char *line = ml_get_buf(buf, rownr + 1); - size_t len = strlen(line); + colnr_T len = ml_get_buf_len(buf, rownr + 1); - if (start < 0 || (size_t)start > len) { + if (start < 0 || start > len) { return luaL_error(lstate, "invalid start"); } char save = NUL; if (end >= 0) { - if ((size_t)end > len || end < start) { + if (end > len || end < start) { return luaL_error(lstate, "invalid end"); } save = line[end]; diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 25a753b179..6d6ef6c7b9 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -371,7 +371,7 @@ static const char *input_cb(void *payload, uint32_t byte_index, TSPoint position return ""; } char *line = ml_get_buf(bp, (linenr_T)position.row + 1); - size_t len = strlen(line); + size_t len = (size_t)ml_get_buf_len(bp, (linenr_T)position.row + 1); if (position.column > len) { *bytes_read = 0; return ""; -- cgit From a74e869ffa503cc9c2d21836e24fec7a7ffca147 Mon Sep 17 00:00:00 2001 From: dundargoc <33953936+dundargoc@users.noreply.github.com> Date: Tue, 12 Mar 2024 06:51:53 +0100 Subject: docs: small fixes (#27364) Co-authored-by: C.D. MacEachern Co-authored-by: Ynda Jas Co-authored-by: Owen Hines Co-authored-by: Wanten <41904684+WantenMN@users.noreply.github.com> Co-authored-by: lukasvrenner <118417051+lukasvrenner@users.noreply.github.com> Co-authored-by: cuinix <915115094@qq.com> --- BUILD.md | 2 +- MAINTAIN.md | 2 +- runtime/doc/index.txt | 1 - runtime/doc/lsp.txt | 14 +++++++------- runtime/doc/luaref.txt | 2 +- runtime/doc/map.txt | 2 +- runtime/doc/vim_diff.txt | 1 + runtime/lua/nvim/health.lua | 2 +- runtime/lua/vim/lsp/client.lua | 2 +- runtime/lua/vim/lsp/util.lua | 2 +- runtime/tutor/en/vim-01-beginner.tutor | 16 ++++++++-------- src/clint.py | 2 +- src/nvim/generators/gen_api_dispatch.lua | 2 +- src/nvim/marktree.c | 4 ++-- src/nvim/move.c | 4 ++-- 15 files changed, 29 insertions(+), 29 deletions(-) diff --git a/BUILD.md b/BUILD.md index ce553eb8bf..2c4cdc76ab 100644 --- a/BUILD.md +++ b/BUILD.md @@ -290,7 +290,7 @@ Platform-specific requirements are listed below. ### Ubuntu / Debian ```sh -sudo apt-get install ninja-build gettext cmake unzip curl +sudo apt-get install ninja-build gettext cmake unzip curl build-essential ``` ### CentOS / RHEL / Fedora diff --git a/MAINTAIN.md b/MAINTAIN.md index 3c21b13276..b2434d6259 100644 --- a/MAINTAIN.md +++ b/MAINTAIN.md @@ -212,7 +212,7 @@ https://github.com/neovim/neovim-backup * Runner versions: * For special-purpose jobs where the runner version doesn't really matter, prefer `-latest` tags so we don't need to manually bump the versions. An - example of a special-purpose workflow is `labeler.yml`. + example of a special-purpose workflow is `labeler_pr.yml`. * For our testing jobs, which are in `test.yml` and `build.yml`, prefer to use the latest stable (i.e. non-beta) version explicitly. Avoid using the `-latest` tags here as it makes it difficult to determine from an diff --git a/runtime/doc/index.txt b/runtime/doc/index.txt index c1a8aec40e..969346c4c7 100644 --- a/runtime/doc/index.txt +++ b/runtime/doc/index.txt @@ -699,7 +699,6 @@ tag char note action in Normal mode ~ tag char note action in Normal mode ~ ------------------------------------------------------------------------------ ~ -g_CTRL-A g CTRL-A dump a memory profile |g_CTRL-G| g CTRL-G show information about current cursor position |g_CTRL-H| g CTRL-H start Select block mode diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index eb37cb2a6f..cecf7c8d38 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -978,9 +978,9 @@ Lua module: vim.lsp.client *lsp-client* case-sensitive. • {flags} (`table`) A table with flags for the client. The current (experimental) flags are: - • {allow_incremental_sync}? (`boolean`) Allow - using incremental sync for buffer edits - (defailt: `true`) + • {allow_incremental_sync}? (`boolean`, + default: `true`) Allow using incremental + sync for buffer edits • {debounce_text_changes} (`integer`, default: `150`) Debounce `didChange` notifications to the server by the given number in @@ -1149,9 +1149,9 @@ Lua module: vim.lsp.client *lsp-client* initialize request. Invalid/empty values will • {flags}? (`table`) A table with flags for the client. The current (experimental) flags are: - • {allow_incremental_sync}? (`boolean`) Allow - using incremental sync for buffer edits - (defailt: `true`) + • {allow_incremental_sync}? (`boolean`, default: + `true`) Allow using incremental sync for + buffer edits • {debounce_text_changes} (`integer`, default: `150`) Debounce `didChange` notifications to the server by the given number in @@ -2096,7 +2096,7 @@ rename({old_fname}, {new_fname}, {opts}) *vim.lsp.util.rename()* It deletes existing buffers that conflict with the renamed file name only when • `opts` requests overwriting; or - • the conflicting buffers are not loaded, so that deleting thme does not + • the conflicting buffers are not loaded, so that deleting them does not result in data loss. Parameters: ~ diff --git a/runtime/doc/luaref.txt b/runtime/doc/luaref.txt index e7b62f4c6c..cd0b648560 100644 --- a/runtime/doc/luaref.txt +++ b/runtime/doc/luaref.txt @@ -932,7 +932,7 @@ implicit extra parameter `self`. Thus, the statement is syntactic sugar for - `t.a.b.c:f = function (self, (` `params` `)` `body` `end` + `t.a.b.c:f = function (` `self`, `params` `)` `body` `end` ============================================================================== 2.6 Visibility Rules *lua-visibility* diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt index 68206dd494..9ec34d5d52 100644 --- a/runtime/doc/map.txt +++ b/runtime/doc/map.txt @@ -604,7 +604,7 @@ instead. Example: > map A oanother line Works like: > map \A oanother line -But after: +But after: > let mapleader = "," It works like: > map ,A oanother line diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index b0caf9fdaf..a76166abf7 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -80,6 +80,7 @@ Defaults *nvim-defaults* - 'tags' defaults to "./tags;,tags" - 'termguicolors' is enabled by default if Nvim can detect support from the host terminal +- 'ttimeout' is enabled - 'ttimeoutlen' defaults to 50 - 'ttyfast' is always set - 'undodir' defaults to ~/.local/state/nvim/undo// (|xdg|), auto-created diff --git a/runtime/lua/nvim/health.lua b/runtime/lua/nvim/health.lua index 0480e4df4e..585c8deaee 100644 --- a/runtime/lua/nvim/health.lua +++ b/runtime/lua/nvim/health.lua @@ -17,7 +17,7 @@ local shell_error = function() return vim.v.shell_error ~= 0 end -local suggest_faq = 'https://github.com/neovim/neovim/blob/docs/install/BUILD.md#building' +local suggest_faq = 'https://github.com/neovim/neovim/blob/master/BUILD.md#building' local function check_runtime() health.start('Runtime') diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua index ff0db166d5..d48be131f3 100644 --- a/runtime/lua/vim/lsp/client.lua +++ b/runtime/lua/vim/lsp/client.lua @@ -15,7 +15,7 @@ local validate = vim.validate --- @inlinedoc --- --- Allow using incremental sync for buffer edits ---- (defailt: `true`) +--- (default: `true`) --- @field allow_incremental_sync? boolean --- --- Debounce `didChange` notifications to the server by the given number in milliseconds. diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index f8e5b6a90d..fc99f54d03 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -690,7 +690,7 @@ end --- --- It deletes existing buffers that conflict with the renamed file name only when --- * `opts` requests overwriting; or ---- * the conflicting buffers are not loaded, so that deleting thme does not result in data loss. +--- * the conflicting buffers are not loaded, so that deleting them does not result in data loss. --- --- @param old_fname string --- @param new_fname string diff --git a/runtime/tutor/en/vim-01-beginner.tutor b/runtime/tutor/en/vim-01-beginner.tutor index c3decdef11..aed6cd2802 100644 --- a/runtime/tutor/en/vim-01-beginner.tutor +++ b/runtime/tutor/en/vim-01-beginner.tutor @@ -360,7 +360,7 @@ Fiix the errors oon thhis line and reeplace them witth undo. 7. To undo previous actions, type: `u`{normal} (lowercase u) To undo all the changes on a line, type: `U`{normal} (capital U) - To undo the undo's, type: ``{normal} + To undo the undos, type: ``{normal} # Lesson 3.1: THE PUT COMMAND @@ -508,7 +508,7 @@ NOTE: When the search reaches the end of the file it will continue at the # Lesson 4.3: MATCHING PARENTHESES SEARCH -** Type `%`{normal} to find a matching ),], or }. ** +** Type `%`{normal} to find a matching ), ], or }. ** 1. Place the cursor on any (, [, or { in the line below marked ✓. @@ -518,9 +518,9 @@ NOTE: When the search reaches the end of the file it will continue at the 4. Type `%`{normal} to move the cursor to the other matching bracket. - 5. Move the cursor to another (,),[,],{ or } and see what `%`{normal} does. + 5. Move the cursor to another (, ), [, ], {, or } and see what `%`{normal} does. -This ( is a test line with ('s, ['s ] and {'s } in it. )) +This ( is a test line with ('s, ['s, ] and {'s } in it. )) NOTE: This is very useful in debugging a program with unmatched parentheses! @@ -582,14 +582,14 @@ NOTE: You can also select the lines you want to substitute first using visual-mo ``{normal} takes you back to older positions, ``{normal} to newer positions. - 3. Typing `%`{normal} while the cursor is on a (,),[,],{, or } goes to its + 3. Typing `%`{normal} while the cursor is on a (, ), [, ], {, or } goes to its match. 4. To substitute new for the first old in a line type ~~~ cmd :s/old/new ~~~ - To substitute new for all 'old's on a line type + To substitute new for all olds on a line type ~~~ cmd :s/old/new/g ~~~ @@ -667,7 +667,7 @@ NOTE: If you were to exit Neovim and start it again with `nvim TEST`, the file 4. Type - `:w TEST`{vim} + `w TEST`{vim} where TEST is a filename that does not exist yet. Verify that you see @@ -762,7 +762,7 @@ Open up a line above this by typing O while the cursor is on this line. This li will allow you to pract appendi text to a line. This line will allow you to practice appending text to a line. -NOTE: [a](a), [i](i) and [A](A) all go to the same Insert mode, the only +NOTE: [a](a), [i](i), and [A](A) all go to the same Insert mode, the only difference is where the characters are inserted. # Lesson 6.3: ANOTHER WAY TO REPLACE diff --git a/src/clint.py b/src/clint.py index 062901b43a..9bca634171 100755 --- a/src/clint.py +++ b/src/clint.py @@ -1689,7 +1689,7 @@ def CheckSpacing(filename, clean_lines, linenum, error): # Look for < that is not surrounded by spaces. This is only # triggered if both sides are missing spaces, even though - # technically should should flag if at least one side is missing a + # technically should flag if at least one side is missing a # space. This is done to avoid some false positives with shifts. match = Search(r'[^\s<]<([^\s=<].*)', reduced_line) if (match and not FindNextMatchingAngleBracket(clean_lines, linenum, diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua index 04b4363e42..e9bc5e5fe3 100644 --- a/src/nvim/generators/gen_api_dispatch.lua +++ b/src/nvim/generators/gen_api_dispatch.lua @@ -259,7 +259,7 @@ put('version') fixdict(1 + #version) for _, item in ipairs(version) do -- NB: all items are mandatory. But any error will be less confusing - -- with placholder vim.NIL (than invalid mpack data) + -- with placeholder vim.NIL (than invalid mpack data) put(item[1], item[2] or vim.NIL) end put('build', version_build) diff --git a/src/nvim/marktree.c b/src/nvim/marktree.c index 0ebebf409e..34d6cd118f 100644 --- a/src/nvim/marktree.c +++ b/src/nvim/marktree.c @@ -460,7 +460,7 @@ static void meta_describe_key(uint32_t *meta_inc, MTKey k) meta_describe_key_inc(meta_inc, &k); } -// if x is internal, asumes x->meta[..] of children are correct +// if x is internal, assumes x->meta[..] of children are correct static void meta_describe_node(uint32_t *meta_node, MTNode *x) { memset(meta_node, 0, kMTMetaCount * sizeof(meta_node[0])); @@ -1425,7 +1425,7 @@ bool marktree_itr_get_ext(MarkTree *b, MTPos p, MarkTreeIter *itr, bool last, bo } if (meta_filter) { if (!meta_has(itr->x->meta[itr->i], meta_filter)) { - // this takes us to the interal position after the first rejected node + // this takes us to the internal position after the first rejected node break; } } diff --git a/src/nvim/move.c b/src/nvim/move.c index 551aa1bd4d..0f7f4d8719 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -239,7 +239,7 @@ static void reset_skipcol(win_T *wp) redraw_later(wp, UPD_SOME_VALID); } -// Update curwin->w_topline to move the cursor onto the screen. +// Update wp->w_topline to move the cursor onto the screen. void update_topline(win_T *wp) { bool check_botline = false; @@ -595,7 +595,7 @@ void changed_line_abv_curs_win(win_T *wp) |VALID_CHEIGHT|VALID_TOPLINE); } -// Make sure the value of curwin->w_botline is valid. +// Make sure the value of wp->w_botline is valid. void validate_botline(win_T *wp) { if (!(wp->w_valid & VALID_BOTLINE)) { -- cgit From 3bd84317fb59ed4f7ec6585c516f9f8f4d823fd6 Mon Sep 17 00:00:00 2001 From: James <89495599+IAKOBVS@users.noreply.github.com> Date: Tue, 12 Mar 2024 13:35:53 +0700 Subject: refactor: avoid quadratic behavior in backslash_halve() (#27827) The original implementation has a worst-case of O(n^2). Every time rem_backslash() is true, it calculates the length of the rest of the string, and shift the rest of it to the left; backslash_halve_save() copies the original string before doing backslash_halve(). The new implementation is O(n). It will find the first character where rem_backslash() is true (it will do nothing if it's always false), and shift the characters in-place; backslash_halve_save() avoids copying the original string before doing backslash_halve(). --- src/nvim/charset.c | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/nvim/charset.c b/src/nvim/charset.c index 20bd364c7e..2e6f24b2d5 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -1457,10 +1457,20 @@ bool rem_backslash(const char *str) /// @param p void backslash_halve(char *p) { - for (; *p; p++) { - if (rem_backslash(p)) { - STRMOVE(p, p + 1); + for (; *p && !rem_backslash(p); p++) {} + if (*p != NUL) { + char *dst = p; + goto start; + while (*p != NUL) { + if (rem_backslash(p)) { +start: + *dst++ = *(p + 1); + p += 2; + } else { + *dst++ = *p++; + } } + *dst = '\0'; } } @@ -1472,8 +1482,16 @@ void backslash_halve(char *p) char *backslash_halve_save(const char *p) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { - // TODO(philix): simplify and improve backslash_halve_save algorithm - char *res = xstrdup(p); - backslash_halve(res); + char *res = xmalloc(strlen(p) + 1); + char *dst = res; + while (*p != NUL) { + if (rem_backslash(p)) { + *dst++ = *(p + 1); + p += 2; + } else { + *dst++ = *p++; + } + } + *dst = '\0'; return res; } -- cgit From 41fb98d6fab5aa02ef370d1b2b283b078517ffa4 Mon Sep 17 00:00:00 2001 From: Tomas Slusny Date: Tue, 12 Mar 2024 08:15:55 +0100 Subject: fix: move fswatch linux check inside of vim.schedule (#27824) Fixes issue reported in the original PR: https://github.com/neovim/neovim/pull/27810 Signed-off-by: Tomas Slusny --- runtime/lua/vim/_watch.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/runtime/lua/vim/_watch.lua b/runtime/lua/vim/_watch.lua index 542e770246..23c810099e 100644 --- a/runtime/lua/vim/_watch.lua +++ b/runtime/lua/vim/_watch.lua @@ -289,10 +289,11 @@ function M.fswatch(path, opts, callback) end if data and #vim.trim(data) > 0 then - if vim.fn.has('linux') == 1 and vim.startswith(data, 'Event queue overflow') then - data = 'inotify(7) limit reached, see :h fswatch-limitations for more info.' - end vim.schedule(function() + if vim.fn.has('linux') == 1 and vim.startswith(data, 'Event queue overflow') then + data = 'inotify(7) limit reached, see :h fswatch-limitations for more info.' + end + vim.notify('fswatch: ' .. data, vim.log.levels.ERROR) end) end -- cgit From cb46f6e467268edf917cc3617b4c024a66b256de Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Tue, 12 Mar 2024 09:32:17 -0500 Subject: feat(treesitter): support URLs (#27132) Tree-sitter queries can add URLs to a capture using the `#set!` directive, e.g. (inline_link (link_text) @text.reference (link_destination) @text.uri (#set! @text.reference "url" @text.uri)) The pattern above is included by default in the `markdown_inline` highlight query so that users with supporting terminals will see hyperlinks. For now, this creates a hyperlink for *all* Markdown URLs of the pattern [link text](link url), even if `link url` does not contain a valid protocol (e.g. if `link url` is a path to a file). We may wish to change this in the future to only linkify when the URL has a valid protocol scheme, but for now we delegate handling this to the terminal emulator. In order to support directives which reference other nodes, the highlighter must be updated to use `iter_matches` rather than `iter_captures`. The former provides the `match` table which maps capture IDs to nodes. However, this has its own challenges: - `iter_matches` does not guarantee the order in which patterns are iterated matches the order in the query file. So we must enforce ordering manually using "subpriorities" (#27131). The pattern index of each match dictates the extmark's subpriority. - When injections are used, the highlighter contains multiple trees. The pattern indices of each tree must be offset relative to the maximum pattern index from all previous trees to ensure that extmarks appear in the correct order. - The `iter_captures` implementation currently has a bug where the "match" table is only returned for the first capture within a pattern (see #27274). This bug means that `#set!` directives in a query apply only to the first capture within a pattern. Unfortunately, many queries in the wild have come to depend on this behavior. `iter_matches` does not share this flaw, so switching to `iter_matches` exposed bugs in existing highlight queries. These queries have been updated in this repo, but may still need to be updated by users. The `#set!` directive applies to the _entire_ query pattern when used without a capture argument. To make `#set!` apply only to a single capture, the capture must be given as an argument. --- runtime/doc/news.txt | 3 + runtime/lua/vim/treesitter/highlighter.lua | 91 +++++++++++++++++++------- runtime/queries/markdown_inline/highlights.scm | 5 ++ runtime/queries/vimdoc/highlights.scm | 23 +++++-- test/functional/treesitter/highlight_spec.lua | 89 ++++++++++++++++++++++++- 5 files changed, 178 insertions(+), 33 deletions(-) diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 3ba7c5e681..44833e5f84 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -254,6 +254,9 @@ The following new APIs and features were added. indexing. • |:InspectTree| shows root nodes • |:InspectTree| now supports |folding| + • The `#set!` directive can set the "url" property of a node to have the + node emit a hyperlink. Hyperlinks are UI specific: in the TUI, the OSC 8 + control sequence is used. • |vim.ui.open()| opens URIs using the system default handler (macOS `open`, Windows `explorer`, Linux `xdg-open`, etc.) diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index 388680259a..cc5e11d632 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -4,7 +4,7 @@ local Range = require('vim.treesitter._range') local ns = api.nvim_create_namespace('treesitter/highlighter') ----@alias vim.treesitter.highlighter.Iter fun(end_line: integer|nil): integer, TSNode, vim.treesitter.query.TSMetadata +---@alias vim.treesitter.highlighter.Iter fun(): integer, table, vim.treesitter.query.TSMetadata ---@class (private) vim.treesitter.highlighter.Query ---@field private _query vim.treesitter.Query? @@ -248,6 +248,13 @@ end ---@param line integer ---@param is_spell_nav boolean local function on_line_impl(self, buf, line, is_spell_nav) + -- Track the maximum pattern index encountered in each tree. For subsequent + -- trees, the subpriority passed to nvim_buf_set_extmark is offset by the + -- largest pattern index from the prior tree. This ensures that extmarks + -- from subsequent trees always appear "on top of" extmarks from previous + -- trees (e.g. injections should always appear over base highlights). + local pattern_offset = 0 + self:for_each_highlight_state(function(state) local root_node = state.tstree:root() local root_start_row, _, root_end_row, _ = root_node:range() @@ -258,22 +265,24 @@ local function on_line_impl(self, buf, line, is_spell_nav) end if state.iter == nil or state.next_row < line then - state.iter = - state.highlighter_query:query():iter_captures(root_node, self.bufnr, line, root_end_row + 1) + state.iter = state.highlighter_query + :query() + :iter_matches(root_node, self.bufnr, line, root_end_row + 1, { all = true }) end + local max_pattern_index = -1 while line >= state.next_row do - local capture, node, metadata = state.iter(line) + local pattern, match, metadata = state.iter() - local range = { root_end_row + 1, 0, root_end_row + 1, 0 } - if node then - range = vim.treesitter.get_range(node, buf, metadata and metadata[capture]) + if pattern and pattern > max_pattern_index then + max_pattern_index = pattern end - local start_row, start_col, end_row, end_col = Range.unpack4(range) - if capture then - local hl = state.highlighter_query:get_hl_from_capture(capture) + if not match then + state.next_row = root_end_row + 1 + end + for capture, nodes in pairs(match or {}) do local capture_name = state.highlighter_query:query().captures[capture] local spell = nil ---@type boolean? if capture_name == 'spell' then @@ -282,28 +291,60 @@ local function on_line_impl(self, buf, line, is_spell_nav) spell = false end + local hl = state.highlighter_query:get_hl_from_capture(capture) + -- Give nospell a higher priority so it always overrides spell captures. local spell_pri_offset = capture_name == 'nospell' and 1 or 0 - if hl and end_row >= line and (not is_spell_nav or spell ~= nil) then - local priority = (tonumber(metadata.priority) or vim.highlight.priorities.treesitter) - + spell_pri_offset - api.nvim_buf_set_extmark(buf, ns, start_row, start_col, { - end_line = end_row, - end_col = end_col, - hl_group = hl, - ephemeral = true, - priority = priority, - conceal = metadata.conceal, - spell = spell, - }) + -- The "priority" attribute can be set at the pattern level or on a particular capture + local priority = ( + tonumber(metadata.priority or metadata[capture] and metadata[capture].priority) + or vim.highlight.priorities.treesitter + ) + spell_pri_offset + + local url = metadata[capture] and metadata[capture].url ---@type string|number|nil + if type(url) == 'number' then + if match and match[url] then + -- Assume there is only one matching node. If there is more than one, take the URL + -- from the first. + local other_node = match[url][1] + url = vim.treesitter.get_node_text(other_node, buf, { + metadata = metadata[url], + }) + else + url = nil + end end - end - if start_row > line then - state.next_row = start_row + -- The "conceal" attribute can be set at the pattern level or on a particular capture + local conceal = metadata.conceal or metadata[capture] and metadata[capture].conceal + + for _, node in ipairs(nodes) do + local range = vim.treesitter.get_range(node, buf, metadata[capture]) + local start_row, start_col, end_row, end_col = Range.unpack4(range) + + if hl and end_row >= line and (not is_spell_nav or spell ~= nil) then + api.nvim_buf_set_extmark(buf, ns, start_row, start_col, { + end_line = end_row, + end_col = end_col, + hl_group = hl, + ephemeral = true, + priority = priority, + _subpriority = pattern_offset + pattern, + conceal = conceal, + spell = spell, + url = url, + }) + end + + if start_row > line then + state.next_row = start_row + end + end end end + + pattern_offset = pattern_offset + max_pattern_index end) end diff --git a/runtime/queries/markdown_inline/highlights.scm b/runtime/queries/markdown_inline/highlights.scm index e9b41c31d5..5f3519777f 100644 --- a/runtime/queries/markdown_inline/highlights.scm +++ b/runtime/queries/markdown_inline/highlights.scm @@ -33,6 +33,11 @@ ] @markup.link (#set! conceal "")) +(inline_link + (link_text) @markup.link.label + (link_destination) @markup.link + (#set! @markup.link.label "url" @markup.link)) + ; Conceal image links (image [ diff --git a/runtime/queries/vimdoc/highlights.scm b/runtime/queries/vimdoc/highlights.scm index 294fa94f10..0c10b3c0b3 100644 --- a/runtime/queries/vimdoc/highlights.scm +++ b/runtime/queries/vimdoc/highlights.scm @@ -12,21 +12,30 @@ (tag "*" @markup.heading.5.marker - (#set! conceal "") - text: (_) @label) + . + text: (_) @label + . + "*" @markup.heading.5.marker + (#set! @markup.heading.5.marker conceal "")) (taglink - "|" @markup.link - (#set! conceal "") - text: (_) @markup.link) + "|" @markup.link.delimiter + . + text: (_) @markup.link + . + "|" @markup.link.delimiter + (#set! @markup.link.delimiter conceal "")) (optionlink text: (_) @markup.link) (codespan "`" @markup.raw.delimiter - (#set! conceal "") - text: (_) @markup.raw) + . + text: (_) @markup.raw + . + "`" @markup.raw.delimiter + (#set! @markup.raw.delimiter conceal "")) ((codeblock) @markup.raw.block (#set! "priority" 90)) diff --git a/test/functional/treesitter/highlight_spec.lua b/test/functional/treesitter/highlight_spec.lua index 2bf230fe69..7f2b5751ae 100644 --- a/test/functional/treesitter/highlight_spec.lua +++ b/test/functional/treesitter/highlight_spec.lua @@ -681,6 +681,12 @@ describe('treesitter highlighting (C)', function() ((identifier) @Identifier (#set! conceal "") (#eq? @Identifier "lstate")) + + ((call_expression + function: (identifier) @function + arguments: (argument_list) @arguments) + (#eq? @function "multiqueue_put") + (#set! @function conceal "V")) ]]}}) ]=] @@ -697,7 +703,7 @@ describe('treesitter highlighting (C)', function() | LuaRef cb = nlua_ref(, 1); | | - multiqueue_put(main_loop.events, nlua_schedule_event, | + {11:V}(main_loop.events, nlua_schedule_event, | 1, (void *)(ptrdiff_t)cb); | return 0; | ^} | @@ -758,6 +764,44 @@ describe('treesitter highlighting (C)', function() end) end) +describe('treesitter highlighting (lua)', function() + local screen + + before_each(function() + screen = Screen.new(65, 18) + screen:attach() + screen:set_default_attr_ids { + [1] = { bold = true, foreground = Screen.colors.Blue }, + [2] = { foreground = Screen.colors.DarkCyan }, + [3] = { foreground = Screen.colors.Magenta }, + [4] = { foreground = Screen.colors.SlateBlue }, + [5] = { bold = true, foreground = Screen.colors.Brown }, + } + end) + + it('supports language injections', function() + insert [[ + local ffi = require('ffi') + ffi.cdef("int (*fun)(int, char *);") + ]] + + exec_lua [[ + vim.bo.filetype = 'lua' + vim.treesitter.start() + ]] + + screen:expect { + grid = [[ + {5:local} {2:ffi} {5:=} {4:require(}{3:'ffi'}{4:)} | + {2:ffi}{4:.}{2:cdef}{4:(}{3:"}{4:int}{3: }{4:(}{5:*}{3:fun}{4:)(int,}{3: }{4:char}{3: }{5:*}{4:);}{3:"}{4:)} | + ^ | + {1:~ }|*14 + | + ]], + } + end) +end) + describe('treesitter highlighting (help)', function() local screen @@ -891,3 +935,46 @@ vim.cmd([[ } end) end) + +describe('treesitter highlighting (markdown)', function() + local screen + + before_each(function() + screen = Screen.new(40, 6) + screen:attach() + screen:set_default_attr_ids { + [1] = { foreground = Screen.colors.Blue1 }, + [2] = { bold = true, foreground = Screen.colors.Blue1 }, + [3] = { bold = true, foreground = Screen.colors.Brown }, + [4] = { foreground = Screen.colors.Cyan4 }, + [5] = { foreground = Screen.colors.Magenta1 }, + } + end) + + it('supports hyperlinks', function() + local url = 'https://example.com' + insert(string.format('[This link text](%s) is a hyperlink.', url)) + exec_lua([[ + vim.bo.filetype = 'markdown' + vim.treesitter.start() + ]]) + + screen:expect { + grid = [[ + {4:[}{6:This link text}{4:](}{7:https://example.com}{4:)} is| + a hyperlink^. | + {2:~ }|*3 + | + ]], + attr_ids = { + [1] = { foreground = Screen.colors.Blue1 }, + [2] = { bold = true, foreground = Screen.colors.Blue1 }, + [3] = { bold = true, foreground = Screen.colors.Brown }, + [4] = { foreground = Screen.colors.Cyan4 }, + [5] = { foreground = Screen.colors.Magenta }, + [6] = { foreground = Screen.colors.Cyan4, url = url }, + [7] = { underline = true, foreground = Screen.colors.SlateBlue }, + }, + } + end) +end) -- cgit From 119390e7ce3fe4f4f3da8bdd963ea10ec8976a3a Mon Sep 17 00:00:00 2001 From: James <89495599+IAKOBVS@users.noreply.github.com> Date: Wed, 13 Mar 2024 03:34:59 +0700 Subject: refactor: avoid copying before vim_strup() if possible (#27830) Current uses of vim_strup() calls memcpy()/strcpy() before calling vim_strup(). This results in 2 * strlen(string) operations. We can trivially convert to lowercase while copying the string instead. --- src/nvim/highlight_group.c | 8 +++----- src/nvim/strings.c | 41 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c index 75c23c5bc4..1474a2ba06 100644 --- a/src/nvim/highlight_group.c +++ b/src/nvim/highlight_group.c @@ -1149,9 +1149,8 @@ void do_highlight(const char *line, const bool forceit, const bool init) error = true; break; } - memcpy(key, key_start, key_len); - key[key_len] = NUL; - vim_strup(key); + vim_memcpy_up(key, key_start, key_len); + key[key_len] = '\0'; linep = skipwhite(linep); if (strcmp(key, "NONE") == 0) { @@ -1943,9 +1942,8 @@ int syn_name2id_len(const char *name, size_t len) // Avoid using stricmp() too much, it's slow on some systems */ // Avoid alloc()/free(), these are slow too. - memcpy(name_u, name, len); + vim_memcpy_up(name_u, name, len); name_u[len] = '\0'; - vim_strup(name_u); // map_get(..., int) returns 0 when no key is present, which is // the expected value for missing highlight group. diff --git a/src/nvim/strings.c b/src/nvim/strings.c index 01bd610292..f9b945f1da 100644 --- a/src/nvim/strings.c +++ b/src/nvim/strings.c @@ -299,8 +299,8 @@ char *vim_strsave_shellescape(const char *string, bool do_special, bool do_newli char *vim_strsave_up(const char *string) FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL { - char *p1 = xstrdup(string); - vim_strup(p1); + char *p1 = xmalloc(strlen(string) + 1); + vim_strcpy_up(p1, string); return p1; } @@ -309,8 +309,8 @@ char *vim_strsave_up(const char *string) char *vim_strnsave_up(const char *string, size_t len) FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL { - char *p1 = xstrnsave(string, len); - vim_strup(p1); + char *p1 = xmalloc(len + 1); + vim_strncpy_up(p1, string, len); return p1; } @@ -324,6 +324,39 @@ void vim_strup(char *p) } } +// strcpy plus vim_strup. +void vim_strcpy_up(char *restrict dst, const char *restrict src) + FUNC_ATTR_NONNULL_ALL +{ + uint8_t c; + while ((c = (uint8_t)(*src++)) != NUL) { + *dst++ = (char)(uint8_t)(c < 'a' || c > 'z' ? c : c - 0x20); + } + *dst = '\0'; +} + +// strncpy (NUL-terminated) plus vim_strup. +void vim_strncpy_up(char *restrict dst, const char *restrict src, size_t n) + FUNC_ATTR_NONNULL_ALL +{ + uint8_t c; + while (n-- && (c = (uint8_t)(*src++)) != NUL) { + *dst++ = (char)(uint8_t)(c < 'a' || c > 'z' ? c : c - 0x20); + } + *dst = '\0'; +} + +// memcpy (does not NUL-terminate) plus vim_strup. +void vim_memcpy_up(char *restrict dst, const char *restrict src, size_t n) + FUNC_ATTR_NONNULL_ALL +{ + uint8_t c; + while (n--) { + c = (uint8_t)(*src++); + *dst++ = (char)(uint8_t)(c < 'a' || c > 'z' ? c : c - 0x20); + } +} + /// Make given string all upper-case or all lower-case /// /// Handles multi-byte characters as good as possible. -- cgit From 29d0ed577c7d283402c84df602a031a25349eb59 Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Tue, 12 Mar 2024 21:02:29 +0000 Subject: vim-patch:9.1.0169: current window number returned by tabpagewinnr may be outdated Problem: current window number returned by tabpagewinnr may be outdated when called from win_execute for the original tabpage. Solution: update the original tabpage's tp_curwin in switch_win; use {un}use_tabpage instead. Don't do it in restore_win to ensure tp_curwin of the temporarily visited tabpage is unchanged from switch_win visiting it, as before. (Sean Dewar) Maybe restore_win should only restore tp_curwin if `curtab == switchwin->sw_curtab`, in case the user changed tabpages from within win_execute, but not doing that is consistent with the old behaviour. related: vim/vim#14186 https://github.com/vim/vim/commit/e101028a5c896480c61fef7ea16855255925709b --- src/nvim/eval/window.c | 22 ++++++++-------------- test/old/testdir/test_execute_func.vim | 24 ++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/nvim/eval/window.c b/src/nvim/eval/window.c index e54f46dcc3..26624c8dd7 100644 --- a/src/nvim/eval/window.c +++ b/src/nvim/eval/window.c @@ -956,13 +956,8 @@ int switch_win_noblock(switchwin_T *switchwin, win_T *win, tabpage_T *tp, bool n if (tp != NULL) { switchwin->sw_curtab = curtab; if (no_display) { - curtab->tp_firstwin = firstwin; - curtab->tp_lastwin = lastwin; - curtab->tp_topframe = topframe; - curtab = tp; - firstwin = curtab->tp_firstwin; - lastwin = curtab->tp_lastwin; - topframe = curtab->tp_topframe; + unuse_tabpage(curtab); + use_tabpage(tp); } else { goto_tabpage_tp(tp, false, false); } @@ -989,13 +984,12 @@ void restore_win_noblock(switchwin_T *switchwin, bool no_display) { if (switchwin->sw_curtab != NULL && valid_tabpage(switchwin->sw_curtab)) { if (no_display) { - curtab->tp_firstwin = firstwin; - curtab->tp_lastwin = lastwin; - curtab->tp_topframe = topframe; - curtab = switchwin->sw_curtab; - firstwin = curtab->tp_firstwin; - lastwin = curtab->tp_lastwin; - topframe = curtab->tp_topframe; + win_T *const old_tp_curwin = curtab->tp_curwin; + + unuse_tabpage(curtab); + // Don't change the curwin of the tabpage we temporarily visited. + curtab->tp_curwin = old_tp_curwin; + use_tabpage(switchwin->sw_curtab); } else { goto_tabpage_tp(switchwin->sw_curtab, false, false); } diff --git a/test/old/testdir/test_execute_func.vim b/test/old/testdir/test_execute_func.vim index d9909e92a6..ec8ed160c3 100644 --- a/test/old/testdir/test_execute_func.vim +++ b/test/old/testdir/test_execute_func.vim @@ -212,4 +212,28 @@ func Test_execute_cmd_with_null() endif endfunc +func Test_win_execute_tabpagewinnr() + belowright split + tab split + belowright split + call assert_equal(2, tabpagewinnr(1)) + + tabprevious + wincmd p + call assert_equal(1, tabpagenr()) + call assert_equal(1, tabpagewinnr(1)) + call assert_equal(2, tabpagewinnr(2)) + + call win_execute(win_getid(1, 2), + \ 'call assert_equal(2, tabpagenr())' + \ .. '| call assert_equal(1, tabpagewinnr(1))' + \ .. '| call assert_equal(1, tabpagewinnr(2))') + + call assert_equal(1, tabpagenr()) + call assert_equal(1, tabpagewinnr(1)) + call assert_equal(2, tabpagewinnr(2)) + + %bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab -- cgit From dc7ccd6bca81dfa6ade6462a6e30770c63d48266 Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Tue, 12 Mar 2024 16:13:40 -0500 Subject: fix(treesitter): use 0 as initial value for computing maximum (#27837) Using -1 as the initial value can cause the pattern offset to become negative, which in turn results in a negative subpriority, which fails validation in nvim_buf_set_extmark. --- runtime/lua/vim/treesitter/highlighter.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index cc5e11d632..cbab5e990e 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -270,7 +270,7 @@ local function on_line_impl(self, buf, line, is_spell_nav) :iter_matches(root_node, self.bufnr, line, root_end_row + 1, { all = true }) end - local max_pattern_index = -1 + local max_pattern_index = 0 while line >= state.next_row do local pattern, match, metadata = state.iter() -- cgit From ca7b603d02ecd1ed4098f487cd01acd470ca6a74 Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Tue, 12 Mar 2024 21:08:50 +0000 Subject: vim-patch:9.1.0170: Re-allow curwin == prevwin, but document it instead Problem: more places exist where curwin == prevwin, and it may even be expected in some cases. Solution: revert v9.1.0001, but document that it's possible instead. (Sean Dewar) I've had a change of heart for the following reasons: - A quick 'n dirty [GitHub code search](https://github.com/search?q=%2F%28winnr%5C%28%5C%29%5Cs*%3D%3D%5Cs*winnr%5C%28%5B%27%22%5D%23%5B%27%22%5D%5C%29%7Cwinnr%5C%28%5B%27%22%5D%23%5B%27%22%5D%5C%29%5Cs*%3D%3D%5Cs*winnr%5C%28%5C%29%29%2F&type=code) reveals some cases where it's expected in the wild. Particularly, it made me aware `winnr() == winnr('#')` is possible when curwin is changed temporarily during the evaluation of a &statusline expression item (`%{...}`), and is used to show something different on the statusline belonging to the previous window; that behaviour wasn't changed in v9.1.0001, but it means curwin == prevwin makes sense in some cases. - The definition and call sites of back_to_prevwin imply some expectation that prevwin == wp (== curwin) is possible, as it's used to skip entering the prevwin in that case. - Prior to v9.1.0001, `:wincmd p` would not beep in the case that was patched in v9.1.0001, but now does. That resulted in vim/vim#14047 being opened, as it affected the CtrlP plugin. I find it odd that `:wincmd p` had cases where it wouldn't beep despite doing nothing, but it may be preferable to keep things that way (or instead also beep if curwin == prevwin, if that's preferred). - After more digging, I found cases in win_free_mem, enter_tabpage, aucmd_restbuf and qf_open_new_cwindow where curwin == prevwin is possible (many of them from autocommands). Others probably exist too, especially in places where curwin is changed temporarily. fixes: vim/vim#14047 closes: vim/vim#14186 https://github.com/vim/vim/commit/d64801e913314d2e19dbb38f60e6d285238debff --- runtime/doc/builtin.txt | 4 ++- runtime/lua/vim/_meta/vimfn.lua | 4 ++- src/nvim/eval.lua | 4 ++- src/nvim/globals.h | 2 +- src/nvim/window.c | 4 --- test/old/testdir/test_window_cmd.vim | 58 ------------------------------------ 6 files changed, 10 insertions(+), 66 deletions(-) diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index 4b1ccc0c5c..1a7efc3d79 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -9007,7 +9007,9 @@ winnr([{arg}]) *winnr()* # the number of the last accessed window (where |CTRL-W_p| goes to). If there is no previous window or it is in another tab page 0 is - returned. + returned. May refer to the current window in + some cases (e.g. when evaluating 'statusline' + expressions). {N}j the number of the Nth window below the current window (where |CTRL-W_j| goes to). {N}k the number of the Nth window above the current diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index ee68f669f8..a51f89227b 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -10724,7 +10724,9 @@ function vim.fn.winline() end --- # the number of the last accessed window (where --- |CTRL-W_p| goes to). If there is no previous --- window or it is in another tab page 0 is ---- returned. +--- returned. May refer to the current window in +--- some cases (e.g. when evaluating 'statusline' +--- expressions). --- {N}j the number of the Nth window below the --- current window (where |CTRL-W_j| goes to). --- {N}k the number of the Nth window above the current diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index febd022254..73715e2631 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -12856,7 +12856,9 @@ M.funcs = { # the number of the last accessed window (where |CTRL-W_p| goes to). If there is no previous window or it is in another tab page 0 is - returned. + returned. May refer to the current window in + some cases (e.g. when evaluating 'statusline' + expressions). {N}j the number of the Nth window below the current window (where |CTRL-W_j| goes to). {N}k the number of the Nth window above the current diff --git a/src/nvim/globals.h b/src/nvim/globals.h index aecb9d1116..06fb95b577 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -363,7 +363,7 @@ EXTERN bool sys_menu INIT( = false); // currently active window. EXTERN win_T *firstwin; // first window EXTERN win_T *lastwin; // last window -EXTERN win_T *prevwin INIT( = NULL); // previous window +EXTERN win_T *prevwin INIT( = NULL); // previous window (may equal curwin) #define ONE_WINDOW (firstwin == lastwin) #define FOR_ALL_FRAMES(frp, first_frame) \ for ((frp) = first_frame; (frp) != NULL; (frp) = (frp)->fr_next) diff --git a/src/nvim/window.c b/src/nvim/window.c index 521699f2f0..843cf154a7 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -4915,14 +4915,10 @@ static void win_enter_ext(win_T *const wp, const int flags) if (wp->w_buffer != curbuf) { buf_copy_options(wp->w_buffer, BCO_ENTER | BCO_NOHELP); } - if (!curwin_invalid) { prevwin = curwin; // remember for CTRL-W p curwin->w_redr_status = true; - } else if (wp == prevwin) { - prevwin = NULL; // don't want it to be the new curwin } - curwin = wp; curbuf = wp->w_buffer; diff --git a/test/old/testdir/test_window_cmd.vim b/test/old/testdir/test_window_cmd.vim index 8898d3a02d..9c34c15ff8 100644 --- a/test/old/testdir/test_window_cmd.vim +++ b/test/old/testdir/test_window_cmd.vim @@ -113,64 +113,6 @@ func Test_window_quit() bw Xa Xb endfunc -func Test_window_curwin_not_prevwin() - botright split - call assert_equal(2, winnr()) - call assert_equal(1, winnr('#')) - quit - call assert_equal(1, winnr()) - call assert_equal(0, winnr('#')) - - botright split - botright split - call assert_equal(3, winnr()) - call assert_equal(2, winnr('#')) - 1quit - call assert_equal(2, winnr()) - call assert_equal(1, winnr('#')) - - botright split - call assert_equal(1, tabpagenr()) - call assert_equal(3, winnr()) - call assert_equal(2, winnr('#')) - wincmd T - call assert_equal(2, tabpagenr()) - call assert_equal(1, winnr()) - call assert_equal(0, winnr('#')) - tabfirst - call assert_equal(1, tabpagenr()) - call assert_equal(2, winnr()) - call assert_equal(0, winnr('#')) - - tabonly - botright split - wincmd t - wincmd p - call assert_equal(3, winnr()) - call assert_equal(1, winnr('#')) - quit - call assert_equal(2, winnr()) - call assert_equal(1, winnr('#')) - - botright split - wincmd t - wincmd p - call assert_equal(1, tabpagenr()) - call assert_equal(3, winnr()) - call assert_equal(1, winnr('#')) - wincmd T - call assert_equal(2, tabpagenr()) - call assert_equal(1, winnr()) - call assert_equal(0, winnr('#')) - tabfirst - call assert_equal(1, tabpagenr()) - call assert_equal(2, winnr()) - call assert_equal(1, winnr('#')) - - tabonly - only -endfunc - func Test_window_horizontal_split() call assert_equal(1, winnr('$')) 3wincmd s -- cgit From 6bbb02d9ba76551dd4856ad50a237e92c678702d Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Tue, 12 Mar 2024 21:19:18 +0000 Subject: vim-patch:9.1.0171: Small split-move related improvements Problem: small improvements can be made to split-move related functions. Solution: apply them (Sean Dewar): Some of these changes were already applied to Nvim. Here are the ones which were missing: - Improve some doc comments (frame_flatten should still work for non-current tabpages, despite the topframe check, which looks benign, though I'm unsure if it's still needed; see vim/vim#2467). - f_win_splitmove should check_split_disallowed on wp, not targetwin, as that's what win_splitmove checks (though it's probably unnecessary to check b_locked_split at all; see vim/vim#14109, which I hope to get around to finishing at some point). - Apply the winframe_restore comment changes, and remove win_comp_pos from after winframe_restore in win_splitmove, as it shouldn't be necessary (no need to remove it from nvim_win_set_config too, as it was already omitted). Move win_append after winframe_restore in win_splitmove to match Vim. closes: vim/vim#14185 https://github.com/vim/vim/commit/5cac1a9bee0798d70a7fd80363a1f697759638e8 --- src/nvim/eval/window.c | 4 ++-- src/nvim/window.c | 9 ++++---- test/old/testdir/test_window_cmd.vim | 41 ++++++++++++++++++++++-------------- 3 files changed, 31 insertions(+), 23 deletions(-) diff --git a/src/nvim/eval/window.c b/src/nvim/eval/window.c index 26624c8dd7..3e2f6301ca 100644 --- a/src/nvim/eval/window.c +++ b/src/nvim/eval/window.c @@ -701,8 +701,8 @@ void f_win_splitmove(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) size = (int)tv_dict_get_number(d, "size"); } - // Check if we can split the target before we bother switching windows. - if (is_aucmd_win(wp) || text_or_buf_locked() || check_split_disallowed(targetwin) == FAIL) { + // Check if we're allowed to continue before we bother switching windows. + if (is_aucmd_win(wp) || text_or_buf_locked() || check_split_disallowed(wp) == FAIL) { return; } diff --git a/src/nvim/window.c b/src/nvim/window.c index 843cf154a7..9468207d41 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -939,7 +939,7 @@ void ui_ext_win_viewport(win_T *wp) } } -/// If "split_disallowed" is set or "wp"s buffer is closing, give an error and return FAIL. +/// If "split_disallowed" is set, or "wp"'s buffer is closing, give an error and return FAIL. /// Otherwise return OK. int check_split_disallowed(const win_T *wp) FUNC_ATTR_NONNULL_ALL @@ -1966,13 +1966,12 @@ int win_splitmove(win_T *wp, int size, int flags) // Split a window on the desired side and put "wp" there. if (win_split_ins(size, flags, wp, dir, unflat_altfr) == NULL) { - win_append(wp->w_prev, wp, NULL); if (!wp->w_floating) { // win_split_ins doesn't change sizes or layout if it fails to insert an // existing window, so just undo winframe_remove. winframe_restore(wp, dir, unflat_altfr); - win_comp_pos(); // recompute window positions } + win_append(wp->w_prev, wp, NULL); return FAIL; } @@ -3271,7 +3270,6 @@ win_T *winframe_find_altwin(win_T *win, int *dirp, tabpage_T *tp, frame_T **altf /// Flatten "frp" into its parent frame if it's the only child, also merging its /// list with the grandparent if they share the same layout. /// Frees "frp" if flattened; also "frp->fr_parent" if it has the same layout. -/// "frp" must be valid in the current tabpage. static void frame_flatten(frame_T *frp) FUNC_ATTR_NONNULL_ALL { @@ -3359,7 +3357,8 @@ void winframe_restore(win_T *wp, int dir, frame_T *unflat_altfr) int row = wp->w_winrow; int col = wp->w_wincol; - // Restore the lost room that was redistributed to the altframe. + // Restore the lost room that was redistributed to the altframe. Also + // adjusts window sizes to fit restored statuslines/separators, if needed. if (dir == 'v') { frame_new_height(unflat_altfr, unflat_altfr->fr_height - frp->fr_height, unflat_altfr == frp->fr_next, false); diff --git a/test/old/testdir/test_window_cmd.vim b/test/old/testdir/test_window_cmd.vim index 9c34c15ff8..77de5edf8f 100644 --- a/test/old/testdir/test_window_cmd.vim +++ b/test/old/testdir/test_window_cmd.vim @@ -200,6 +200,20 @@ func Test_window_split_edit_bufnr() %bw! endfunc +func s:win_layout_info() abort + return #{ + \ layout: winlayout(), + \ pos_sizes: range(1, winnr('$')) + \ ->map({_, nr -> win_getid(nr)->getwininfo()[0]}) + \ ->map({_, wininfo -> #{id: wininfo.winid, + \ row: wininfo.winrow, + \ col: wininfo.wincol, + \ width: wininfo.width, + \ height: wininfo.height}}) + \ ->sort({a, b -> a.id - b.id}) + \ } +endfunc + func Test_window_split_no_room() " N horizontal windows need >= 2*N + 1 lines: " - 1 line + 1 status line in each window @@ -216,12 +230,10 @@ func Test_window_split_no_room() botright vsplit wincmd | - let layout = winlayout() - let restcmd = winrestcmd() + let info = s:win_layout_info() call assert_fails('wincmd J', 'E36:') call assert_fails('wincmd K', 'E36:') - call assert_equal(layout, winlayout()) - call assert_equal(restcmd, winrestcmd()) + call assert_equal(info, s:win_layout_info()) only " N vertical windows need >= 2*(N - 1) + 1 columns: @@ -238,18 +250,18 @@ func Test_window_split_no_room() split wincmd | - let layout = winlayout() - let restcmd = winrestcmd() + let info = s:win_layout_info() call assert_fails('wincmd H', 'E36:') call assert_fails('wincmd L', 'E36:') - call assert_equal(layout, winlayout()) - call assert_equal(restcmd, winrestcmd()) + call assert_equal(info, s:win_layout_info()) " Check that the last statusline isn't lost. " Set its window's width to 2 for the test. wincmd j set laststatus=0 winminwidth=0 vertical resize 2 + " Update expected positions/sizes after the resize. Layout is unchanged. + let info.pos_sizes = s:win_layout_info().pos_sizes set winminwidth& call setwinvar(winnr('k'), '&statusline', '@#') let last_stl_row = win_screenpos(0)[0] - 1 @@ -257,11 +269,9 @@ func Test_window_split_no_room() call assert_equal('@#|', GetScreenStr(last_stl_row)) call assert_equal('~ |', GetScreenStr(&lines - &cmdheight)) - let restcmd = winrestcmd() call assert_fails('wincmd H', 'E36:') call assert_fails('wincmd L', 'E36:') - call assert_equal(layout, winlayout()) - call assert_equal(restcmd, winrestcmd()) + call assert_equal(info, s:win_layout_info()) call setwinvar(winnr('k'), '&statusline', '=-') redraw call assert_equal('=-|', GetScreenStr(last_stl_row)) @@ -1076,6 +1086,7 @@ func Test_win_splitmove() augroup WinSplitMove au! au WinEnter * ++once let s:triggered = v:true + \| call assert_fails('call win_splitmove(winnr(), winnr("$"))', 'E242:') \| call assert_fails('call win_splitmove(winnr("$"), winnr())', 'E242:') augroup END quit @@ -1086,7 +1097,7 @@ func Test_win_splitmove() augroup WinSplitMove au! au BufHidden * ++once let s:triggered = v:true - \| call assert_fails('call win_splitmove(winnr("#"), winnr())', 'E1159:') + \| call assert_fails('call win_splitmove(winnr(), winnr("#"))', 'E1159:') augroup END hide call assert_equal(v:true, s:triggered) @@ -2088,13 +2099,11 @@ func Test_autocmd_window_force_room() au BufEnter * ++once let s:triggered = v:true \| call assert_equal('autocmd', win_gettype()) augroup END - let layout = winlayout() - let restcmd = winrestcmd() + let info = s:win_layout_info() " bufload opening the autocommand window shouldn't give E36. call bufload('unload me') call assert_equal(v:true, s:triggered) - call assert_equal(winlayout(), layout) - call assert_equal(winrestcmd(), restcmd) + call assert_equal(info, s:win_layout_info()) unlet! s:triggered au! AucmdWinForceRoom -- cgit From c048beef6c034a46e324fcea7210082d48db32ee Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Tue, 12 Mar 2024 21:41:35 +0000 Subject: vim-patch:9a660d2883f9 runtime(doc): add reference to matchbufline() at :h search() related: vim/vim#14173 https://github.com/vim/vim/commit/9a660d2883f92b3a3761c964dc14363a8f70c8d8 Co-authored-by: Christian Brabandt --- runtime/doc/builtin.txt | 1 + runtime/lua/vim/_meta/vimfn.lua | 1 + src/nvim/eval.lua | 1 + 3 files changed, 3 insertions(+) diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index 1a7efc3d79..3df24a3b5f 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -6054,6 +6054,7 @@ search({pattern} [, {flags} [, {stopline} [, {timeout} [, {skip}]]]]) *search()* When a match has been found its line number is returned. If there is no match a 0 is returned and the cursor doesn't move. No error message is given. + To get the matched string, use |matchbufline()|. {flags} is a String, which can contain these character flags: 'b' search Backward instead of forward diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index a51f89227b..2b93ea7d4e 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -7259,6 +7259,7 @@ function vim.fn.screenstring(row, col) end --- When a match has been found its line number is returned. --- If there is no match a 0 is returned and the cursor doesn't --- move. No error message is given. +--- To get the matched string, use |matchbufline()|. --- --- {flags} is a String, which can contain these character flags: --- 'b' search Backward instead of forward diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 73715e2631..96dc32259f 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -8746,6 +8746,7 @@ M.funcs = { When a match has been found its line number is returned. If there is no match a 0 is returned and the cursor doesn't move. No error message is given. + To get the matched string, use |matchbufline()|. {flags} is a String, which can contain these character flags: 'b' search Backward instead of forward -- cgit From 93c93a0e3646a205013f439013e22d674b224cdb Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Wed, 13 Mar 2024 11:27:04 +0800 Subject: refactor: remove "once" argument of loop_uv_run() (#27841) It is always set to true when used, and makes the code a bit confusing. --- src/nvim/event/loop.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/nvim/event/loop.c b/src/nvim/event/loop.c index 93948d3eaa..e1ebcecbd6 100644 --- a/src/nvim/event/loop.c +++ b/src/nvim/event/loop.c @@ -39,10 +39,8 @@ void loop_init(Loop *loop, void *data) /// @param ms 0: non-blocking poll. /// > 0: timeout after `ms`. /// < 0: wait forever. -/// @param once true: process at most one `Loop.uv` event. -/// false: process until `ms` timeout (only has effect if `ms` > 0). /// @return true if `ms` > 0 and was reached -bool loop_uv_run(Loop *loop, int64_t ms, bool once) +static bool loop_uv_run(Loop *loop, int64_t ms) { if (loop->recursive++) { abort(); // Should not re-enter uv_run @@ -60,9 +58,7 @@ bool loop_uv_run(Loop *loop, int64_t ms, bool once) mode = UV_RUN_NOWAIT; } - do { - uv_run(&loop->uv, mode); - } while (ms > 0 && !once && !*timeout_expired); + uv_run(&loop->uv, mode); if (ms > 0) { uv_timer_stop(&loop->poll_timer); @@ -83,7 +79,7 @@ bool loop_uv_run(Loop *loop, int64_t ms, bool once) /// @return true if `ms` > 0 and was reached bool loop_poll_events(Loop *loop, int64_t ms) { - bool timeout_expired = loop_uv_run(loop, ms, true); + bool timeout_expired = loop_uv_run(loop, ms); multiqueue_process_events(loop->fast_events); return timeout_expired; } -- cgit From d5488633f68fcfd58b4bcad654ab103b4746204b Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Wed, 13 Mar 2024 11:36:41 +0800 Subject: fix(drawline): initialize linebuf_attr to 0 instead of -1 (#27840) This also obviates the end-of-line loop when there is virtual text. --- src/nvim/drawline.c | 22 +++++++++------------- test/functional/ui/decorations_spec.lua | 15 ++++++++++++++- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index 4281cdff33..c5f6ce2e36 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -288,26 +288,23 @@ static void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int if (item->draw_col < 0) { continue; } - int col = 0; if (item->kind == kDecorKindUIWatched) { // send mark position to UI - col = item->draw_col; - WinExtmark m = { (NS)item->data.ui.ns_id, item->data.ui.mark_id, win_row, col }; + WinExtmark m = { (NS)item->data.ui.ns_id, item->data.ui.mark_id, win_row, item->draw_col }; kv_push(win_extmark_arr, m); } if (vt) { int vcol = item->draw_col - col_off; - col = draw_virt_text_item(buf, item->draw_col, vt->data.virt_text, - vt->hl_mode, max_col, vcol); + int col = draw_virt_text_item(buf, item->draw_col, vt->data.virt_text, + vt->hl_mode, max_col, vcol); if (vt->pos == kVPosEndOfLine && do_eol) { state->eol_col = col + 1; } + *end_col = MAX(*end_col, col); } if (!vt || !(vt->flags & kVTRepeatLinebreak)) { item->draw_col = INT_MIN; // deactivate } - - *end_col = MAX(*end_col, col); } } @@ -898,7 +895,7 @@ static void win_line_start(win_T *wp, winlinevars_T *wlv) wlv->need_lbr = false; for (int i = 0; i < wp->w_grid.cols; i++) { linebuf_char[i] = schar_from_ascii(' '); - linebuf_attr[i] = -1; + linebuf_attr[i] = 0; linebuf_vcol[i] = -1; } } @@ -2569,13 +2566,12 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s advance_color_col(&wlv, vcol_hlc(wlv)); - bool has_virttext = false; // Make sure alignment is the same regardless // if listchars=eol:X is used or not. const int eol_skip = (lcs_eol_todo && eol_hl_off == 0 ? 1 : 0); if (has_decor) { - has_virttext = decor_redraw_eol(wp, &decor_state, &wlv.line_attr, wlv.col + eol_skip); + decor_redraw_eol(wp, &decor_state, &wlv.line_attr, wlv.col + eol_skip); } if (((wp->w_p_cuc @@ -2583,7 +2579,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s && wp->w_virtcol < grid->cols * (ptrdiff_t)(wlv.row - startrow + 1) + start_col && lnum != wp->w_cursor.lnum) || wlv.color_cols || wlv.line_attr_lowprio || wlv.line_attr - || wlv.diff_hlf != 0 || has_virttext)) { + || wlv.diff_hlf != 0)) { int rightmost_vcol = get_rightmost_vcol(wp, wlv.color_cols); const int cuc_attr = win_hl_attr(wp, HLF_CUC); const int mc_attr = win_hl_attr(wp, HLF_MC); @@ -2597,7 +2593,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s : 0; const int base_attr = hl_combine_attr(wlv.line_attr_lowprio, diff_attr); - if (base_attr || wlv.line_attr || has_virttext) { + if (base_attr || wlv.line_attr) { rightmost_vcol = INT_MAX; } @@ -2624,7 +2620,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s break; } - wlv.vcol += 1; + wlv.vcol++; } } diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua index e57e719192..98d221be7d 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -2301,8 +2301,21 @@ describe('extmark decorations', function() ]]} end) + it('virtual text does not crash with blend, conceal and wrap #27836', function() + screen:try_resize(50, 3) + insert(('a'):rep(45) .. '|hidden|' .. ('b'):rep(45)) + command('syntax match test /|hidden|/ conceal') + command('set conceallevel=2 concealcursor=n') + api.nvim_buf_set_extmark(0, ns, 0, 0, {virt_text = {{'FOO'}}, virt_text_pos='right_align', hl_mode='blend'}) + screen:expect{grid=[[ + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa FOO| + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb^b | + | + ]]} + end) + it('works with both hl_group and sign_hl_group', function() - screen:try_resize(screen._width, 3) + screen:try_resize(50, 3) insert('abcdefghijklmn') api.nvim_buf_set_extmark(0, ns, 0, 0, {sign_text='S', sign_hl_group='NonText', hl_group='Error', end_col=14}) screen:expect{grid=[[ -- cgit From 08fc1ebbaa49e3110b65bddeed28d2e61a96f5d9 Mon Sep 17 00:00:00 2001 From: bfredl Date: Mon, 11 Mar 2024 13:19:49 +0100 Subject: fix(api/buffer): fix handling of viewport of non-current buffer A lot of functions in move.c only worked for curwin, alternatively took a `wp` arg but still only work if that happens to be curwin. Refactor those that are needed for update_topline(wp) to work for any window. fixes #27723 fixes #27720 --- src/nvim/api/buffer.c | 11 +- src/nvim/api/extmark.c | 4 +- src/nvim/api/window.c | 4 +- src/nvim/autocmd.c | 6 +- src/nvim/buffer.c | 8 +- src/nvim/change.c | 6 +- src/nvim/cursor.c | 87 ++-- src/nvim/decoration.c | 4 +- src/nvim/diff.c | 13 +- src/nvim/drawline.c | 6 +- src/nvim/drawscreen.c | 14 +- src/nvim/edit.c | 70 ++-- src/nvim/eval/buffer.c | 6 +- src/nvim/eval/funcs.c | 16 +- src/nvim/eval/window.c | 12 +- src/nvim/ex_cmds.c | 12 +- src/nvim/ex_cmds2.c | 2 +- src/nvim/ex_docmd.c | 54 ++- src/nvim/ex_getln.c | 10 +- src/nvim/fileio.c | 2 +- src/nvim/fold.c | 34 +- src/nvim/getchar.c | 4 +- src/nvim/indent.c | 4 +- src/nvim/insexpand.c | 4 +- src/nvim/lua/executor.c | 2 +- src/nvim/mark.c | 2 +- src/nvim/match.c | 2 +- src/nvim/memline.c | 6 +- src/nvim/menu.c | 4 +- src/nvim/mouse.c | 12 +- src/nvim/move.c | 795 +++++++++++++++++------------------- src/nvim/normal.c | 184 ++++----- src/nvim/ops.c | 68 +-- src/nvim/option.c | 10 +- src/nvim/optionstr.c | 5 +- src/nvim/plines.c | 8 +- src/nvim/popupmenu.c | 6 +- src/nvim/quickfix.c | 6 +- src/nvim/search.c | 10 +- src/nvim/state.c | 4 +- src/nvim/state.h | 1 + src/nvim/tag.c | 6 +- src/nvim/terminal.c | 4 +- src/nvim/textformat.c | 12 +- src/nvim/textobject.c | 12 +- src/nvim/undo.c | 10 +- src/nvim/window.c | 14 +- test/client/uv_stream.lua | 2 +- test/functional/api/buffer_spec.lua | 67 ++- test/functional/lua/ffi_spec.lua | 13 +- 50 files changed, 844 insertions(+), 814 deletions(-) diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 035e36a2dd..42467d1562 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -1269,10 +1269,13 @@ static void fix_cursor(win_T *win, linenr_T lo, linenr_T hi, linenr_T extra) } else if (extra < 0) { check_cursor_lnum(win); } - check_cursor_col_win(win); + check_cursor_col(win); changed_cline_bef_curs(win); + win->w_valid &= ~(VALID_BOTLINE_AP); + update_topline(win); + } else { + invalidate_botline(win); } - invalidate_botline(win); } /// Fix cursor position after replacing text @@ -1307,7 +1310,7 @@ static void fix_cursor_cols(win_T *win, linenr_T start_row, colnr_T start_col, l // it's easier to work with a single value here. // col and coladd are fixed by a later call - // to check_cursor_col_win when necessary + // to check_cursor_col when necessary win->w_cursor.col += win->w_cursor.coladd; win->w_cursor.coladd = 0; @@ -1343,7 +1346,7 @@ static void fix_cursor_cols(win_T *win, linenr_T start_row, colnr_T start_col, l } } - check_cursor_col_win(win); + check_cursor_col(win); changed_cline_bef_curs(win); invalidate_botline(win); } diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index a21cf5b337..b5f56d270c 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -1246,7 +1246,7 @@ Boolean nvim_win_add_ns(Window window, Integer ns_id, Error *err) set_put(uint32_t, &win->w_ns_set, (uint32_t)ns_id); - changed_window_setting_win(win); + changed_window_setting(win); return true; } @@ -1291,7 +1291,7 @@ Boolean nvim_win_remove_ns(Window window, Integer ns_id, Error *err) set_del(uint32_t, &win->w_ns_set, (uint32_t)ns_id); - changed_window_setting_win(win); + changed_window_setting(win); return true; } diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 1a80e9ea16..026d09d9a9 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -138,7 +138,7 @@ void nvim_win_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err) win->w_cursor.col = (colnr_T)col; win->w_cursor.coladd = 0; // When column is out of range silently correct it. - check_cursor_col_win(win); + check_cursor_col(win); // Make sure we stick in this column. win->w_set_curswant = true; @@ -148,7 +148,7 @@ void nvim_win_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err) switchwin_T switchwin; switch_win(&switchwin, win, NULL, true); update_topline(curwin); - validate_cursor(); + validate_cursor(curwin); restore_win(&switchwin, true); redraw_later(win, UPD_VALID); diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index 652b6ba74e..285ef538b9 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -1432,7 +1432,7 @@ win_found: // the buffer contents may have changed VIsual_active = aco->save_VIsual_active; - check_cursor(); + check_cursor(curwin); if (curwin->w_topline > curbuf->b_ml.ml_line_count) { curwin->w_topline = curbuf->b_ml.ml_line_count; curwin->w_topfill = 0; @@ -1464,12 +1464,12 @@ win_found: // In case the autocommand moves the cursor to a position that does not // exist in curbuf VIsual_active = aco->save_VIsual_active; - check_cursor(); + check_cursor(curwin); } } VIsual_active = aco->save_VIsual_active; - check_cursor(); // just in case lines got deleted + check_cursor(curwin); // just in case lines got deleted if (VIsual_active) { check_pos(curbuf, &VIsual); } diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index e141706edd..3c2d52e6ad 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -1752,7 +1752,7 @@ void enter_buffer(buf_T *buf) maketitle(); // when autocmds didn't change it if (curwin->w_topline == 1 && !curwin->w_topline_was_set) { - scroll_cursor_halfway(false, false); // redisplay at correct position + scroll_cursor_halfway(curwin, false, false); // redisplay at correct position } // Change directories when the 'acd' option is set. @@ -2172,7 +2172,7 @@ int buflist_getfile(int n, linenr_T lnum, int options, int forceit) // cursor is at to BOL and w_cursor.lnum is checked due to getfile() if (!p_sol && col != 0) { curwin->w_cursor.col = col; - check_cursor_col(); + check_cursor_col(curwin); curwin->w_cursor.coladd = 0; curwin->w_set_curswant = true; } @@ -2197,7 +2197,7 @@ void buflist_getfpos(void) curwin->w_cursor.col = 0; } else { curwin->w_cursor.col = fpos->col; - check_cursor_col(); + check_cursor_col(curwin); curwin->w_cursor.coladd = 0; curwin->w_set_curswant = true; } @@ -3257,7 +3257,7 @@ void fileinfo(int fullname, int shorthelp, bool dont_truncate) (int64_t)curwin->w_cursor.lnum, (int64_t)curbuf->b_ml.ml_line_count, n); - validate_virtcol(); + validate_virtcol(curwin); size_t len = strlen(buffer); col_print(buffer + len, IOSIZE - len, (int)curwin->w_cursor.col + 1, (int)curwin->w_virtcol + 1); diff --git a/src/nvim/change.c b/src/nvim/change.c index 8b1e7587de..673907fa27 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -707,7 +707,7 @@ void ins_char(int c) void ins_char_bytes(char *buf, size_t charlen) { // Break tabs if needed. - if (virtual_active() && curwin->w_cursor.coladd > 0) { + if (virtual_active(curwin) && curwin->w_cursor.coladd > 0) { coladvance_force(getviscol()); } @@ -815,7 +815,7 @@ void ins_str(char *s) int newlen = (int)strlen(s); linenr_T lnum = curwin->w_cursor.lnum; - if (virtual_active() && curwin->w_cursor.coladd > 0) { + if (virtual_active(curwin) && curwin->w_cursor.coladd > 0) { coladvance_force(getviscol()); } @@ -918,7 +918,7 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) // fixpos is true, we don't want to end up positioned at the NUL, // unless "restart_edit" is set or 'virtualedit' contains "onemore". if (col > 0 && fixpos && restart_edit == 0 - && (get_ve_flags() & VE_ONEMORE) == 0) { + && (get_ve_flags(curwin) & VE_ONEMORE) == 0) { curwin->w_cursor.col--; curwin->w_cursor.coladd = 0; curwin->w_cursor.col -= utf_head_off(oldp, oldp + curwin->w_cursor.col); diff --git a/src/nvim/cursor.c b/src/nvim/cursor.c index ab99d1b854..c3f5a36500 100644 --- a/src/nvim/cursor.c +++ b/src/nvim/cursor.c @@ -57,7 +57,7 @@ int getviscol2(colnr_T col, colnr_T coladd) /// The caller must have saved the cursor line for undo! int coladvance_force(colnr_T wcol) { - int rc = coladvance2(&curwin->w_cursor, true, false, wcol); + int rc = coladvance2(curwin, &curwin->w_cursor, true, false, wcol); if (wcol == MAXCOL) { curwin->w_valid &= ~VALID_VIRTCOL; @@ -76,25 +76,26 @@ int coladvance_force(colnr_T wcol) /// beginning at coladd 0. /// /// @return OK if desired column is reached, FAIL if not -int coladvance(colnr_T wcol) +int coladvance(win_T *wp, colnr_T wcol) { - int rc = getvpos(&curwin->w_cursor, wcol); + int rc = getvpos(wp, &wp->w_cursor, wcol); if (wcol == MAXCOL || rc == FAIL) { - curwin->w_valid &= ~VALID_VIRTCOL; - } else if (*get_cursor_pos_ptr() != TAB) { + wp->w_valid &= ~VALID_VIRTCOL; + } else if (*(ml_get_buf(wp->w_buffer, wp->w_cursor.lnum) + wp->w_cursor.col) != TAB) { // Virtcol is valid when not on a TAB - curwin->w_valid |= VALID_VIRTCOL; - curwin->w_virtcol = wcol; + wp->w_valid |= VALID_VIRTCOL; + wp->w_virtcol = wcol; } return rc; } -/// @param addspaces change the text to achieve our goal? +/// @param addspaces change the text to achieve our goal? only for wp=curwin! /// @param finetune change char offset for the exact column /// @param wcol_arg column to move to (can be negative) -static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_arg) +static int coladvance2(win_T *wp, pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_arg) { + assert(wp == curwin || !addspaces); colnr_T wcol = wcol_arg; int idx; colnr_T col = 0; @@ -104,30 +105,30 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a || (State & MODE_TERMINAL) || restart_edit != NUL || (VIsual_active && *p_sel != 'o') - || ((get_ve_flags() & VE_ONEMORE) && wcol < MAXCOL); + || ((get_ve_flags(wp) & VE_ONEMORE) && wcol < MAXCOL); - char *line = ml_get_buf(curbuf, pos->lnum); + char *line = ml_get_buf(wp->w_buffer, pos->lnum); if (wcol >= MAXCOL) { idx = (int)strlen(line) - 1 + one_more; col = wcol; if ((addspaces || finetune) && !VIsual_active) { - curwin->w_curswant = linetabsize(curwin, pos->lnum) + one_more; - if (curwin->w_curswant > 0) { - curwin->w_curswant--; + wp->w_curswant = linetabsize(wp, pos->lnum) + one_more; + if (wp->w_curswant > 0) { + wp->w_curswant--; } } } else { - int width = curwin->w_width_inner - win_col_off(curwin); + int width = wp->w_width_inner - win_col_off(wp); int csize = 0; if (finetune - && curwin->w_p_wrap - && curwin->w_width_inner != 0 + && wp->w_p_wrap + && wp->w_width_inner != 0 && wcol >= (colnr_T)width && width > 0) { - csize = linetabsize(curwin, pos->lnum); + csize = linetabsize(wp, pos->lnum); if (csize > 0) { csize--; } @@ -143,7 +144,7 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a } CharsizeArg csarg; - CSType cstype = init_charsize_arg(&csarg, curwin, pos->lnum, line); + CSType cstype = init_charsize_arg(&csarg, wp, pos->lnum, line); StrCharInfo ci = utf_ptr2StrCharInfo(line); col = 0; while (col <= wcol && *ci.ptr != NUL) { @@ -159,14 +160,14 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a // is needed to ensure that a virtual position off the end of // a line has the correct indexing. The one_more comparison // replaces an explicit add of one_more later on. - if (col > wcol || (!virtual_active() && one_more == 0)) { + if (col > wcol || (!virtual_active(wp) && one_more == 0)) { idx -= 1; // Don't count the chars from 'showbreak'. csize -= head; col -= csize; } - if (virtual_active() + if (virtual_active(wp) && addspaces && wcol >= 0 && ((col != wcol && col != wcol + 1) || csize > 1)) { @@ -229,14 +230,14 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a if (!one_more) { colnr_T scol, ecol; - getvcol(curwin, pos, &scol, NULL, &ecol); + getvcol(wp, pos, &scol, NULL, &ecol); pos->coladd = ecol - scol; } } else { int b = (int)wcol - (int)col; // The difference between wcol and col is used to set coladd. - if (b > 0 && b < (MAXCOL - 2 * curwin->w_width_inner)) { + if (b > 0 && b < (MAXCOL - 2 * wp->w_width_inner)) { pos->coladd = b; } @@ -245,7 +246,7 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a } // Prevent from moving onto a trail byte. - mark_mb_adjustpos(curbuf, pos); + mark_mb_adjustpos(wp->w_buffer, pos); if (wcol < 0 || col < wcol) { return FAIL; @@ -256,9 +257,9 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a /// Return in "pos" the position of the cursor advanced to screen column "wcol". /// /// @return OK if desired column is reached, FAIL if not -int getvpos(pos_T *pos, colnr_T wcol) +int getvpos(win_T *wp, pos_T *pos, colnr_T wcol) { - return coladvance2(pos, false, virtual_active(), wcol); + return coladvance2(wp, pos, false, virtual_active(wp), wcol); } /// Increment the cursor position. See inc() for return values. @@ -294,7 +295,7 @@ linenr_T get_cursor_rel_lnum(win_T *wp, linenr_T lnum) // Loop until we reach to_line, skipping folds. for (; from_line < to_line; from_line++, retval++) { // If from_line is in a fold, set it to the last line of that fold. - hasFoldingWin(wp, from_line, NULL, &from_line, true, NULL); + hasFolding(wp, from_line, NULL, &from_line); } // If to_line is in a closed fold, the line count is off by +1. Correct it. @@ -329,7 +330,7 @@ void check_cursor_lnum(win_T *win) if (win->w_cursor.lnum > buf->b_ml.ml_line_count) { // If there is a closed fold at the end of the file, put the cursor in // its first line. Otherwise in the last line. - if (!hasFolding(buf->b_ml.ml_line_count, &win->w_cursor.lnum, NULL)) { + if (!hasFolding(win, buf->b_ml.ml_line_count, &win->w_cursor.lnum, NULL)) { win->w_cursor.lnum = buf->b_ml.ml_line_count; } } @@ -338,19 +339,13 @@ void check_cursor_lnum(win_T *win) } } -/// Make sure curwin->w_cursor.col is valid. -void check_cursor_col(void) -{ - check_cursor_col_win(curwin); -} - /// Make sure win->w_cursor.col is valid. Special handling of insert-mode. /// @see mb_check_adjust_col -void check_cursor_col_win(win_T *win) +void check_cursor_col(win_T *win) { colnr_T oldcol = win->w_cursor.col; colnr_T oldcoladd = win->w_cursor.col + win->w_cursor.coladd; - unsigned cur_ve_flags = get_ve_flags(); + unsigned cur_ve_flags = get_ve_flags(win); colnr_T len = (colnr_T)strlen(ml_get_buf(win->w_buffer, win->w_cursor.lnum)); if (len == 0) { @@ -363,7 +358,7 @@ void check_cursor_col_win(win_T *win) if ((State & MODE_INSERT) || restart_edit || (VIsual_active && *p_sel != 'o') || (cur_ve_flags & VE_ONEMORE) - || virtual_active()) { + || virtual_active(win)) { win->w_cursor.col = len; } else { win->w_cursor.col = len - 1; @@ -403,10 +398,10 @@ void check_cursor_col_win(win_T *win) } /// Make sure curwin->w_cursor in on a valid character -void check_cursor(void) +void check_cursor(win_T *wp) { - check_cursor_lnum(curwin); - check_cursor_col(); + check_cursor_lnum(wp); + check_cursor_col(wp); } /// Check if VIsual position is valid, correct it if not. @@ -453,8 +448,8 @@ bool set_leftcol(colnr_T leftcol) changed_cline_bef_curs(curwin); // TODO(hinidu): I think it should be colnr_T or int, but p_siso is long. // Perhaps we can change p_siso to int. - int64_t lastcol = curwin->w_leftcol + curwin->w_width_inner - curwin_col_off() - 1; - validate_virtcol(); + int64_t lastcol = curwin->w_leftcol + curwin->w_width_inner - win_col_off(curwin) - 1; + validate_virtcol(curwin); bool retval = false; // If the cursor is right or left of the screen, move it to last or first @@ -462,10 +457,10 @@ bool set_leftcol(colnr_T leftcol) int siso = get_sidescrolloff_value(curwin); if (curwin->w_virtcol > (colnr_T)(lastcol - siso)) { retval = true; - coladvance((colnr_T)(lastcol - siso)); + coladvance(curwin, (colnr_T)(lastcol - siso)); } else if (curwin->w_virtcol < curwin->w_leftcol + siso) { retval = true; - coladvance((colnr_T)(curwin->w_leftcol + siso)); + coladvance(curwin, (colnr_T)(curwin->w_leftcol + siso)); } // If the start of the character under the cursor is not on the screen, @@ -475,10 +470,10 @@ bool set_leftcol(colnr_T leftcol) getvvcol(curwin, &curwin->w_cursor, &s, NULL, &e); if (e > (colnr_T)lastcol) { retval = true; - coladvance(s - 1); + coladvance(curwin, s - 1); } else if (s < curwin->w_leftcol) { retval = true; - if (coladvance(e + 1) == FAIL) { // there isn't another character + if (coladvance(curwin, e + 1) == FAIL) { // there isn't another character curwin->w_leftcol = s; // adjust w_leftcol instead changed_cline_bef_curs(curwin); } diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 51d5d08f78..41ef1aceaf 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -890,9 +890,9 @@ int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines, TriState has_fo } assert(lnum > 0); - bool below_fold = lnum > 1 && hasFoldingWin(wp, lnum - 1, NULL, NULL, true, NULL); + bool below_fold = lnum > 1 && hasFolding(wp, lnum - 1, NULL, NULL); if (has_fold == kNone) { - has_fold = hasFoldingWin(wp, lnum, NULL, NULL, true, NULL); + has_fold = hasFolding(wp, lnum, NULL, NULL); } const int row = lnum - 1; diff --git a/src/nvim/diff.c b/src/nvim/diff.c index 2b3010e063..bc91c1e4c2 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -1347,7 +1347,7 @@ void ex_diffsplit(exarg_T *eap) set_bufref(&old_curbuf, curbuf); // Need to compute w_fraction when no redraw happened yet. - validate_cursor(); + validate_cursor(curwin); set_fraction(curwin); // don't use a new tab page, each tab page has its own diffs @@ -1457,7 +1457,7 @@ void diff_win_options(win_T *wp, bool addbuf) foldUpdateAll(wp); // make sure topline is not halfway through a fold - changed_window_setting_win(wp); + changed_window_setting(wp); if (vim_strchr(p_sbo, 'h') == NULL) { do_cmdline_cmd("set sbo+=hor"); } @@ -1522,7 +1522,7 @@ void ex_diffoff(exarg_T *eap) // make sure topline is not halfway a fold and cursor is // invalidated - changed_window_setting_win(wp); + changed_window_setting(wp); // Note: 'sbo' is not restored, it's a global option. diff_buf_adjust(wp); @@ -2137,7 +2137,7 @@ int diff_check_with_linestatus(win_T *wp, linenr_T lnum, int *linestatus) } // A closed fold never has filler lines. - if (hasFoldingWin(wp, lnum, NULL, NULL, true, NULL)) { + if (hasFolding(wp, lnum, NULL, NULL)) { return 0; } @@ -2451,8 +2451,7 @@ void diff_set_topline(win_T *fromwin, win_T *towin) changed_line_abv_curs_win(towin); check_topfill(towin, false); - hasFoldingWin(towin, towin->w_topline, &towin->w_topline, - NULL, true, NULL); + hasFolding(towin, towin->w_topline, &towin->w_topline, NULL); } /// This is called when 'diffopt' is changed. @@ -2988,7 +2987,7 @@ theend: // Check that the cursor is on a valid character and update its // position. When there were filler lines the topline has become // invalid. - check_cursor(); + check_cursor(curwin); changed_line_abv_curs(); if (diff_need_update) { diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index c5f6ce2e36..a7b1d561b6 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -1393,7 +1393,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s // the end of the line may be before the start of the displayed part. if (wlv.vcol < start_col && (wp->w_p_cuc || wlv.color_cols - || virtual_active() + || virtual_active(wp) || (VIsual_active && wp->w_buffer == curwin->w_buffer))) { wlv.vcol = start_col; } @@ -2339,7 +2339,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s && wlv.line_attr == 0 && wlv.line_attr_lowprio == 0) { // In virtualedit, visual selections may extend beyond end of line - if (!(area_highlighting && virtual_active() + if (!(area_highlighting && virtual_active(wp) && wlv.tocol != MAXCOL && wlv.vcol < wlv.tocol)) { wlv.p_extra = ""; } @@ -2382,7 +2382,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s mb_schar = schar_from_ascii(mb_c); } else if (VIsual_active && (VIsual_mode == Ctrl_V || VIsual_mode == 'v') - && virtual_active() + && virtual_active(wp) && wlv.tocol != MAXCOL && wlv.vcol < wlv.tocol && wlv.col < grid->cols) { diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c index 402f7fa428..f2ad4ca77e 100644 --- a/src/nvim/drawscreen.c +++ b/src/nvim/drawscreen.c @@ -823,7 +823,7 @@ void setcursor(void) void setcursor_mayforce(bool force) { if (force || redrawing()) { - validate_cursor(); + validate_cursor(curwin); ScreenGrid *grid = &curwin->w_grid; int row = curwin->w_wrow; @@ -851,7 +851,7 @@ void show_cursor_info_later(bool force) && *ml_get_buf(curwin->w_buffer, curwin->w_cursor.lnum) == NUL; // Only draw when something changed. - validate_virtcol_win(curwin); + validate_virtcol(curwin); if (force || curwin->w_cursor.lnum != curwin->w_stl_cursor.lnum || curwin->w_cursor.col != curwin->w_stl_cursor.col @@ -1611,14 +1611,14 @@ static void win_update(win_T *wp) } } - hasFoldingWin(wp, mod_top, &mod_top, NULL, true, NULL); + hasFolding(wp, mod_top, &mod_top, NULL); if (mod_top > lnumt) { mod_top = lnumt; } // Now do the same for the bottom line (one above mod_bot). mod_bot--; - hasFoldingWin(wp, mod_bot, NULL, &mod_bot, true, NULL); + hasFolding(wp, mod_bot, NULL, &mod_bot); mod_bot++; if (mod_bot < lnumb) { mod_bot = lnumb; @@ -1691,7 +1691,7 @@ static void win_update(win_T *wp) if (j >= wp->w_grid.rows - 2) { break; } - hasFoldingWin(wp, ln, NULL, &ln, true, NULL); + hasFolding(wp, ln, NULL, &ln); } } else { j = wp->w_lines[0].wl_lnum - wp->w_topline; @@ -1903,7 +1903,7 @@ static void win_update(win_T *wp) // Highlight to the end of the line, unless 'virtualedit' has // "block". if (curwin->w_curswant == MAXCOL) { - if (get_ve_flags() & VE_BLOCK) { + if (get_ve_flags(curwin) & VE_BLOCK) { pos_T pos; int cursor_above = curwin->w_cursor.lnum < VIsual.lnum; @@ -2148,7 +2148,7 @@ static void win_update(win_T *wp) // rows, and may insert/delete lines int j = idx; for (l = lnum; l < mod_bot; l++) { - if (hasFoldingWin(wp, l, NULL, &l, true, NULL)) { + if (hasFolding(wp, l, NULL, &l)) { new_rows++; } else if (l == wp->w_topline) { int n = plines_win_nofill(wp, l, false) + wp->w_topfill; diff --git a/src/nvim/edit.c b/src/nvim/edit.c index a0d6f7125e..5b62ab4215 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -185,7 +185,7 @@ static void insert_enter(InsertState *s) curwin->w_cursor = save_cursor; State = MODE_INSERT; - check_cursor_col(); + check_cursor_col(curwin); State = save_state; } } @@ -282,7 +282,7 @@ static void insert_enter(InsertState *s) // correct in very rare cases). // Also do this if curswant is greater than the current virtual // column. Eg after "^O$" or "^O80|". - validate_virtcol(); + validate_virtcol(curwin); update_curswant(); if (((ins_at_eol && curwin->w_cursor.lnum == o_lnum) || curwin->w_curswant > curwin->w_virtcol) @@ -468,7 +468,7 @@ static int insert_check(VimState *state) && curwin->w_topline == s->old_topline && curwin->w_topfill == s->old_topfill) { s->mincol = curwin->w_wcol; - validate_cursor_col(); + validate_cursor_col(curwin); if (curwin->w_wcol < s->mincol - tabstop_at(get_nolist_virtcol(), curbuf->b_p_ts, @@ -478,7 +478,7 @@ static int insert_check(VimState *state) || curwin->w_topfill > 0)) { if (curwin->w_topfill > 0) { curwin->w_topfill--; - } else if (hasFolding(curwin->w_topline, NULL, &s->old_topline)) { + } else if (hasFolding(curwin, curwin->w_topline, NULL, &s->old_topline)) { set_topline(curwin, s->old_topline + 1); } else { set_topline(curwin, curwin->w_topline + 1); @@ -491,7 +491,7 @@ static int insert_check(VimState *state) s->did_backspace = false; - validate_cursor(); // may set must_redraw + validate_cursor(curwin); // may set must_redraw // Redraw the display when no characters are waiting. // Also shows mode, ruler and positions cursor. @@ -743,7 +743,7 @@ static int insert_handle_key(InsertState *s) ins_ctrl_o(); // don't move the cursor left when 'virtualedit' has "onemore". - if (get_ve_flags() & VE_ONEMORE) { + if (get_ve_flags(curwin) & VE_ONEMORE) { ins_at_eol = false; s->nomove = true; } @@ -1451,7 +1451,7 @@ void edit_putchar(int c, bool highlight) int attr; update_topline(curwin); // just in case w_topline isn't valid - validate_cursor(); + validate_cursor(curwin); if (highlight) { attr = HL_ATTR(HLF_8); } else { @@ -1521,7 +1521,7 @@ static void init_prompt(int cmdchar_todo) ml_append(curbuf->b_ml.ml_line_count, prompt, 0, false); } curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; - coladvance(MAXCOL); + coladvance(curwin, MAXCOL); inserted_bytes(curbuf->b_ml.ml_line_count, 0, 0, (colnr_T)strlen(prompt)); } @@ -1536,13 +1536,13 @@ static void init_prompt(int cmdchar_todo) } if (cmdchar_todo == 'A') { - coladvance(MAXCOL); + coladvance(curwin, MAXCOL); } if (curwin->w_cursor.col < (colnr_T)strlen(prompt)) { curwin->w_cursor.col = (colnr_T)strlen(prompt); } // Make sure the cursor is in a valid position. - check_cursor(); + check_cursor(curwin); } /// @return true if the cursor is in the editable position of the prompt line. @@ -2394,7 +2394,7 @@ static void stop_insert(pos_T *end_insert_pos, int esc, int nomove) pos_T tpos = curwin->w_cursor; curwin->w_cursor = *end_insert_pos; - check_cursor_col(); // make sure it is not past the line + check_cursor_col(curwin); // make sure it is not past the line while (true) { if (gchar_cursor() == NUL && curwin->w_cursor.col > 0) { curwin->w_cursor.col--; @@ -2471,7 +2471,7 @@ void free_last_insert(void) void beginline(int flags) { if ((flags & BL_SOL) && !p_sol) { - coladvance(curwin->w_curswant); + coladvance(curwin, curwin->w_curswant); } else { curwin->w_cursor.col = 0; curwin->w_cursor.coladd = 0; @@ -2497,13 +2497,13 @@ int oneright(void) { char *ptr; - if (virtual_active()) { + if (virtual_active(curwin)) { pos_T prevpos = curwin->w_cursor; // Adjust for multi-wide char (excluding TAB) ptr = get_cursor_pos_ptr(); - coladvance(getviscol() + ((*ptr != TAB && vim_isprintc(utf_ptr2char(ptr))) - ? ptr2cells(ptr) : 1)); + coladvance(curwin, getviscol() + ((*ptr != TAB && vim_isprintc(utf_ptr2char(ptr))) + ? ptr2cells(ptr) : 1)); curwin->w_set_curswant = true; // Return OK if the cursor moved, FAIL otherwise (at window edge). return (prevpos.col != curwin->w_cursor.col @@ -2519,7 +2519,7 @@ int oneright(void) // move "l" bytes right, but don't end up on the NUL, unless 'virtualedit' // contains "onemore". - if (ptr[l] == NUL && (get_ve_flags() & VE_ONEMORE) == 0) { + if (ptr[l] == NUL && (get_ve_flags(curwin) & VE_ONEMORE) == 0) { return FAIL; } curwin->w_cursor.col += l; @@ -2531,7 +2531,7 @@ int oneright(void) int oneleft(void) { - if (virtual_active()) { + if (virtual_active(curwin)) { int v = getviscol(); if (v == 0) { @@ -2541,7 +2541,7 @@ int oneleft(void) // We might get stuck on 'showbreak', skip over it. int width = 1; while (true) { - coladvance(v - width); + coladvance(curwin, v - width); // getviscol() is slow, skip it when 'showbreak' is empty, // 'breakindent' is not set and there are no multi-byte // characters @@ -2590,7 +2590,7 @@ void cursor_up_inner(win_T *wp, linenr_T n) // Count each sequence of folded lines as one logical line. // go to the start of the current fold - hasFoldingWin(wp, lnum, &lnum, NULL, true, NULL); + hasFolding(wp, lnum, &lnum, NULL); while (n--) { // move up one line @@ -2602,7 +2602,7 @@ void cursor_up_inner(win_T *wp, linenr_T n) // Insert mode or when 'foldopen' contains "all": it will open // in a moment. if (n > 0 || !((State & MODE_INSERT) || (fdo_flags & FDO_ALL))) { - hasFoldingWin(wp, lnum, &lnum, NULL, true, NULL); + hasFolding(wp, lnum, &lnum, NULL); } } if (lnum < 1) { @@ -2625,7 +2625,7 @@ int cursor_up(linenr_T n, bool upd_topline) cursor_up_inner(curwin, n); // try to advance to the column we want to be at - coladvance(curwin->w_curswant); + coladvance(curwin, curwin->w_curswant); if (upd_topline) { update_topline(curwin); // make sure curwin->w_topline is valid @@ -2678,7 +2678,7 @@ int cursor_down(int n, bool upd_topline) cursor_down_inner(curwin, n); // try to advance to the column we want to be at - coladvance(curwin->w_curswant); + coladvance(curwin, curwin->w_curswant); if (upd_topline) { update_topline(curwin); // make sure curwin->w_topline is valid @@ -3274,7 +3274,7 @@ static void ins_reg(void) // Cursor may be moved back a column. curwin->w_cursor = curpos; - check_cursor(); + check_cursor(curwin); } if (regname == NUL || !valid_yank_reg(regname, false)) { vim_beep(BO_REG); @@ -3466,7 +3466,7 @@ static bool ins_esc(int *count, int cmdchar, bool nomove) && (curwin->w_cursor.col != 0 || curwin->w_cursor.coladd > 0) && (restart_edit == NUL || (gchar_cursor() == NUL && !VIsual_active)) && !revins_on) { - if (curwin->w_cursor.coladd > 0 || get_ve_flags() == VE_ALL) { + if (curwin->w_cursor.coladd > 0 || get_ve_flags(curwin) == VE_ALL) { oneleft(); if (restart_edit != NUL) { curwin->w_cursor.coladd++; @@ -3598,7 +3598,7 @@ static void ins_ctrl_o(void) } else { restart_edit = 'I'; } - if (virtual_active()) { + if (virtual_active(curwin)) { ins_at_eol = false; // cursor always keeps its column } else { ins_at_eol = (gchar_cursor() == NUL); @@ -4028,7 +4028,7 @@ static void ins_left(void) // always break undo when moving upwards/downwards, else undo may break start_arrow(&tpos); curwin->w_cursor.lnum--; - coladvance(MAXCOL); + coladvance(curwin, MAXCOL); curwin->w_set_curswant = true; // so we stay at the end } else { vim_beep(BO_CRSR); @@ -4062,7 +4062,7 @@ static void ins_end(int c) if (c == K_C_END) { curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; } - coladvance(MAXCOL); + coladvance(curwin, MAXCOL); curwin->w_curswant = MAXCOL; start_arrow(&tpos); @@ -4096,13 +4096,13 @@ static void ins_right(void) foldOpenCursor(); } undisplay_dollar(); - if (gchar_cursor() != NUL || virtual_active()) { + if (gchar_cursor() != NUL || virtual_active(curwin)) { start_arrow_with_change(&curwin->w_cursor, end_change); if (!end_change) { AppendCharToRedobuff(K_RIGHT); } curwin->w_set_curswant = true; - if (virtual_active()) { + if (virtual_active(curwin)) { oneright(); } else { curwin->w_cursor.col += utfc_ptr2len(get_cursor_pos_ptr()); @@ -4157,7 +4157,7 @@ static void ins_up(bool startcol) pos_T tpos = curwin->w_cursor; if (cursor_up(1, true) == OK) { if (startcol) { - coladvance(getvcol_nolist(&Insstart)); + coladvance(curwin, getvcol_nolist(&Insstart)); } if (old_topline != curwin->w_topline || old_topfill != curwin->w_topfill) { @@ -4202,7 +4202,7 @@ static void ins_down(bool startcol) pos_T tpos = curwin->w_cursor; if (cursor_down(1, true) == OK) { if (startcol) { - coladvance(getvcol_nolist(&Insstart)); + coladvance(curwin, getvcol_nolist(&Insstart)); } if (old_topline != curwin->w_topline || old_topfill != curwin->w_topfill) { @@ -4474,8 +4474,8 @@ bool ins_eol(int c) // Put cursor on NUL if on the last char and coladd is 1 (happens after // CTRL-O). - if (virtual_active() && curwin->w_cursor.coladd > 0) { - coladvance(getviscol()); + if (virtual_active(curwin) && curwin->w_cursor.coladd > 0) { + coladvance(curwin, getviscol()); } // NL in reverse insert will always start in the end of current line. @@ -4574,7 +4574,7 @@ int ins_copychar(linenr_T lnum) } // try to advance to the cursor column - validate_virtcol(); + validate_virtcol(curwin); int const end_vcol = curwin->w_virtcol; char *line = ml_get(lnum); @@ -4720,7 +4720,7 @@ colnr_T get_nolist_virtcol(void) if (curwin->w_p_list && vim_strchr(p_cpo, CPO_LISTWM) == NULL) { return getvcol_nolist(&curwin->w_cursor); } - validate_virtcol(); + validate_virtcol(curwin); return curwin->w_virtcol; } diff --git a/src/nvim/eval/buffer.c b/src/nvim/eval/buffer.c index 7b8f71ef3f..73bfd6db2a 100644 --- a/src/nvim/eval/buffer.c +++ b/src/nvim/eval/buffer.c @@ -197,7 +197,7 @@ static void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append, typval_ && ml_replace(lnum, line, true) == OK) { inserted_bytes(lnum, 0, old_len, (int)strlen(line)); if (is_curbuf && lnum == curwin->w_cursor.lnum) { - check_cursor_col(); + check_cursor_col(curwin); } rettv->vval.v_number = 0; // OK } @@ -229,7 +229,7 @@ static void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append, typval_ wp->w_cursor.lnum += (linenr_T)added; } } - check_cursor_col(); + check_cursor_col(curwin); update_topline(curwin); } @@ -469,7 +469,7 @@ void f_deletebufline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } } } - check_cursor_col(); + check_cursor_col(curwin); deleted_lines_mark(first, count); rettv->vval.v_number = 0; // OK diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 1d5835c9bf..99da15ddd7 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -727,7 +727,7 @@ static void get_col(typval_T *argvars, typval_T *rettv, bool charcol) return; } - check_cursor(); + check_cursor(curwin); winchanged = true; } @@ -746,7 +746,7 @@ static void get_col(typval_T *argvars, typval_T *rettv, bool charcol) col = fp->col + 1; // col(".") when the cursor is on the NUL at the end of the line // because of "coladd" can be seen as an extra column. - if (virtual_active() && fp == &curwin->w_cursor) { + if (virtual_active(curwin) && fp == &curwin->w_cursor) { char *p = get_cursor_pos_ptr(); if (curwin->w_cursor.coladd >= (colnr_T)win_chartabsize(curwin, p, @@ -1191,7 +1191,7 @@ static void set_cursorpos(typval_T *argvars, typval_T *rettv, bool charcol) curwin->w_cursor.coladd = coladd; // Make sure the cursor is in a valid position. - check_cursor(); + check_cursor(curwin); // Correct cursor for multi-byte character. mb_adjust_cursor(); @@ -2890,7 +2890,7 @@ static void f_getregion(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) curbuf = findbuf; curwin->w_buffer = curbuf; const TriState save_virtual = virtual_op; - virtual_op = virtual_active(); + virtual_op = virtual_active(curwin); // NOTE: Adjust is needed. p1.col--; @@ -4643,7 +4643,7 @@ static void f_line(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) if (wp != NULL && tp != NULL) { switchwin_T switchwin; if (switch_win_noblock(&switchwin, wp, tp, true) == OK) { - check_cursor(); + check_cursor(curwin); fp = var2fpos(&argvars[0], true, &fnum, false); } restore_win_noblock(&switchwin, true); @@ -7029,7 +7029,7 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) } // "/$" will put the cursor after the end of the line, may need to // correct that here - check_cursor(); + check_cursor(curwin); } // If 'n' flag is used: restore cursor position. @@ -7791,7 +7791,7 @@ static void set_position(typval_T *argvars, typval_T *rettv, bool charpos) curwin->w_curswant = curswant - 1; curwin->w_set_curswant = false; } - check_cursor(); + check_cursor(curwin); rettv->vval.v_number = 0; } else if (name[0] == '\'' && name[1] != NUL && name[2] == NUL) { // set mark @@ -9204,7 +9204,7 @@ static void f_virtcol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) goto theend; } - check_cursor(); + check_cursor(curwin); winchanged = true; } diff --git a/src/nvim/eval/window.c b/src/nvim/eval/window.c index 3e2f6301ca..68de40f983 100644 --- a/src/nvim/eval/window.c +++ b/src/nvim/eval/window.c @@ -516,7 +516,7 @@ bool win_execute_before(win_execute_T *args, win_T *wp, tabpage_T *tp) } if (switch_win_noblock(&args->switchwin, wp, tp, true) == OK) { - check_cursor(); + check_cursor(curwin); return true; } return false; @@ -540,7 +540,7 @@ void win_execute_after(win_execute_T *args) // In case the command moved the cursor or changed the Visual area, // check it is valid. - check_cursor(); + check_cursor(curwin); if (VIsual_active) { check_pos(curbuf, &VIsual); } @@ -774,7 +774,7 @@ void f_winbufnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// "wincol()" function void f_wincol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - validate_cursor(); + validate_cursor(curwin); rettv->vval.v_number = curwin->w_wcol + 1; } @@ -811,7 +811,7 @@ void f_winlayout(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// "winline()" function void f_winline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - validate_cursor(); + validate_cursor(curwin); rettv->vval.v_number = curwin->w_wrow + 1; } @@ -883,10 +883,10 @@ void f_winrestview(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) curwin->w_skipcol = (colnr_T)tv_get_number(&di->di_tv); } - check_cursor(); + check_cursor(curwin); win_new_height(curwin, curwin->w_height); win_new_width(curwin, curwin->w_width); - changed_window_setting(); + changed_window_setting(curwin); if (curwin->w_topline <= 0) { curwin->w_topline = 1; diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 14bd2b87e3..9f48312ec6 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -2638,14 +2638,14 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum if (newcol >= 0) { // position set by autocommands curwin->w_cursor.lnum = newlnum; curwin->w_cursor.col = newcol; - check_cursor(); + check_cursor(curwin); } else if (newlnum > 0) { // line number from caller or old position curwin->w_cursor.lnum = newlnum; check_cursor_lnum(curwin); if (solcol >= 0 && !p_sol) { // 'sol' is off: Use last known column. curwin->w_cursor.col = solcol; - check_cursor_col(); + check_cursor_col(curwin); curwin->w_cursor.coladd = 0; curwin->w_set_curswant = true; } else { @@ -3787,7 +3787,7 @@ static int do_sub(exarg_T *eap, const proftime_T timeout, const int cmdpreview_n highlight_match = true; update_topline(curwin); - validate_cursor(); + validate_cursor(curwin); redraw_later(curwin, UPD_SOME_VALID); show_cursor_info_later(true); update_screen(); @@ -4247,7 +4247,7 @@ skip: // when interactive leave cursor on the match if (!subflags.do_ask) { if (endcolumn) { - coladvance(MAXCOL); + coladvance(curwin, MAXCOL); } else { beginline(BL_WHITE | BL_FIX); } @@ -4278,7 +4278,7 @@ skip: if (subflags.do_ask && hasAnyFolding(curwin)) { // Cursor position may require updating - changed_window_setting(); + changed_window_setting(curwin); } vim_regfree(regmatch.regprog); @@ -4514,7 +4514,7 @@ void global_exe(char *cmd) if (global_need_beginline) { beginline(BL_WHITE | BL_FIX); } else { - check_cursor(); // cursor may be beyond the end of the line + check_cursor(curwin); // cursor may be beyond the end of the line } // the cursor may not have moved in the text but a change in a previous diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 12687d0ea8..a34eb0232b 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -654,7 +654,7 @@ void ex_listdo(exarg_T *eap) } if (eap->cmdidx == CMD_windo && execute) { - validate_cursor(); // cursor may have moved + validate_cursor(curwin); // cursor may have moved // required when 'scrollbind' has been set if (curwin->w_p_scb) { do_check_scrollbind(true); diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 1b4e83d392..6db72ff2d1 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -1743,8 +1743,8 @@ int execute_cmd(exarg_T *eap, CmdParseInfo *cmdinfo, bool preview) && eap->addr_type == ADDR_LINES) { // Put the first line at the start of a closed fold, put the last line // at the end of a closed fold. - hasFolding(eap->line1, &eap->line1, NULL); - hasFolding(eap->line2, NULL, &eap->line2); + hasFolding(curwin, eap->line1, &eap->line1, NULL); + hasFolding(curwin, eap->line2, NULL, &eap->line2); } // Use first argument as count when possible @@ -2213,8 +2213,8 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter && ea.addr_type == ADDR_LINES) { // Put the first line at the start of a closed fold, put the last line // at the end of a closed fold. - hasFolding(ea.line1, &ea.line1, NULL); - hasFolding(ea.line2, NULL, &ea.line2); + hasFolding(curwin, ea.line1, &ea.line1, NULL); + hasFolding(curwin, ea.line2, NULL, &ea.line2); } // For the ":make" and ":grep" commands we insert the 'makeprg'/'grepprg' @@ -2875,9 +2875,9 @@ int parse_cmd_address(exarg_T *eap, const char **errormsg, bool silent) // (where zero usually means to use the first line). // Check the cursor position before returning. if (eap->line2 > 0) { - check_cursor(); + check_cursor(curwin); } else { - check_cursor_col(); + check_cursor_col(curwin); } need_check_cursor = true; } @@ -2899,7 +2899,7 @@ int parse_cmd_address(exarg_T *eap, const char **errormsg, bool silent) theend: if (need_check_cursor) { - check_cursor(); + check_cursor(curwin); } return ret; } @@ -3596,7 +3596,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, bool // closed fold after the first address. if (addr_type == ADDR_LINES && (i == '-' || i == '+') && address_count >= 2) { - hasFolding(lnum, NULL, &lnum); + hasFolding(curwin, lnum, NULL, &lnum); } if (i == '-') { lnum -= n; @@ -5528,8 +5528,6 @@ static void ex_swapname(exarg_T *eap) /// (1998-11-02 16:21:01 R. Edward Ralston ) static void ex_syncbind(exarg_T *eap) { - win_T *save_curwin = curwin; - buf_T *save_curbuf = curbuf; linenr_T topline; int y; linenr_T old_linenr = curwin->w_cursor.lnum; @@ -5556,23 +5554,19 @@ static void ex_syncbind(exarg_T *eap) // Set all scrollbind windows to the same topline. FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - curwin = wp; - if (curwin->w_p_scb) { - curbuf = curwin->w_buffer; - y = topline - curwin->w_topline; + if (wp->w_p_scb) { + y = topline - wp->w_topline; if (y > 0) { - scrollup(y, true); + scrollup(wp, y, true); } else { - scrolldown(-y, true); + scrolldown(wp, -y, true); } - curwin->w_scbind_pos = topline; - redraw_later(curwin, UPD_VALID); - cursor_correct(); - curwin->w_redr_status = true; + wp->w_scbind_pos = topline; + redraw_later(wp, UPD_VALID); + cursor_correct(wp); + wp->w_redr_status = true; } } - curwin = save_curwin; - curbuf = save_curbuf; if (curwin->w_p_scb) { did_syncbind = true; checkpcmark(); @@ -5854,7 +5848,7 @@ static void ex_equal(exarg_T *eap) static void ex_sleep(exarg_T *eap) { - if (cursor_valid()) { + if (cursor_valid(curwin)) { setcursor_mayforce(true); } @@ -5990,7 +5984,7 @@ static void ex_put(exarg_T *eap) eap->forceit = true; } curwin->w_cursor.lnum = eap->line2; - check_cursor_col(); + check_cursor_col(curwin); do_put(eap->regname, NULL, eap->forceit ? BACKWARD : FORWARD, 1, PUT_LINE|PUT_CURSLINE); } @@ -6084,7 +6078,7 @@ static void ex_at(exarg_T *eap) int prev_len = typebuf.tb_len; curwin->w_cursor.lnum = eap->line2; - check_cursor_col(); + check_cursor_col(curwin); // Get the register name. No name means use the previous one. int c = (uint8_t)(*eap->arg); @@ -6306,7 +6300,7 @@ static void ex_redraw(exarg_T *eap) RedrawingDisabled = 0; p_lz = false; - validate_cursor(); + validate_cursor(curwin); update_topline(curwin); if (eap->forceit) { redraw_all_later(UPD_NOT_VALID); @@ -6459,10 +6453,10 @@ static void ex_mark(exarg_T *eap) /// Update w_topline, w_leftcol and the cursor position. void update_topline_cursor(void) { - check_cursor(); // put cursor on valid line + check_cursor(curwin); // put cursor on valid line update_topline(curwin); if (!curwin->w_p_wrap) { - validate_cursor(); + validate_cursor(curwin); } update_curswant(); } @@ -6766,7 +6760,7 @@ static void ex_pedit(exarg_T *eap) if (curwin != curwin_save && win_valid(curwin_save)) { // Return cursor to where we were - validate_cursor(); + validate_cursor(curwin); redraw_later(curwin, UPD_VALID); win_enter(curwin_save, true); } @@ -7408,7 +7402,7 @@ static void ex_folddo(exarg_T *eap) { // First set the marks for all lines closed/open. for (linenr_T lnum = eap->line1; lnum <= eap->line2; lnum++) { - if (hasFolding(lnum, NULL, NULL) == (eap->cmdidx == CMD_folddoclosed)) { + if (hasFolding(curwin, lnum, NULL, NULL) == (eap->cmdidx == CMD_folddoclosed)) { ml_setmarked(lnum); } } diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 44a78711d2..303337ae98 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -510,7 +510,7 @@ static void may_do_incsearch_highlighting(int firstc, int count, incsearch_state s->match_start = curwin->w_cursor; set_search_match(&curwin->w_cursor); - validate_cursor(); + validate_cursor(curwin); end_pos = curwin->w_cursor; s->match_end = end_pos; curwin->w_cursor = save_pos; @@ -530,7 +530,7 @@ static void may_do_incsearch_highlighting(int firstc, int count, incsearch_state ccline.cmdbuff[skiplen + patlen] = next_char; } - validate_cursor(); + validate_cursor(curwin); // May redraw the status line to show the cursor position. if (p_ru && (curwin->w_status_height > 0 || global_stl_height() > 0)) { @@ -626,7 +626,7 @@ static void finish_incsearch_highlighting(bool gotesc, incsearch_state_T *s, magic_overruled = s->magic_overruled_save; - validate_cursor(); // needed for TAB + validate_cursor(curwin); // needed for TAB status_redraw_all(); redraw_all_later(UPD_SOME_VALID); if (call_update_screen) { @@ -1483,7 +1483,7 @@ static int may_do_command_line_next_incsearch(int firstc, int count, incsearch_s curwin->w_cursor = s->match_start; changed_cline_bef_curs(curwin); update_topline(curwin); - validate_cursor(); + validate_cursor(curwin); highlight_match = true; save_viewstate(curwin, &s->old_viewstate); redraw_later(curwin, UPD_NOT_VALID); @@ -4623,6 +4623,6 @@ static void set_search_match(pos_T *t) t->col = search_match_endcol; if (t->lnum > curbuf->b_ml.ml_line_count) { t->lnum = curbuf->b_ml.ml_line_count; - coladvance(MAXCOL); + coladvance(curwin, MAXCOL); } } diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 3b715e2c0b..4150d0997d 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -3197,7 +3197,7 @@ void buf_reload(buf_T *buf, int orig_mode, bool reload_options) curwin->w_topline = old_topline; } curwin->w_cursor = old_cursor; - check_cursor(); + check_cursor(curwin); update_topline(curwin); keep_filetype = false; diff --git a/src/nvim/fold.c b/src/nvim/fold.c index c571aaf0a4..15aba432c4 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -143,7 +143,7 @@ void copyFoldingState(win_T *wp_from, win_T *wp_to) } // hasAnyFolding() {{{2 -/// @return true if there may be folded lines in the current window. +/// @return true if there may be folded lines in window "win". int hasAnyFolding(win_T *win) { // very simple now, but can become more complex later @@ -155,10 +155,10 @@ int hasAnyFolding(win_T *win) /// When returning true, *firstp and *lastp are set to the first and last /// lnum of the sequence of folded lines (skipped when NULL). /// -/// @return true if line "lnum" in the current window is part of a closed fold. -bool hasFolding(linenr_T lnum, linenr_T *firstp, linenr_T *lastp) +/// @return true if line "lnum" in window "win" is part of a closed fold. +bool hasFolding(win_T *win, linenr_T lnum, linenr_T *firstp, linenr_T *lastp) { - return hasFoldingWin(curwin, lnum, firstp, lastp, true, NULL); + return hasFoldingWin(win, lnum, firstp, lastp, true, NULL); } // hasFoldingWin() {{{2 @@ -398,13 +398,13 @@ void opFoldRange(pos_T firstpos, pos_T lastpos, int opening, int recurse, bool h // Opening one level only: next fold to open is after the one going to // be opened. if (opening && !recurse) { - hasFolding(lnum, NULL, &lnum_next); + hasFolding(curwin, lnum, NULL, &lnum_next); } setManualFold(temp, opening, recurse, &done); // Closing one level only: next line to close a fold is after just // closed fold. if (!opening && !recurse) { - hasFolding(lnum, NULL, &lnum_next); + hasFolding(curwin, lnum, NULL, &lnum_next); } } if (done == DONE_NOTHING) { @@ -477,7 +477,7 @@ static void newFoldLevelWin(win_T *wp) } wp->w_fold_manual = false; } - changed_window_setting_win(wp); + changed_window_setting(wp); } // foldCheckClose() {{{2 @@ -492,7 +492,7 @@ void foldCheckClose(void) checkupdate(curwin); if (checkCloseRec(&curwin->w_folds, curwin->w_cursor.lnum, (int)curwin->w_p_fdl)) { - changed_window_setting(); + changed_window_setting(curwin); } } @@ -661,7 +661,7 @@ void foldCreate(win_T *wp, pos_T start, pos_T end) fp->fd_small = kNone; // redraw - changed_window_setting_win(wp); + changed_window_setting(wp); } } @@ -735,7 +735,7 @@ void deleteFold(win_T *const wp, const linenr_T start, const linenr_T end, const did_one = true; // redraw window - changed_window_setting_win(wp); + changed_window_setting(wp); } } if (!did_one) { @@ -746,7 +746,7 @@ void deleteFold(win_T *const wp, const linenr_T start, const linenr_T end, const } } else { // Deleting markers may make cursor column invalid - check_cursor_col_win(wp); + check_cursor_col(wp); } if (last_lnum > 0) { @@ -1009,11 +1009,11 @@ void foldAdjustVisual(void) start = &curwin->w_cursor; end = &VIsual; } - if (hasFolding(start->lnum, &start->lnum, NULL)) { + if (hasFolding(curwin, start->lnum, &start->lnum, NULL)) { start->col = 0; } - if (!hasFolding(end->lnum, NULL, &end->lnum)) { + if (!hasFolding(curwin, end->lnum, NULL, &end->lnum)) { return; } @@ -1028,9 +1028,9 @@ void foldAdjustVisual(void) // cursor_foldstart() {{{2 /// Move the cursor to the first line of a closed fold. -void foldAdjustCursor(void) +void foldAdjustCursor(win_T *wp) { - hasFolding(curwin->w_cursor.lnum, &curwin->w_cursor.lnum, NULL); + hasFolding(wp, wp->w_cursor.lnum, &wp->w_cursor.lnum, NULL); } // Internal functions for "fold_T" {{{1 @@ -1269,7 +1269,7 @@ static linenr_T setManualFoldWin(win_T *wp, linenr_T lnum, bool opening, bool re } wp->w_fold_manual = true; if (done & DONE_ACTION) { - changed_window_setting_win(wp); + changed_window_setting(wp); } done |= DONE_FOLD; } else if (donep == NULL && wp == curwin) { @@ -2117,7 +2117,7 @@ static void foldUpdateIEMS(win_T *const wp, linenr_T top, linenr_T bot) // If some fold changed, need to redraw and position cursor. if (fold_changed && wp->w_p_fen) { - changed_window_setting_win(wp); + changed_window_setting(wp); } // If we updated folds past "bot", need to redraw more lines. Don't do diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 64c9c5a8c3..f68bd7098b 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -2508,7 +2508,7 @@ static int vgetorpeek(bool advance) unshowmode(true); mode_deleted = true; } - validate_cursor(); + validate_cursor(curwin); int old_wcol = curwin->w_wcol; int old_wrow = curwin->w_wrow; @@ -2541,7 +2541,7 @@ static int vgetorpeek(bool advance) curwin->w_wrow = curwin->w_cline_row + curwin->w_wcol / curwin->w_width_inner; curwin->w_wcol %= curwin->w_width_inner; - curwin->w_wcol += curwin_col_off(); + curwin->w_wcol += win_col_off(curwin); col = 0; // no correction needed } else { curwin->w_wcol--; diff --git a/src/nvim/indent.c b/src/nvim/indent.c index 14247b6d86..6cbb86866e 100644 --- a/src/nvim/indent.c +++ b/src/nvim/indent.c @@ -1116,7 +1116,7 @@ void ex_retab(exarg_T *eap) } xfree(new_ts_str); } - coladvance(curwin->w_curswant); + coladvance(curwin, curwin->w_curswant); u_clearline(curbuf); } @@ -1160,7 +1160,7 @@ int get_expr_indent(void) curwin->w_cursor = save_pos; curwin->w_curswant = save_curswant; curwin->w_set_curswant = save_set_curswant; - check_cursor(); + check_cursor(curwin); State = save_State; // Reset did_throw, unless 'debug' has "throw" and inside a try/catch. diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index d0cd24773f..a1f341f404 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -2436,7 +2436,7 @@ static void expand_by_function(int type, char *base) textlock--; curwin->w_cursor = pos; // restore the cursor position - validate_cursor(); + validate_cursor(curwin); if (!equalpos(curwin->w_cursor, pos)) { emsg(_(e_compldel)); goto theend; @@ -4096,7 +4096,7 @@ static int get_userdefined_compl_info(colnr_T curs_col) State = save_State; curwin->w_cursor = pos; // restore the cursor position - validate_cursor(); + validate_cursor(curwin); if (!equalpos(curwin->w_cursor, pos)) { emsg(_(e_compldel)); return FAIL; diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 08677b77b0..78c746d169 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -1797,7 +1797,7 @@ void ex_luado(exarg_T *const eap) } lua_pop(lstate, 1); - check_cursor(); + check_cursor(curwin); redraw_curbuf_later(UPD_NOT_VALID); } diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 34e35a8277..0ecdd88ebd 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -588,7 +588,7 @@ MarkMoveRes mark_move_to(fmark_T *fm, MarkMove flags) } if (res & kMarkSwitchedBuf || res & kMarkChangedCursor) { - check_cursor(); + check_cursor(curwin); } end: return res; diff --git a/src/nvim/match.c b/src/nvim/match.c index c8837969b6..ea8a1a05f4 100644 --- a/src/nvim/match.c +++ b/src/nvim/match.c @@ -533,7 +533,7 @@ void prepare_search_hl(win_T *wp, match_T *search_hl, linenr_T lnum) for (shl->first_lnum = lnum; shl->first_lnum > wp->w_topline; shl->first_lnum--) { - if (hasFoldingWin(wp, shl->first_lnum - 1, NULL, NULL, true, NULL)) { + if (hasFolding(wp, shl->first_lnum - 1, NULL, NULL)) { break; } } diff --git a/src/nvim/memline.c b/src/nvim/memline.c index a63c23f0a3..ca47f6aa98 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -1192,7 +1192,7 @@ void ml_recover(bool checkext) ml_delete(curbuf->b_ml.ml_line_count, false); } curbuf->b_flags |= BF_RECOVERED; - check_cursor(); + check_cursor(curwin); recoverymode = false; if (got_int) { @@ -4076,14 +4076,14 @@ void goto_byte(int cnt) if (lnum < 1) { // past the end curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; curwin->w_curswant = MAXCOL; - coladvance(MAXCOL); + coladvance(curwin, MAXCOL); } else { curwin->w_cursor.lnum = lnum; curwin->w_cursor.col = (colnr_T)boff; curwin->w_cursor.coladd = 0; curwin->w_set_curswant = true; } - check_cursor(); + check_cursor(curwin); // Make sure the cursor is on the first byte of a multi-byte char. mb_adjust_cursor(); diff --git a/src/nvim/menu.c b/src/nvim/menu.c index 4ca2a61ab1..ab28eeca1c 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -1478,11 +1478,11 @@ void execute_menu(const exarg_T *eap, vimmenu_T *menu, int mode_idx) // Activate visual mode VIsual_active = true; VIsual_reselect = true; - check_cursor(); + check_cursor(curwin); VIsual = curwin->w_cursor; curwin->w_cursor = tpos; - check_cursor(); + check_cursor(curwin); // Adjust the cursor to make sure it is in the correct pos // for exclusive mode diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index 506a428243..d82ba58918 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -771,7 +771,7 @@ popupexit: // move VIsual to the right column start_visual = curwin->w_cursor; // save the cursor pos curwin->w_cursor = end_visual; - coladvance(end_visual.col); + coladvance(curwin, end_visual.col); VIsual = curwin->w_cursor; curwin->w_cursor = start_visual; // restore the cursor } else { @@ -1430,7 +1430,7 @@ retnomove: break; } first = false; - hasFolding(curwin->w_topline, &curwin->w_topline, NULL); + hasFolding(curwin, curwin->w_topline, &curwin->w_topline, NULL); if (curwin->w_topfill < win_get_fill(curwin, curwin->w_topline)) { curwin->w_topfill++; } else { @@ -1460,7 +1460,7 @@ retnomove: if (curwin->w_topfill > 0) { curwin->w_topfill--; } else { - if (hasFolding(curwin->w_topline, NULL, &curwin->w_topline) + if (hasFolding(curwin, curwin->w_topline, NULL, &curwin->w_topline) && curwin->w_topline == curbuf->b_ml.ml_line_count) { break; } @@ -1515,7 +1515,7 @@ retnomove: curwin->w_curswant = col; curwin->w_set_curswant = false; // May still have been true - if (coladvance(col) == FAIL) { // Mouse click beyond end of line + if (coladvance(curwin, col) == FAIL) { // Mouse click beyond end of line if (inclusive != NULL) { *inclusive = true; } @@ -1548,7 +1548,7 @@ static bool do_mousescroll_horiz(colnr_T leftcol) // When the line of the cursor is too short, move the cursor to the // longest visible line. - if (!virtual_active() + if (!virtual_active(curwin) && leftcol > scroll_line_len(curwin->w_cursor.lnum)) { curwin->w_cursor.lnum = find_longest_lnum(); curwin->w_cursor.col = 0; @@ -1637,7 +1637,7 @@ bool mouse_comp_pos(win_T *win, int *rowp, int *colp, linenr_T *lnump) break; // Position is in this buffer line. } - hasFoldingWin(win, lnum, NULL, &lnum, true, NULL); + hasFolding(win, lnum, NULL, &lnum); if (lnum == win->w_buffer->b_ml.ml_line_count) { retval = true; diff --git a/src/nvim/move.c b/src/nvim/move.c index 0f7f4d8719..3c4da7f8ac 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -182,7 +182,7 @@ static void redraw_for_cursorcolumn(win_T *wp) // When current buffer's cursor moves in Visual mode, redraw it with UPD_INVERTED. if (VIsual_active && wp->w_buffer == curbuf) { - redraw_curbuf_later(UPD_INVERTED); + redraw_buf_later(curbuf, UPD_INVERTED); } } @@ -332,7 +332,7 @@ void update_topline(win_T *wp) if (lnum >= wp->w_buffer->b_ml.ml_line_count || n >= halfheight) { break; } - hasFoldingWin(wp, lnum, NULL, &lnum, true, NULL); + hasFolding(wp, lnum, NULL, &lnum); } } else { n = wp->w_topline + *so_ptr - wp->w_cursor.lnum; @@ -342,14 +342,14 @@ void update_topline(win_T *wp) // cursor in the middle of the window. Otherwise put the cursor // near the top of the window. if (n >= halfheight) { - scroll_cursor_halfway(false, false); + scroll_cursor_halfway(wp, false, false); } else { - scroll_cursor_top(scrolljump_value(), false); + scroll_cursor_top(wp, scrolljump_value(), false); check_botline = true; } } else { // Make sure topline is the first line of a fold. - hasFoldingWin(wp, wp->w_topline, &wp->w_topline, NULL, true, NULL); + hasFolding(wp, wp->w_topline, &wp->w_topline, NULL); check_botline = true; } } @@ -377,7 +377,7 @@ void update_topline(win_T *wp) int n = wp->w_empty_rows; loff.lnum = wp->w_cursor.lnum; // In a fold go to its last line. - hasFoldingWin(wp, loff.lnum, NULL, &loff.lnum, true, NULL); + hasFolding(wp, loff.lnum, NULL, &loff.lnum); loff.fill = 0; n += wp->w_filler_rows; loff.height = 0; @@ -411,15 +411,15 @@ void update_topline(win_T *wp) if (lnum <= 0 || line_count > wp->w_height_inner + 1) { break; } - hasFolding(lnum, &lnum, NULL); + hasFolding(wp, lnum, &lnum, NULL); } } else { line_count = wp->w_cursor.lnum - wp->w_botline + 1 + (int)(*so_ptr); } if (line_count <= wp->w_height_inner + 1) { - scroll_cursor_bot(scrolljump_value(), false); + scroll_cursor_bot(wp, scrolljump_value(), false); } else { - scroll_cursor_halfway(false, false); + scroll_cursor_halfway(wp, false, false); } } } @@ -443,7 +443,7 @@ void update_topline(win_T *wp) // May need to set w_skipcol when cursor in w_topline. if (wp->w_cursor.lnum == wp->w_topline) { - validate_cursor(); + validate_cursor(wp); } } @@ -491,7 +491,7 @@ static bool check_top_offset(void) /// Update w_curswant. void update_curswant_force(void) { - validate_virtcol(); + validate_virtcol(curwin); curwin->w_curswant = curwin->w_virtcol; curwin->w_set_curswant = false; } @@ -536,12 +536,7 @@ void check_cursor_moved(win_T *wp) // Call this function when some window settings have changed, which require // the cursor position, botline and topline to be recomputed and the window to // be redrawn. E.g, when changing the 'wrap' option or folding. -void changed_window_setting(void) -{ - changed_window_setting_win(curwin); -} - -void changed_window_setting_win(win_T *wp) +void changed_window_setting(win_T *wp) { wp->w_lines_valid = 0; changed_line_abv_curs_win(wp); @@ -555,7 +550,7 @@ void set_topline(win_T *wp, linenr_T lnum) linenr_T prev_topline = wp->w_topline; // go to first of folded lines - hasFoldingWin(wp, lnum, &lnum, NULL, true, NULL); + hasFolding(wp, lnum, &lnum, NULL); // Approximate the value of w_botline wp->w_botline += lnum - wp->w_topline; wp->w_topline = lnum; @@ -614,21 +609,21 @@ void approximate_botline_win(win_T *wp) wp->w_valid &= ~VALID_BOTLINE; } -// Return true if curwin->w_wrow and curwin->w_wcol are valid. -int cursor_valid(void) +// Return true if wp->w_wrow and wp->w_wcol are valid. +int cursor_valid(win_T *wp) { - check_cursor_moved(curwin); - return (curwin->w_valid & (VALID_WROW|VALID_WCOL)) == (VALID_WROW|VALID_WCOL); + check_cursor_moved(wp); + return (wp->w_valid & (VALID_WROW|VALID_WCOL)) == (VALID_WROW|VALID_WCOL); } // Validate cursor position. Makes sure w_wrow and w_wcol are valid. // w_topline must be valid, you may need to call update_topline() first! -void validate_cursor(void) +void validate_cursor(win_T *wp) { - check_cursor(); - check_cursor_moved(curwin); - if ((curwin->w_valid & (VALID_WCOL|VALID_WROW)) != (VALID_WCOL|VALID_WROW)) { - curs_columns(curwin, true); + check_cursor(wp); + check_cursor_moved(wp); + if ((wp->w_valid & (VALID_WCOL|VALID_WROW)) != (VALID_WCOL|VALID_WROW)) { + curs_columns(wp, true); } } @@ -692,26 +687,19 @@ static void curs_rows(win_T *wp) } else if (i > wp->w_lines_valid) { // a line that is too long to fit on the last screen line wp->w_cline_height = 0; - wp->w_cline_folded = hasFoldingWin(wp, wp->w_cursor.lnum, NULL, - NULL, true, NULL); + wp->w_cline_folded = hasFolding(wp, wp->w_cursor.lnum, NULL, NULL); } else { wp->w_cline_height = wp->w_lines[i].wl_size; wp->w_cline_folded = wp->w_lines[i].wl_folded; } } - redraw_for_cursorline(curwin); + redraw_for_cursorline(wp); wp->w_valid |= VALID_CROW|VALID_CHEIGHT; } -// Validate curwin->w_virtcol only. -void validate_virtcol(void) -{ - validate_virtcol_win(curwin); -} - // Validate wp->w_virtcol only. -void validate_virtcol_win(win_T *wp) +void validate_virtcol(win_T *wp) { check_cursor_moved(wp); @@ -724,49 +712,48 @@ void validate_virtcol_win(win_T *wp) wp->w_valid |= VALID_VIRTCOL; } -// Validate curwin->w_cline_height only. -void validate_cheight(void) +// Validate wp->w_cline_height only. +void validate_cheight(win_T *wp) { - check_cursor_moved(curwin); + check_cursor_moved(wp); - if (curwin->w_valid & VALID_CHEIGHT) { + if (wp->w_valid & VALID_CHEIGHT) { return; } - curwin->w_cline_height = plines_win_full(curwin, curwin->w_cursor.lnum, - NULL, &curwin->w_cline_folded, - true, true); - curwin->w_valid |= VALID_CHEIGHT; + wp->w_cline_height = plines_win_full(wp, wp->w_cursor.lnum, + NULL, &wp->w_cline_folded, + true, true); + wp->w_valid |= VALID_CHEIGHT; } // Validate w_wcol and w_virtcol only. -void validate_cursor_col(void) +void validate_cursor_col(win_T *wp) { - validate_virtcol(); + validate_virtcol(wp); - if (curwin->w_valid & VALID_WCOL) { + if (wp->w_valid & VALID_WCOL) { return; } - colnr_T col = curwin->w_virtcol; - colnr_T off = curwin_col_off(); + colnr_T col = wp->w_virtcol; + colnr_T off = win_col_off(wp); col += off; - int width = curwin->w_width_inner - off + curwin_col_off2(); + int width = wp->w_width_inner - off + win_col_off2(wp); - // long line wrapping, adjust curwin->w_wrow - if (curwin->w_p_wrap && col >= (colnr_T)curwin->w_width_inner - && width > 0) { + // long line wrapping, adjust wp->w_wrow + if (wp->w_p_wrap && col >= (colnr_T)wp->w_width_inner && width > 0) { // use same formula as what is used in curs_columns() - col -= ((col - curwin->w_width_inner) / width + 1) * width; + col -= ((col - wp->w_width_inner) / width + 1) * width; } - if (col > (int)curwin->w_leftcol) { - col -= curwin->w_leftcol; + if (col > (int)wp->w_leftcol) { + col -= wp->w_leftcol; } else { col = 0; } - curwin->w_wcol = col; + wp->w_wcol = col; - curwin->w_valid |= VALID_WCOL; + wp->w_valid |= VALID_WCOL; } // Compute offset of a window, occupied by absolute or relative line number, @@ -779,11 +766,6 @@ int win_col_off(win_T *wp) + win_fdccol_count(wp) + (wp->w_scwidth * SIGN_WIDTH); } -int curwin_col_off(void) -{ - return win_col_off(curwin); -} - // Return the difference in column offset for the second screen line of a // wrapped line. It's positive if 'number' or 'relativenumber' is on and 'n' // is in 'cpoptions'. @@ -796,11 +778,6 @@ int win_col_off2(win_T *wp) return 0; } -int curwin_col_off2(void) -{ - return win_col_off2(curwin); -} - // Compute wp->w_wcol and wp->w_virtcol. // Also updates wp->w_wrow and wp->w_cline_row. // Also updates wp->w_leftcol. @@ -896,7 +873,7 @@ void curs_columns(win_T *wp, int may_scroll) // middle of window. int new_leftcol; if (p_ss == 0 || diff >= width1 / 2 || off_right >= off_left) { - new_leftcol = curwin->w_wcol - extra - width1 / 2; + new_leftcol = wp->w_wcol - extra - width1 / 2; } else { if (diff < p_ss) { assert(p_ss <= INT_MAX); @@ -984,9 +961,9 @@ void curs_columns(win_T *wp, int may_scroll) n = plines - wp->w_height_inner + 1; } if (n > 0) { - curwin->w_skipcol = width1 + (n - 1) * width2; + wp->w_skipcol = width1 + (n - 1) * width2; } else { - curwin->w_skipcol = 0; + wp->w_skipcol = 0; } } else if (extra == 1) { // less than 'scrolloff' lines above, decrease skipcol @@ -1063,7 +1040,7 @@ void textpos2screenpos(win_T *wp, pos_T *pos, int *rowp, int *scolp, int *ccolp, linenr_T lnum = pos->lnum; if (lnum >= wp->w_topline && lnum <= wp->w_botline) { - is_folded = hasFoldingWin(wp, lnum, &lnum, NULL, true, NULL); + is_folded = hasFolding(wp, lnum, &lnum, NULL); row = plines_m_win(wp, wp->w_topline, lnum - 1, false); // "row" should be the screen line where line "lnum" begins, which can // be negative if "lnum" is "w_topline" and "w_skipcol" is non-zero. @@ -1207,128 +1184,128 @@ void f_virtcol2col(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) rettv->vval.v_number = virtcol2col(wp, lnum, screencol); } -/// Scroll the current window down by "line_count" logical lines. "CTRL-Y" +/// Scroll a window down by "line_count" logical lines. "CTRL-Y" /// /// @param line_count number of lines to scroll /// @param byfold if true, count a closed fold as one line -bool scrolldown(linenr_T line_count, int byfold) +bool scrolldown(win_T *wp, linenr_T line_count, int byfold) { int done = 0; // total # of physical lines done int width1 = 0; int width2 = 0; - bool do_sms = curwin->w_p_wrap && curwin->w_p_sms; + bool do_sms = wp->w_p_wrap && wp->w_p_sms; if (do_sms) { - width1 = curwin->w_width_inner - curwin_col_off(); - width2 = width1 + curwin_col_off2(); + width1 = wp->w_width_inner - win_col_off(wp); + width2 = width1 + win_col_off2(wp); } // Make sure w_topline is at the first of a sequence of folded lines. - hasFolding(curwin->w_topline, &curwin->w_topline, NULL); - validate_cursor(); // w_wrow needs to be valid + hasFolding(wp, wp->w_topline, &wp->w_topline, NULL); + validate_cursor(wp); // w_wrow needs to be valid for (int todo = line_count; todo > 0; todo--) { - if (curwin->w_topfill < win_get_fill(curwin, curwin->w_topline) - && curwin->w_topfill < curwin->w_height_inner - 1) { - curwin->w_topfill++; + if (wp->w_topfill < win_get_fill(wp, wp->w_topline) + && wp->w_topfill < wp->w_height_inner - 1) { + wp->w_topfill++; done++; } else { // break when at the very top - if (curwin->w_topline == 1 && (!do_sms || curwin->w_skipcol < width1)) { + if (wp->w_topline == 1 && (!do_sms || wp->w_skipcol < width1)) { break; } - if (do_sms && curwin->w_skipcol >= width1) { + if (do_sms && wp->w_skipcol >= width1) { // scroll a screen line down - if (curwin->w_skipcol >= width1 + width2) { - curwin->w_skipcol -= width2; + if (wp->w_skipcol >= width1 + width2) { + wp->w_skipcol -= width2; } else { - curwin->w_skipcol -= width1; + wp->w_skipcol -= width1; } - redraw_later(curwin, UPD_NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); done++; } else { // scroll a text line down - curwin->w_topline--; - curwin->w_skipcol = 0; - curwin->w_topfill = 0; + wp->w_topline--; + wp->w_skipcol = 0; + wp->w_topfill = 0; // A sequence of folded lines only counts for one logical line linenr_T first; - if (hasFolding(curwin->w_topline, &first, NULL)) { + if (hasFolding(wp, wp->w_topline, &first, NULL)) { done++; if (!byfold) { - todo -= curwin->w_topline - first - 1; + todo -= wp->w_topline - first - 1; } - curwin->w_botline -= curwin->w_topline - first; - curwin->w_topline = first; + wp->w_botline -= wp->w_topline - first; + wp->w_topline = first; } else { if (do_sms) { - int size = win_linetabsize(curwin, curwin->w_topline, - ml_get(curwin->w_topline), MAXCOL); + int size = win_linetabsize(wp, wp->w_topline, + ml_get(wp->w_topline), MAXCOL); if (size > width1) { - curwin->w_skipcol = width1; + wp->w_skipcol = width1; size -= width1; - redraw_later(curwin, UPD_NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); } while (size > width2) { - curwin->w_skipcol += width2; + wp->w_skipcol += width2; size -= width2; } done++; } else { - done += plines_win_nofill(curwin, curwin->w_topline, true); + done += plines_win_nofill(wp, wp->w_topline, true); } } } } - curwin->w_botline--; // approximate w_botline - invalidate_botline(curwin); + wp->w_botline--; // approximate w_botline + invalidate_botline(wp); } - curwin->w_wrow += done; // keep w_wrow updated - curwin->w_cline_row += done; // keep w_cline_row updated + wp->w_wrow += done; // keep w_wrow updated + wp->w_cline_row += done; // keep w_cline_row updated - if (curwin->w_cursor.lnum == curwin->w_topline) { - curwin->w_cline_row = 0; + if (wp->w_cursor.lnum == wp->w_topline) { + wp->w_cline_row = 0; } - check_topfill(curwin, true); + check_topfill(wp, true); // Compute the row number of the last row of the cursor line // and move the cursor onto the displayed part of the window. - int wrow = curwin->w_wrow; - if (curwin->w_p_wrap && curwin->w_width_inner != 0) { - validate_virtcol(); - validate_cheight(); - wrow += curwin->w_cline_height - 1 - - curwin->w_virtcol / curwin->w_width_inner; + int wrow = wp->w_wrow; + if (wp->w_p_wrap && wp->w_width_inner != 0) { + validate_virtcol(wp); + validate_cheight(wp); + wrow += wp->w_cline_height - 1 - + wp->w_virtcol / wp->w_width_inner; } bool moved = false; - while (wrow >= curwin->w_height_inner && curwin->w_cursor.lnum > 1) { + while (wrow >= wp->w_height_inner && wp->w_cursor.lnum > 1) { linenr_T first; - if (hasFolding(curwin->w_cursor.lnum, &first, NULL)) { + if (hasFolding(wp, wp->w_cursor.lnum, &first, NULL)) { wrow--; if (first == 1) { - curwin->w_cursor.lnum = 1; + wp->w_cursor.lnum = 1; } else { - curwin->w_cursor.lnum = first - 1; + wp->w_cursor.lnum = first - 1; } } else { - wrow -= plines_win(curwin, curwin->w_cursor.lnum--, true); + wrow -= plines_win(wp, wp->w_cursor.lnum--, true); } - curwin->w_valid &= + wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_CHEIGHT|VALID_CROW|VALID_VIRTCOL); moved = true; } if (moved) { // Move cursor to first line of closed fold. - foldAdjustCursor(); - coladvance(curwin->w_curswant); + foldAdjustCursor(wp); + coladvance(wp, wp->w_curswant); } - if (curwin->w_cursor.lnum == curwin->w_topline && do_sms) { - int so = get_scrolloff_value(curwin); + if (wp->w_cursor.lnum == wp->w_topline && do_sms) { + int so = get_scrolloff_value(wp); colnr_T scrolloff_cols = so == 0 ? 0 : width1 + (so - 1) * width2; // make sure the cursor is in the visible text - validate_virtcol(); - colnr_T col = curwin->w_virtcol - curwin->w_skipcol + scrolloff_cols; + validate_virtcol(wp); + colnr_T col = wp->w_virtcol - wp->w_skipcol + scrolloff_cols; int row = 0; if (col >= width1) { col -= width1; @@ -1337,32 +1314,32 @@ bool scrolldown(linenr_T line_count, int byfold) if (col > width2 && width2 > 0) { row += (int)col / width2; } - if (row >= curwin->w_height_inner) { - curwin->w_curswant = curwin->w_virtcol - (row - curwin->w_height_inner + 1) * width2; - coladvance(curwin->w_curswant); + if (row >= wp->w_height_inner) { + wp->w_curswant = wp->w_virtcol - (row - wp->w_height_inner + 1) * width2; + coladvance(wp, wp->w_curswant); } } return moved; } -/// Scroll the current window up by "line_count" logical lines. "CTRL-E" +/// Scroll a window up by "line_count" logical lines. "CTRL-E" /// /// @param line_count number of lines to scroll /// @param byfold if true, count a closed fold as one line -bool scrollup(linenr_T line_count, bool byfold) +bool scrollup(win_T *wp, linenr_T line_count, bool byfold) { - linenr_T topline = curwin->w_topline; - linenr_T botline = curwin->w_botline; - bool do_sms = curwin->w_p_wrap && curwin->w_p_sms; + linenr_T topline = wp->w_topline; + linenr_T botline = wp->w_botline; + bool do_sms = wp->w_p_wrap && wp->w_p_sms; - if (do_sms || (byfold && hasAnyFolding(curwin)) || win_may_fill(curwin)) { - int width1 = curwin->w_width_inner - curwin_col_off(); - int width2 = width1 + curwin_col_off2(); + if (do_sms || (byfold && hasAnyFolding(wp)) || win_may_fill(wp)) { + int width1 = wp->w_width_inner - win_col_off(wp); + int width2 = width1 + win_col_off2(wp); int size = 0; - const colnr_T prev_skipcol = curwin->w_skipcol; + const colnr_T prev_skipcol = wp->w_skipcol; if (do_sms) { - size = linetabsize(curwin, curwin->w_topline); + size = linetabsize(wp, wp->w_topline); } // diff mode: first consume "topfill" @@ -1370,93 +1347,93 @@ bool scrollup(linenr_T line_count, bool byfold) // the line, then advance to the next line. // folding: count each sequence of folded lines as one logical line. for (int todo = line_count; todo > 0; todo--) { - if (curwin->w_topfill > 0) { - curwin->w_topfill--; + if (wp->w_topfill > 0) { + wp->w_topfill--; } else { - linenr_T lnum = curwin->w_topline; + linenr_T lnum = wp->w_topline; if (byfold) { // for a closed fold: go to the last line in the fold - hasFolding(lnum, NULL, &lnum); + hasFolding(wp, lnum, NULL, &lnum); } - if (lnum == curwin->w_topline && do_sms) { + if (lnum == wp->w_topline && do_sms) { // 'smoothscroll': increase "w_skipcol" until it goes over // the end of the line, then advance to the next line. - int add = curwin->w_skipcol > 0 ? width2 : width1; - curwin->w_skipcol += add; - if (curwin->w_skipcol >= size) { - if (lnum == curbuf->b_ml.ml_line_count) { + int add = wp->w_skipcol > 0 ? width2 : width1; + wp->w_skipcol += add; + if (wp->w_skipcol >= size) { + if (lnum == wp->w_buffer->b_ml.ml_line_count) { // at the last screen line, can't scroll further - curwin->w_skipcol -= add; + wp->w_skipcol -= add; break; } lnum++; } } else { - if (lnum >= curbuf->b_ml.ml_line_count) { + if (lnum >= wp->w_buffer->b_ml.ml_line_count) { break; } lnum++; } - if (lnum > curwin->w_topline) { + if (lnum > wp->w_topline) { // approximate w_botline - curwin->w_botline += lnum - curwin->w_topline; - curwin->w_topline = lnum; - curwin->w_topfill = win_get_fill(curwin, lnum); - curwin->w_skipcol = 0; + wp->w_botline += lnum - wp->w_topline; + wp->w_topline = lnum; + wp->w_topfill = win_get_fill(wp, lnum); + wp->w_skipcol = 0; if (todo > 1 && do_sms) { - size = linetabsize(curwin, curwin->w_topline); + size = linetabsize(wp, wp->w_topline); } } } } - if (prev_skipcol > 0 || curwin->w_skipcol > 0) { + if (prev_skipcol > 0 || wp->w_skipcol > 0) { // need to redraw more, because wl_size of the (new) topline may // now be invalid - redraw_later(curwin, UPD_NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); } } else { - curwin->w_topline += line_count; - curwin->w_botline += line_count; // approximate w_botline + wp->w_topline += line_count; + wp->w_botline += line_count; // approximate w_botline } - if (curwin->w_topline > curbuf->b_ml.ml_line_count) { - curwin->w_topline = curbuf->b_ml.ml_line_count; + if (wp->w_topline > wp->w_buffer->b_ml.ml_line_count) { + wp->w_topline = wp->w_buffer->b_ml.ml_line_count; } - if (curwin->w_botline > curbuf->b_ml.ml_line_count + 1) { - curwin->w_botline = curbuf->b_ml.ml_line_count + 1; + if (wp->w_botline > wp->w_buffer->b_ml.ml_line_count + 1) { + wp->w_botline = wp->w_buffer->b_ml.ml_line_count + 1; } - check_topfill(curwin, false); + check_topfill(wp, false); - if (hasAnyFolding(curwin)) { + if (hasAnyFolding(wp)) { // Make sure w_topline is at the first of a sequence of folded lines. - hasFolding(curwin->w_topline, &curwin->w_topline, NULL); + hasFolding(wp, wp->w_topline, &wp->w_topline, NULL); } - curwin->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE); - if (curwin->w_cursor.lnum < curwin->w_topline) { - curwin->w_cursor.lnum = curwin->w_topline; - curwin->w_valid &= + wp->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE); + if (wp->w_cursor.lnum < wp->w_topline) { + wp->w_cursor.lnum = wp->w_topline; + wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_CHEIGHT|VALID_CROW|VALID_VIRTCOL); - coladvance(curwin->w_curswant); + coladvance(wp, wp->w_curswant); } - if (curwin->w_cursor.lnum == curwin->w_topline && do_sms && curwin->w_skipcol > 0) { - int col_off = curwin_col_off(); - int col_off2 = curwin_col_off2(); + if (wp->w_cursor.lnum == wp->w_topline && do_sms && wp->w_skipcol > 0) { + int col_off = win_col_off(wp); + int col_off2 = win_col_off2(wp); - int width1 = curwin->w_width_inner - col_off; + int width1 = wp->w_width_inner - col_off; int width2 = width1 + col_off2; int extra2 = col_off - col_off2; - int so = get_scrolloff_value(curwin); + int so = get_scrolloff_value(wp); colnr_T scrolloff_cols = so == 0 ? 0 : width1 + (so - 1) * width2; - int space_cols = (curwin->w_height_inner - 1) * width2; + int space_cols = (wp->w_height_inner - 1) * width2; // If we have non-zero scrolloff, just ignore the marker as we are // going past it anyway. - int overlap = scrolloff_cols != 0 ? 0 : sms_marker_overlap(curwin, extra2); + int overlap = scrolloff_cols != 0 ? 0 : sms_marker_overlap(wp, extra2); // Make sure the cursor is in a visible part of the line, taking // 'scrolloff' into account, but using screen lines. @@ -1464,26 +1441,26 @@ bool scrollup(linenr_T line_count, bool byfold) if (scrolloff_cols > space_cols / 2) { scrolloff_cols = space_cols / 2; } - validate_virtcol(); - if (curwin->w_virtcol < curwin->w_skipcol + overlap + scrolloff_cols) { - colnr_T col = curwin->w_virtcol; + validate_virtcol(wp); + if (wp->w_virtcol < wp->w_skipcol + overlap + scrolloff_cols) { + colnr_T col = wp->w_virtcol; if (col < width1) { col += width1; } - while (col < curwin->w_skipcol + overlap + scrolloff_cols) { + while (col < wp->w_skipcol + overlap + scrolloff_cols) { col += width2; } - curwin->w_curswant = col; - coladvance(curwin->w_curswant); + wp->w_curswant = col; + coladvance(wp, wp->w_curswant); // validate_virtcol() marked various things as valid, but after // moving the cursor they need to be recomputed - curwin->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_CHEIGHT|VALID_CROW|VALID_VIRTCOL); + wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_CHEIGHT|VALID_CROW|VALID_VIRTCOL); } } - bool moved = topline != curwin->w_topline || botline != curwin->w_botline; + bool moved = topline != wp->w_topline || botline != wp->w_botline; return moved; } @@ -1496,16 +1473,16 @@ void adjust_skipcol(void) return; } - int width1 = curwin->w_width_inner - curwin_col_off(); + int width1 = curwin->w_width_inner - win_col_off(curwin); if (width1 <= 0) { return; // no text will be displayed } - int width2 = width1 + curwin_col_off2(); + int width2 = width1 + win_col_off2(curwin); int so = get_scrolloff_value(curwin); colnr_T scrolloff_cols = so == 0 ? 0 : width1 + (so - 1) * width2; bool scrolled = false; - validate_cheight(); + validate_cheight(curwin); if (curwin->w_cline_height == curwin->w_height_inner // w_cline_height may be capped at w_height_inner, check there aren't // actually more lines. @@ -1515,8 +1492,8 @@ void adjust_skipcol(void) return; } - validate_virtcol(); - int overlap = sms_marker_overlap(curwin, curwin_col_off() - curwin_col_off2()); + validate_virtcol(curwin); + int overlap = sms_marker_overlap(curwin, win_col_off(curwin) - win_col_off2(curwin)); while (curwin->w_skipcol > 0 && curwin->w_virtcol < curwin->w_skipcol + overlap + scrolloff_cols) { // scroll a screen line down @@ -1528,7 +1505,7 @@ void adjust_skipcol(void) scrolled = true; } if (scrolled) { - validate_virtcol(); + validate_virtcol(curwin); redraw_later(curwin, UPD_NOT_VALID); return; // don't scroll in the other direction now } @@ -1572,7 +1549,7 @@ void check_topfill(win_T *wp, bool down) } } } - win_check_anchored_floats(curwin); + win_check_anchored_floats(wp); } // Use as many filler lines as possible for w_topline. Make sure w_topline @@ -1601,7 +1578,7 @@ void scrolldown_clamp(void) return; } - validate_cursor(); // w_wrow needs to be valid + validate_cursor(curwin); // w_wrow needs to be valid // Compute the row number of the last row of the cursor line // and make sure it doesn't go off the screen. Make sure the cursor @@ -1613,8 +1590,8 @@ void scrolldown_clamp(void) end_row += plines_win_nofill(curwin, curwin->w_topline - 1, true); } if (curwin->w_p_wrap && curwin->w_width_inner != 0) { - validate_cheight(); - validate_virtcol(); + validate_cheight(curwin); + validate_virtcol(curwin); end_row += curwin->w_cline_height - 1 - curwin->w_virtcol / curwin->w_width_inner; } @@ -1626,7 +1603,7 @@ void scrolldown_clamp(void) curwin->w_topline--; curwin->w_topfill = 0; } - hasFolding(curwin->w_topline, &curwin->w_topline, NULL); + hasFolding(curwin, curwin->w_topline, &curwin->w_topline, NULL); curwin->w_botline--; // approximate w_botline curwin->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE); } @@ -1641,7 +1618,7 @@ void scrollup_clamp(void) return; } - validate_cursor(); // w_wrow needs to be valid + validate_cursor(curwin); // w_wrow needs to be valid // Compute the row number of the first row of the cursor line // and make sure it doesn't go off the screen. Make sure the cursor @@ -1650,14 +1627,14 @@ void scrollup_clamp(void) - plines_win_nofill(curwin, curwin->w_topline, true) - curwin->w_topfill); if (curwin->w_p_wrap && curwin->w_width_inner != 0) { - validate_virtcol(); + validate_virtcol(curwin); start_row -= curwin->w_virtcol / curwin->w_width_inner; } if (start_row >= get_scrolloff_value(curwin)) { if (curwin->w_topfill > 0) { curwin->w_topfill--; } else { - hasFolding(curwin->w_topline, NULL, &curwin->w_topline); + hasFolding(curwin, curwin->w_topline, NULL, &curwin->w_topline); curwin->w_topline++; } curwin->w_botline++; // approximate w_botline @@ -1681,7 +1658,7 @@ static void topline_back_winheight(win_T *wp, lineoff_T *lp, int winheight) lp->fill = 0; if (lp->lnum < 1) { lp->height = MAXCOL; - } else if (hasFolding(lp->lnum, &lp->lnum, NULL)) { + } else if (hasFolding(wp, lp->lnum, &lp->lnum, NULL)) { // Add a closed fold lp->height = 1; } else { @@ -1711,7 +1688,7 @@ static void botline_forw(win_T *wp, lineoff_T *lp) assert(wp->w_buffer != 0); if (lp->lnum > wp->w_buffer->b_ml.ml_line_count) { lp->height = MAXCOL; - } else if (hasFoldingWin(wp, lp->lnum, NULL, &lp->lnum, true, NULL)) { + } else if (hasFolding(wp, lp->lnum, NULL, &lp->lnum)) { // Add a closed fold lp->height = 1; } else { @@ -1745,12 +1722,12 @@ static void topline_botline(lineoff_T *lp) // Recompute topline to put the cursor at the top of the window. // Scroll at least "min_scroll" lines. // If "always" is true, always set topline (for "zt"). -void scroll_cursor_top(int min_scroll, int always) +void scroll_cursor_top(win_T *wp, int min_scroll, int always) { - linenr_T old_topline = curwin->w_topline; - int old_skipcol = curwin->w_skipcol; - linenr_T old_topfill = curwin->w_topfill; - int off = get_scrolloff_value(curwin); + linenr_T old_topline = wp->w_topline; + int old_skipcol = wp->w_skipcol; + linenr_T old_topfill = wp->w_topfill; + int off = get_scrolloff_value(wp); if (mouse_dragging > 0) { off = mouse_dragging - 1; @@ -1761,54 +1738,54 @@ void scroll_cursor_top(int min_scroll, int always) // - (part of) the cursor line is moved off the screen or // - moved at least 'scrolljump' lines and // - at least 'scrolloff' lines above and below the cursor - validate_cheight(); + validate_cheight(wp); int scrolled = 0; - int used = curwin->w_cline_height; // includes filler lines above - if (curwin->w_cursor.lnum < curwin->w_topline) { + int used = wp->w_cline_height; // includes filler lines above + if (wp->w_cursor.lnum < wp->w_topline) { scrolled = used; } linenr_T top; // just above displayed lines linenr_T bot; // just below displayed lines - if (hasFolding(curwin->w_cursor.lnum, &top, &bot)) { + if (hasFolding(wp, wp->w_cursor.lnum, &top, &bot)) { top--; bot++; } else { - top = curwin->w_cursor.lnum - 1; - bot = curwin->w_cursor.lnum + 1; + top = wp->w_cursor.lnum - 1; + bot = wp->w_cursor.lnum + 1; } linenr_T new_topline = top + 1; // "used" already contains the number of filler lines above, don't add it // again. // Hide filler lines above cursor line by adding them to "extra". - int extra = win_get_fill(curwin, curwin->w_cursor.lnum); + int extra = win_get_fill(wp, wp->w_cursor.lnum); // Check if the lines from "top" to "bot" fit in the window. If they do, // set new_topline and advance "top" and "bot" to include more lines. while (top > 0) { - int i = hasFolding(top, &top, NULL) + int i = hasFolding(wp, top, &top, NULL) ? 1 // count one logical line for a sequence of folded lines - : plines_win_nofill(curwin, top, true); - if (top < curwin->w_topline) { + : plines_win_nofill(wp, top, true); + if (top < wp->w_topline) { scrolled += i; } // If scrolling is needed, scroll at least 'sj' lines. - if ((new_topline >= curwin->w_topline || scrolled > min_scroll) && extra >= off) { + if ((new_topline >= wp->w_topline || scrolled > min_scroll) && extra >= off) { break; } used += i; - if (extra + i <= off && bot < curbuf->b_ml.ml_line_count) { - if (hasFolding(bot, NULL, &bot)) { + if (extra + i <= off && bot < wp->w_buffer->b_ml.ml_line_count) { + if (hasFolding(wp, bot, NULL, &bot)) { // count one logical line for a sequence of folded lines used++; } else { - used += plines_win(curwin, bot, true); + used += plines_win(wp, bot, true); } } - if (used > curwin->w_height_inner) { + if (used > wp->w_height_inner) { break; } @@ -1821,43 +1798,43 @@ void scroll_cursor_top(int min_scroll, int always) // If we don't have enough space, put cursor in the middle. // This makes sure we get the same position when using "k" and "j" // in a small window. - if (used > curwin->w_height_inner) { - scroll_cursor_halfway(false, false); + if (used > wp->w_height_inner) { + scroll_cursor_halfway(wp, false, false); } else { // If "always" is false, only adjust topline to a lower value, higher // value may happen with wrapping lines. - if (new_topline < curwin->w_topline || always) { - curwin->w_topline = new_topline; + if (new_topline < wp->w_topline || always) { + wp->w_topline = new_topline; } - if (curwin->w_topline > curwin->w_cursor.lnum) { - curwin->w_topline = curwin->w_cursor.lnum; + if (wp->w_topline > wp->w_cursor.lnum) { + wp->w_topline = wp->w_cursor.lnum; } - curwin->w_topfill = win_get_fill(curwin, curwin->w_topline); - if (curwin->w_topfill > 0 && extra > off) { - curwin->w_topfill -= extra - off; - if (curwin->w_topfill < 0) { - curwin->w_topfill = 0; + wp->w_topfill = win_get_fill(wp, wp->w_topline); + if (wp->w_topfill > 0 && extra > off) { + wp->w_topfill -= extra - off; + if (wp->w_topfill < 0) { + wp->w_topfill = 0; } } - check_topfill(curwin, false); - if (curwin->w_topline != old_topline) { - reset_skipcol(curwin); - } else if (curwin->w_topline == curwin->w_cursor.lnum) { - validate_virtcol(); - if (curwin->w_skipcol >= curwin->w_virtcol) { + check_topfill(wp, false); + if (wp->w_topline != old_topline) { + reset_skipcol(wp); + } else if (wp->w_topline == wp->w_cursor.lnum) { + validate_virtcol(wp); + if (wp->w_skipcol >= wp->w_virtcol) { // TODO(vim): if the line doesn't fit may optimize w_skipcol instead // of making it zero - reset_skipcol(curwin); + reset_skipcol(wp); } } - if (curwin->w_topline != old_topline - || curwin->w_skipcol != old_skipcol - || curwin->w_topfill != old_topfill) { - curwin->w_valid &= + if (wp->w_topline != old_topline + || wp->w_skipcol != old_skipcol + || wp->w_topfill != old_topfill) { + wp->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP); } - curwin->w_valid |= VALID_TOPLINE; - curwin->w_viewport_invalid = true; + wp->w_valid |= VALID_TOPLINE; + wp->w_viewport_invalid = true; } } @@ -1886,79 +1863,79 @@ void set_empty_rows(win_T *wp, int used) /// When scrolling scroll at least "min_scroll" lines. /// If "set_topbot" is true, set topline and botline first (for "zb"). /// This is messy stuff!!! -void scroll_cursor_bot(int min_scroll, bool set_topbot) +void scroll_cursor_bot(win_T *wp, int min_scroll, bool set_topbot) { lineoff_T loff; - linenr_T old_topline = curwin->w_topline; - int old_skipcol = curwin->w_skipcol; - int old_topfill = curwin->w_topfill; - linenr_T old_botline = curwin->w_botline; - int old_valid = curwin->w_valid; - int old_empty_rows = curwin->w_empty_rows; - linenr_T cln = curwin->w_cursor.lnum; // Cursor Line Number - bool do_sms = curwin->w_p_wrap && curwin->w_p_sms; + linenr_T old_topline = wp->w_topline; + int old_skipcol = wp->w_skipcol; + int old_topfill = wp->w_topfill; + linenr_T old_botline = wp->w_botline; + int old_valid = wp->w_valid; + int old_empty_rows = wp->w_empty_rows; + linenr_T cln = wp->w_cursor.lnum; // Cursor Line Number + bool do_sms = wp->w_p_wrap && wp->w_p_sms; if (set_topbot) { bool set_skipcol = false; int used = 0; - curwin->w_botline = cln + 1; + wp->w_botline = cln + 1; loff.fill = 0; - for (curwin->w_topline = curwin->w_botline; - curwin->w_topline > 1; - curwin->w_topline = loff.lnum) { - loff.lnum = curwin->w_topline; - topline_back_winheight(curwin, &loff, false); + for (wp->w_topline = wp->w_botline; + wp->w_topline > 1; + wp->w_topline = loff.lnum) { + loff.lnum = wp->w_topline; + topline_back_winheight(wp, &loff, false); if (loff.height == MAXCOL) { break; } - if (used + loff.height > curwin->w_height_inner) { + if (used + loff.height > wp->w_height_inner) { if (do_sms) { // 'smoothscroll' and 'wrap' are set. The above line is // too long to show in its entirety, so we show just a part // of it. - if (used < curwin->w_height_inner) { - int plines_offset = used + loff.height - curwin->w_height_inner; - used = curwin->w_height_inner; - curwin->w_topfill = loff.fill; - curwin->w_topline = loff.lnum; - curwin->w_skipcol = skipcol_from_plines(curwin, plines_offset); + if (used < wp->w_height_inner) { + int plines_offset = used + loff.height - wp->w_height_inner; + used = wp->w_height_inner; + wp->w_topfill = loff.fill; + wp->w_topline = loff.lnum; + wp->w_skipcol = skipcol_from_plines(wp, plines_offset); set_skipcol = true; } } break; } used += loff.height; - curwin->w_topfill = loff.fill; + wp->w_topfill = loff.fill; } - set_empty_rows(curwin, used); - curwin->w_valid |= VALID_BOTLINE|VALID_BOTLINE_AP; - if (curwin->w_topline != old_topline - || curwin->w_topfill != old_topfill + set_empty_rows(wp, used); + wp->w_valid |= VALID_BOTLINE|VALID_BOTLINE_AP; + if (wp->w_topline != old_topline + || wp->w_topfill != old_topfill || set_skipcol - || curwin->w_skipcol != 0) { - curwin->w_valid &= ~(VALID_WROW|VALID_CROW); + || wp->w_skipcol != 0) { + wp->w_valid &= ~(VALID_WROW|VALID_CROW); if (set_skipcol) { - redraw_later(curwin, UPD_NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); } else { - reset_skipcol(curwin); + reset_skipcol(wp); } } } else { - validate_botline(curwin); + validate_botline(wp); } // The lines of the cursor line itself are always used. - int used = plines_win_nofill(curwin, cln, true); + int used = plines_win_nofill(wp, cln, true); int scrolled = 0; // If the cursor is on or below botline, we will at least scroll by the // height of the cursor line, which is "used". Correct for empty lines, // which are really part of botline. - if (cln >= curwin->w_botline) { + if (cln >= wp->w_botline) { scrolled = used; - if (cln == curwin->w_botline) { - scrolled -= curwin->w_empty_rows; + if (cln == wp->w_botline) { + scrolled -= wp->w_empty_rows; } if (do_sms) { // 'smoothscroll' and 'wrap' are set. @@ -1966,21 +1943,21 @@ void scroll_cursor_bot(int min_scroll, bool set_topbot) // occupies. If it is occupying more than the entire window, we // need to scroll the additional clipped lines to scroll past the // top line before we can move on to the other lines. - int top_plines = plines_win_nofill(curwin, curwin->w_topline, false); + int top_plines = plines_win_nofill(wp, wp->w_topline, false); int skip_lines = 0; - int width1 = curwin->w_width_inner - curwin_col_off(); + int width1 = wp->w_width_inner - win_col_off(wp); if (width1 > 0) { - int width2 = width1 + curwin_col_off2(); + int width2 = width1 + win_col_off2(wp); // similar formula is used in curs_columns() - if (curwin->w_skipcol > width1) { - skip_lines += (curwin->w_skipcol - width1) / width2 + 1; - } else if (curwin->w_skipcol > 0) { + if (wp->w_skipcol > width1) { + skip_lines += (wp->w_skipcol - width1) / width2 + 1; + } else if (wp->w_skipcol > 0) { skip_lines = 1; } top_plines -= skip_lines; - if (top_plines > curwin->w_height_inner) { - scrolled += (top_plines - curwin->w_height_inner); + if (top_plines > wp->w_height_inner) { + scrolled += (top_plines - wp->w_height_inner); } } } @@ -1992,67 +1969,67 @@ void scroll_cursor_bot(int min_scroll, bool set_topbot) // - scrolled nothing or at least 'sj' lines // - at least 'so' lines below the cursor // - lines between botline and cursor have been counted - if (!hasFolding(curwin->w_cursor.lnum, &loff.lnum, &boff.lnum)) { + if (!hasFolding(wp, wp->w_cursor.lnum, &loff.lnum, &boff.lnum)) { loff.lnum = cln; boff.lnum = cln; } loff.fill = 0; boff.fill = 0; - int fill_below_window = win_get_fill(curwin, curwin->w_botline) - curwin->w_filler_rows; + int fill_below_window = win_get_fill(wp, wp->w_botline) - wp->w_filler_rows; int extra = 0; - int so = get_scrolloff_value(curwin); + int so = get_scrolloff_value(wp); while (loff.lnum > 1) { // Stop when scrolled nothing or at least "min_scroll", found "extra" // context for 'scrolloff' and counted all lines below the window. if ((((scrolled <= 0 || scrolled >= min_scroll) && extra >= (mouse_dragging > 0 ? mouse_dragging - 1 : so)) - || boff.lnum + 1 > curbuf->b_ml.ml_line_count) - && loff.lnum <= curwin->w_botline - && (loff.lnum < curwin->w_botline + || boff.lnum + 1 > wp->w_buffer->b_ml.ml_line_count) + && loff.lnum <= wp->w_botline + && (loff.lnum < wp->w_botline || loff.fill >= fill_below_window)) { break; } // Add one line above - topline_back(curwin, &loff); + topline_back(wp, &loff); if (loff.height == MAXCOL) { used = MAXCOL; } else { used += loff.height; } - if (used > curwin->w_height_inner) { + if (used > wp->w_height_inner) { break; } - if (loff.lnum >= curwin->w_botline - && (loff.lnum > curwin->w_botline + if (loff.lnum >= wp->w_botline + && (loff.lnum > wp->w_botline || loff.fill <= fill_below_window)) { // Count screen lines that are below the window. scrolled += loff.height; - if (loff.lnum == curwin->w_botline + if (loff.lnum == wp->w_botline && loff.fill == 0) { - scrolled -= curwin->w_empty_rows; + scrolled -= wp->w_empty_rows; } } - if (boff.lnum < curbuf->b_ml.ml_line_count) { + if (boff.lnum < wp->w_buffer->b_ml.ml_line_count) { // Add one line below - botline_forw(curwin, &boff); + botline_forw(wp, &boff); used += boff.height; - if (used > curwin->w_height_inner) { + if (used > wp->w_height_inner) { break; } if (extra < (mouse_dragging > 0 ? mouse_dragging - 1 : so) || scrolled < min_scroll) { extra += boff.height; - if (boff.lnum >= curwin->w_botline - || (boff.lnum + 1 == curwin->w_botline - && boff.fill > curwin->w_filler_rows)) { + if (boff.lnum >= wp->w_botline + || (boff.lnum + 1 == wp->w_botline + && boff.fill > wp->w_filler_rows)) { // Count screen lines that are below the window. scrolled += boff.height; - if (boff.lnum == curwin->w_botline + if (boff.lnum == wp->w_botline && boff.fill == 0) { - scrolled -= curwin->w_empty_rows; + scrolled -= wp->w_empty_rows; } } } @@ -2060,77 +2037,77 @@ void scroll_cursor_bot(int min_scroll, bool set_topbot) } linenr_T line_count; - // curwin->w_empty_rows is larger, no need to scroll + // wp->w_empty_rows is larger, no need to scroll if (scrolled <= 0) { line_count = 0; // more than a screenfull, don't scroll but redraw - } else if (used > curwin->w_height_inner) { + } else if (used > wp->w_height_inner) { line_count = used; // scroll minimal number of lines } else { line_count = 0; - boff.fill = curwin->w_topfill; - boff.lnum = curwin->w_topline - 1; + boff.fill = wp->w_topfill; + boff.lnum = wp->w_topline - 1; int i; - for (i = 0; i < scrolled && boff.lnum < curwin->w_botline;) { - botline_forw(curwin, &boff); + for (i = 0; i < scrolled && boff.lnum < wp->w_botline;) { + botline_forw(wp, &boff); i += boff.height; line_count++; } - if (i < scrolled) { // below curwin->w_botline, don't scroll + if (i < scrolled) { // below wp->w_botline, don't scroll line_count = 9999; } } // Scroll up if the cursor is off the bottom of the screen a bit. // Otherwise put it at 1/2 of the screen. - if (line_count >= curwin->w_height_inner && line_count > min_scroll) { - scroll_cursor_halfway(false, true); + if (line_count >= wp->w_height_inner && line_count > min_scroll) { + scroll_cursor_halfway(wp, false, true); } else if (line_count > 0) { if (do_sms) { - scrollup(scrolled, true); // TODO(vim): + scrollup(wp, scrolled, true); // TODO(vim): } else { - scrollup(line_count, true); + scrollup(wp, line_count, true); } } // If topline didn't change we need to restore w_botline and w_empty_rows // (we changed them). // If topline did change, update_screen() will set botline. - if (curwin->w_topline == old_topline && curwin->w_skipcol == old_skipcol && set_topbot) { - curwin->w_botline = old_botline; - curwin->w_empty_rows = old_empty_rows; - curwin->w_valid = old_valid; + if (wp->w_topline == old_topline && wp->w_skipcol == old_skipcol && set_topbot) { + wp->w_botline = old_botline; + wp->w_empty_rows = old_empty_rows; + wp->w_valid = old_valid; } - curwin->w_valid |= VALID_TOPLINE; - curwin->w_viewport_invalid = true; + wp->w_valid |= VALID_TOPLINE; + wp->w_viewport_invalid = true; } /// Recompute topline to put the cursor halfway across the window /// /// @param atend if true, also put the cursor halfway to the end of the file. /// -void scroll_cursor_halfway(bool atend, bool prefer_above) +void scroll_cursor_halfway(win_T *wp, bool atend, bool prefer_above) { - linenr_T old_topline = curwin->w_topline; - lineoff_T loff = { .lnum = curwin->w_cursor.lnum }; - lineoff_T boff = { .lnum = curwin->w_cursor.lnum }; - hasFolding(loff.lnum, &loff.lnum, &boff.lnum); - int used = plines_win_nofill(curwin, loff.lnum, true); + linenr_T old_topline = wp->w_topline; + lineoff_T loff = { .lnum = wp->w_cursor.lnum }; + lineoff_T boff = { .lnum = wp->w_cursor.lnum }; + hasFolding(wp, loff.lnum, &loff.lnum, &boff.lnum); + int used = plines_win_nofill(wp, loff.lnum, true); loff.fill = 0; boff.fill = 0; linenr_T topline = loff.lnum; colnr_T skipcol = 0; int want_height; - bool do_sms = curwin->w_p_wrap && curwin->w_p_sms; + bool do_sms = wp->w_p_wrap && wp->w_p_sms; if (do_sms) { // 'smoothscroll' and 'wrap' are set if (atend) { - want_height = (curwin->w_height_inner - used) / 2; + want_height = (wp->w_height_inner - used) / 2; used = 0; } else { - want_height = curwin->w_height_inner; + want_height = wp->w_height_inner; } } @@ -2139,20 +2116,20 @@ void scroll_cursor_halfway(bool atend, bool prefer_above) // If using smoothscroll, we can precisely scroll to the // exact point where the cursor is halfway down the screen. if (do_sms) { - topline_back_winheight(curwin, &loff, false); + topline_back_winheight(wp, &loff, false); if (loff.height == MAXCOL) { break; } used += loff.height; - if (!atend && boff.lnum < curbuf->b_ml.ml_line_count) { - botline_forw(curwin, &boff); + if (!atend && boff.lnum < wp->w_buffer->b_ml.ml_line_count) { + botline_forw(wp, &boff); used += boff.height; } if (used > want_height) { if (used - loff.height < want_height) { topline = loff.lnum; topfill = loff.fill; - skipcol = skipcol_from_plines(curwin, used - want_height); + skipcol = skipcol_from_plines(wp, used - want_height); } break; } @@ -2176,10 +2153,10 @@ void scroll_cursor_halfway(bool atend, bool prefer_above) ? (round == 2 && below < above) : (round == 1 && below <= above)) { // add a line below the cursor - if (boff.lnum < curbuf->b_ml.ml_line_count) { - botline_forw(curwin, &boff); + if (boff.lnum < wp->w_buffer->b_ml.ml_line_count) { + botline_forw(wp, &boff); used += boff.height; - if (used > curwin->w_height_inner) { + if (used > wp->w_height_inner) { done = true; break; } @@ -2196,13 +2173,13 @@ void scroll_cursor_halfway(bool atend, bool prefer_above) ? (round == 1 && below >= above) : (round == 1 && below > above)) { // add a line above the cursor - topline_back(curwin, &loff); + topline_back(wp, &loff); if (loff.height == MAXCOL) { used = MAXCOL; } else { used += loff.height; } - if (used > curwin->w_height_inner) { + if (used > wp->w_height_inner) { done = true; break; } @@ -2216,51 +2193,51 @@ void scroll_cursor_halfway(bool atend, bool prefer_above) } } - if (!hasFolding(topline, &curwin->w_topline, NULL) - && (curwin->w_topline != topline || skipcol != 0 || curwin->w_skipcol != 0)) { - curwin->w_topline = topline; + if (!hasFolding(wp, topline, &wp->w_topline, NULL) + && (wp->w_topline != topline || skipcol != 0 || wp->w_skipcol != 0)) { + wp->w_topline = topline; if (skipcol != 0) { - curwin->w_skipcol = skipcol; - redraw_later(curwin, UPD_NOT_VALID); + wp->w_skipcol = skipcol; + redraw_later(wp, UPD_NOT_VALID); } else if (do_sms) { - reset_skipcol(curwin); + reset_skipcol(wp); } } - curwin->w_topfill = topfill; - if (old_topline > curwin->w_topline + curwin->w_height_inner) { - curwin->w_botfill = false; + wp->w_topfill = topfill; + if (old_topline > wp->w_topline + wp->w_height_inner) { + wp->w_botfill = false; } - check_topfill(curwin, false); - curwin->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP); - curwin->w_valid |= VALID_TOPLINE; + check_topfill(wp, false); + wp->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP); + wp->w_valid |= VALID_TOPLINE; } // Correct the cursor position so that it is in a part of the screen at least // 'so' lines from the top and bottom, if possible. // If not possible, put it at the same position as scroll_cursor_halfway(). // When called topline must be valid! -void cursor_correct(void) +void cursor_correct(win_T *wp) { // How many lines we would like to have above/below the cursor depends on // whether the first/last line of the file is on screen. - int above_wanted = get_scrolloff_value(curwin); - int below_wanted = get_scrolloff_value(curwin); + int above_wanted = get_scrolloff_value(wp); + int below_wanted = get_scrolloff_value(wp); if (mouse_dragging > 0) { above_wanted = mouse_dragging - 1; below_wanted = mouse_dragging - 1; } - if (curwin->w_topline == 1) { + if (wp->w_topline == 1) { above_wanted = 0; - int max_off = curwin->w_height_inner / 2; + int max_off = wp->w_height_inner / 2; if (below_wanted > max_off) { below_wanted = max_off; } } - validate_botline(curwin); - if (curwin->w_botline == curbuf->b_ml.ml_line_count + 1 + validate_botline(wp); + if (wp->w_botline == wp->w_buffer->b_ml.ml_line_count + 1 && mouse_dragging == 0) { below_wanted = 0; - int max_off = (curwin->w_height_inner - 1) / 2; + int max_off = (wp->w_height_inner - 1) / 2; if (above_wanted > max_off) { above_wanted = max_off; } @@ -2268,18 +2245,18 @@ void cursor_correct(void) // If there are sufficient file-lines above and below the cursor, we can // return now. - linenr_T cln = curwin->w_cursor.lnum; // Cursor Line Number - if (cln >= curwin->w_topline + above_wanted - && cln < curwin->w_botline - below_wanted - && !hasAnyFolding(curwin)) { + linenr_T cln = wp->w_cursor.lnum; // Cursor Line Number + if (cln >= wp->w_topline + above_wanted + && cln < wp->w_botline - below_wanted + && !hasAnyFolding(wp)) { return; } - if (curwin->w_p_sms && !curwin->w_p_wrap) { + if (wp->w_p_sms && !wp->w_p_wrap) { // 'smoothscroll' is active - if (curwin->w_cline_height == curwin->w_height_inner) { + if (wp->w_cline_height == wp->w_height_inner) { // The cursor line just fits in the window, don't scroll. - reset_skipcol(curwin); + reset_skipcol(wp); return; } // TODO(vim): If the cursor line doesn't fit in the window then only adjust w_skipcol. @@ -2289,52 +2266,52 @@ void cursor_correct(void) // the top and the bottom until: // - the desired context lines are found // - the lines from the top is past the lines from the bottom - linenr_T topline = curwin->w_topline; - linenr_T botline = curwin->w_botline - 1; + linenr_T topline = wp->w_topline; + linenr_T botline = wp->w_botline - 1; // count filler lines as context - int above = curwin->w_topfill; // screen lines above topline - int below = curwin->w_filler_rows; // screen lines below botline + int above = wp->w_topfill; // screen lines above topline + int below = wp->w_filler_rows; // screen lines below botline while ((above < above_wanted || below < below_wanted) && topline < botline) { if (below < below_wanted && (below <= above || above >= above_wanted)) { - if (hasFolding(botline, &botline, NULL)) { + if (hasFolding(wp, botline, &botline, NULL)) { below++; } else { - below += plines_win(curwin, botline, true); + below += plines_win(wp, botline, true); } botline--; } if (above < above_wanted && (above < below || below >= below_wanted)) { - if (hasFolding(topline, NULL, &topline)) { + if (hasFolding(wp, topline, NULL, &topline)) { above++; } else { - above += plines_win_nofill(curwin, topline, true); + above += plines_win_nofill(wp, topline, true); } // Count filler lines below this line as context. if (topline < botline) { - above += win_get_fill(curwin, topline + 1); + above += win_get_fill(wp, topline + 1); } topline++; } } if (topline == botline || botline == 0) { - curwin->w_cursor.lnum = topline; + wp->w_cursor.lnum = topline; } else if (topline > botline) { - curwin->w_cursor.lnum = botline; + wp->w_cursor.lnum = botline; } else { - if (cln < topline && curwin->w_topline > 1) { - curwin->w_cursor.lnum = topline; - curwin->w_valid &= + if (cln < topline && wp->w_topline > 1) { + wp->w_cursor.lnum = topline; + wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_CHEIGHT|VALID_CROW); } - if (cln > botline && curwin->w_botline <= curbuf->b_ml.ml_line_count) { - curwin->w_cursor.lnum = botline; - curwin->w_valid &= + if (cln > botline && wp->w_botline <= wp->w_buffer->b_ml.ml_line_count) { + wp->w_cursor.lnum = botline; + wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_CHEIGHT|VALID_CROW); } } - curwin->w_valid |= VALID_TOPLINE; - curwin->w_viewport_invalid = true; + wp->w_valid |= VALID_TOPLINE; + wp->w_viewport_invalid = true; } /// Move screen "count" pages up ("dir" is BACKWARD) or down ("dir" is FORWARD) @@ -2461,7 +2438,7 @@ int onepage(Direction dir, int count) botline_forw(curwin, &loff); botline_topline(&loff); // We're at the wrong end of a fold now. - hasFoldingWin(curwin, loff.lnum, &loff.lnum, NULL, true, NULL); + hasFolding(curwin, loff.lnum, &loff.lnum, NULL); // Always scroll at least one line. Avoid getting stuck on // very long lines. @@ -2491,9 +2468,9 @@ int onepage(Direction dir, int count) } } } - foldAdjustCursor(); - cursor_correct(); - check_cursor_col(); + foldAdjustCursor(curwin); + cursor_correct(curwin); + check_cursor_col(curwin); if (retval == OK) { beginline(BL_SOL | BL_FIX); } @@ -2504,14 +2481,14 @@ int onepage(Direction dir, int count) // But make sure we scroll at least one line (happens with mix of long // wrapping lines and non-wrapping line). if (check_top_offset()) { - scroll_cursor_top(1, false); + scroll_cursor_top(curwin, 1, false); if (curwin->w_topline <= old_topline && old_topline < curbuf->b_ml.ml_line_count) { curwin->w_topline = old_topline + 1; - hasFolding(curwin->w_topline, &curwin->w_topline, NULL); + hasFolding(curwin, curwin->w_topline, &curwin->w_topline, NULL); } } else if (curwin->w_botline > curbuf->b_ml.ml_line_count) { - hasFolding(curwin->w_topline, &curwin->w_topline, NULL); + hasFolding(curwin, curwin->w_topline, &curwin->w_topline, NULL); } } @@ -2610,7 +2587,7 @@ void halfpage(bool flag, linenr_T Prenum) if (n < 0 && scrolled > 0) { break; } - hasFolding(curwin->w_topline, NULL, &curwin->w_topline); + hasFolding(curwin, curwin->w_topline, NULL, &curwin->w_topline); curwin->w_topline++; curwin->w_topfill = win_get_fill(curwin, curwin->w_topline); @@ -2634,7 +2611,7 @@ void halfpage(bool flag, linenr_T Prenum) if (i > room) { break; } - hasFolding(curwin->w_botline, NULL, &curwin->w_botline); + hasFolding(curwin, curwin->w_botline, NULL, &curwin->w_botline); curwin->w_botline++; room -= i; } while (curwin->w_botline <= curbuf->b_ml.ml_line_count); @@ -2646,7 +2623,7 @@ void halfpage(bool flag, linenr_T Prenum) if (hasAnyFolding(curwin)) { while (--n >= 0 && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { - hasFolding(curwin->w_cursor.lnum, NULL, + hasFolding(curwin, curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum); curwin->w_cursor.lnum++; } @@ -2669,7 +2646,7 @@ void halfpage(bool flag, linenr_T Prenum) break; } curwin->w_topline--; - hasFolding(curwin->w_topline, &curwin->w_topline, NULL); + hasFolding(curwin, curwin->w_topline, &curwin->w_topline, NULL); curwin->w_topfill = 0; } curwin->w_valid &= ~(VALID_CROW|VALID_WROW| @@ -2688,7 +2665,7 @@ void halfpage(bool flag, linenr_T Prenum) } else if (hasAnyFolding(curwin)) { while (--n >= 0 && curwin->w_cursor.lnum > 1) { curwin->w_cursor.lnum--; - hasFolding(curwin->w_cursor.lnum, + hasFolding(curwin, curwin->w_cursor.lnum, &curwin->w_cursor.lnum, NULL); } } else { @@ -2697,9 +2674,9 @@ void halfpage(bool flag, linenr_T Prenum) } } // Move cursor to first line of closed fold. - foldAdjustCursor(); + foldAdjustCursor(curwin); check_topfill(curwin, !flag); - cursor_correct(); + cursor_correct(curwin); beginline(BL_SOL | BL_FIX); redraw_later(curwin, UPD_VALID); } @@ -2748,12 +2725,12 @@ void do_check_cursorbind(void) { int restart_edit_save = restart_edit; restart_edit = true; - check_cursor(); + check_cursor(curwin); // Avoid a scroll here for the cursor position, 'scrollbind' is // more important. if (!curwin->w_p_scb) { - validate_cursor(); + validate_cursor(curwin); } restart_edit = restart_edit_save; diff --git a/src/nvim/normal.c b/src/nvim/normal.c index aae9621d4a..3603a054b6 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -1009,12 +1009,12 @@ normal_end: mb_check_adjust_col(curwin); // #6203 if (curwin->w_p_scb && s->toplevel) { - validate_cursor(); // may need to update w_leftcol + validate_cursor(curwin); // may need to update w_leftcol do_check_scrollbind(true); } if (curwin->w_p_crb && s->toplevel) { - validate_cursor(); // may need to update w_leftcol + validate_cursor(curwin); // may need to update w_leftcol do_check_cursorbind(); } @@ -1343,7 +1343,7 @@ static void normal_redraw(NormalState *s) // Before redrawing, make sure w_topline is correct, and w_leftcol // if lines don't wrap, and w_skipcol if lines wrap. update_topline(curwin); - validate_cursor(); + validate_cursor(curwin); show_cursor_info_later(false); @@ -1420,7 +1420,7 @@ static int normal_check(VimState *state) // Ensure curwin->w_topline and curwin->w_leftcol are up to date // before triggering a WinScrolled autocommand. update_topline(curwin); - validate_cursor(); + validate_cursor(curwin); normal_check_cursor_moved(s); normal_check_text_changed(s); @@ -1515,7 +1515,7 @@ void end_visual_mode(void) curbuf->b_visual.vi_end = curwin->w_cursor; curbuf->b_visual.vi_curswant = curwin->w_curswant; curbuf->b_visual_mode_eval = VIsual_mode; - if (!virtual_active()) { + if (!virtual_active(curwin)) { curwin->w_cursor.coladd = 0; } @@ -1863,8 +1863,8 @@ void clear_showcmd(void) bot = VIsual.lnum; } // Include closed folds as a whole. - hasFolding(top, &top, NULL); - hasFolding(bot, NULL, &bot); + hasFolding(curwin, top, &top, NULL); + hasFolding(curwin, bot, NULL, &bot); lines = bot - top + 1; if (VIsual_mode == Ctrl_V) { @@ -2174,14 +2174,14 @@ void check_scrollbind(linenr_T topline_diff, int leftcol_diff) y = topline - curwin->w_topline; if (y > 0) { - scrollup(y, false); + scrollup(curwin, y, false); } else { - scrolldown(-y, false); + scrolldown(curwin, -y, false); } } redraw_later(curwin, UPD_VALID); - cursor_correct(); + cursor_correct(curwin); curwin->w_redr_status = true; } @@ -2466,8 +2466,8 @@ static bool nv_screengo(oparg_T *oap, int dir, int dist) oap->motion_type = kMTCharWise; oap->inclusive = (curwin->w_curswant == MAXCOL); - col_off1 = curwin_col_off(); - col_off2 = col_off1 - curwin_col_off2(); + col_off1 = win_col_off(curwin); + col_off2 = col_off1 - win_col_off2(curwin); width1 = curwin->w_width_inner - col_off1; width2 = curwin->w_width_inner - col_off2; @@ -2481,7 +2481,7 @@ static bool nv_screengo(oparg_T *oap, int dir, int dist) // try to stick in the last column of the screen. if (curwin->w_curswant == MAXCOL) { atend = true; - validate_virtcol(); + validate_virtcol(curwin); if (width1 <= 0) { curwin->w_curswant = 0; } else { @@ -2506,7 +2506,7 @@ static bool nv_screengo(oparg_T *oap, int dir, int dist) while (dist--) { if (dir == BACKWARD) { if (curwin->w_curswant >= width1 - && !hasFolding(curwin->w_cursor.lnum, NULL, NULL)) { + && !hasFolding(curwin, curwin->w_cursor.lnum, NULL, NULL)) { // Move back within the line. This can give a negative value // for w_curswant if width1 < width2 (with cpoptions+=n), // which will get clipped to column 0. @@ -2533,7 +2533,7 @@ static bool nv_screengo(oparg_T *oap, int dir, int dist) n = width1; } if (curwin->w_curswant + width2 < (colnr_T)n - && !hasFolding(curwin->w_cursor.lnum, NULL, NULL)) { + && !hasFolding(curwin, curwin->w_cursor.lnum, NULL, NULL)) { // move forward within line curwin->w_curswant += width2; } else { @@ -2558,17 +2558,17 @@ static bool nv_screengo(oparg_T *oap, int dir, int dist) } } - if (virtual_active() && atend) { - coladvance(MAXCOL); + if (virtual_active(curwin) && atend) { + coladvance(curwin, MAXCOL); } else { - coladvance(curwin->w_curswant); + coladvance(curwin, curwin->w_curswant); } if (curwin->w_cursor.col > 0 && curwin->w_p_wrap) { // Check for landing on a character that got split at the end of the // last line. We want to advance a screenline, not end up in the same // screenline or move two screenlines. - validate_virtcol(); + validate_virtcol(curwin); colnr_T virtcol = curwin->w_virtcol; if (virtcol > (colnr_T)width1 && *get_showbreak_value(curwin) != NUL) { virtcol -= vim_strsize(get_showbreak_value(curwin)); @@ -2616,13 +2616,13 @@ void scroll_redraw(bool up, linenr_T count) linenr_T prev_lnum = curwin->w_cursor.lnum; bool moved = up - ? scrollup(count, true) - : scrolldown(count, true); + ? scrollup(curwin, count, true) + : scrolldown(curwin, count, true); if (get_scrolloff_value(curwin) > 0) { // Adjust the cursor position for 'scrolloff'. Mark w_topline as // valid, otherwise the screen jumps back at the end of the file. - cursor_correct(); + cursor_correct(curwin); check_cursor_moved(curwin); curwin->w_valid |= VALID_TOPLINE; @@ -2651,7 +2651,7 @@ void scroll_redraw(bool up, linenr_T count) } } if (curwin->w_cursor.lnum != prev_lnum) { - coladvance(curwin->w_curswant); + coladvance(curwin, curwin->w_curswant); } if (moved) { curwin->w_viewport_invalid = true; @@ -2803,7 +2803,7 @@ static void nv_zet(cmdarg_T *cap) } else { curwin->w_cursor.lnum = cap->count0; } - check_cursor_col(); + check_cursor_col(curwin); } switch (nchar) { @@ -2826,7 +2826,7 @@ static void nv_zet(cmdarg_T *cap) FALLTHROUGH; case 't': - scroll_cursor_top(0, true); + scroll_cursor_top(curwin, 0, true); redraw_later(curwin, UPD_VALID); set_fraction(curwin); break; @@ -2837,7 +2837,7 @@ static void nv_zet(cmdarg_T *cap) FALLTHROUGH; case 'z': - scroll_cursor_halfway(true, false); + scroll_cursor_halfway(curwin, true, false); redraw_later(curwin, UPD_VALID); set_fraction(curwin); break; @@ -2847,7 +2847,7 @@ static void nv_zet(cmdarg_T *cap) // when is at bottom of window, and puts that one at // bottom of window. if (cap->count0 != 0) { - scroll_cursor_bot(0, true); + scroll_cursor_bot(curwin, 0, true); curwin->w_cursor.lnum = curwin->w_topline; } else if (curwin->w_topline == 1) { curwin->w_cursor.lnum = 1; @@ -2860,7 +2860,7 @@ static void nv_zet(cmdarg_T *cap) FALLTHROUGH; case 'b': - scroll_cursor_bot(0, true); + scroll_cursor_bot(curwin, 0, true); redraw_later(curwin, UPD_VALID); set_fraction(curwin); break; @@ -2895,7 +2895,7 @@ static void nv_zet(cmdarg_T *cap) // "zs" - scroll screen, cursor at the start case 's': if (!curwin->w_p_wrap) { - if (hasFolding(curwin->w_cursor.lnum, NULL, NULL)) { + if (hasFolding(curwin, curwin->w_cursor.lnum, NULL, NULL)) { col = 0; // like the cursor is in col 0 } else { getvcol(curwin, &curwin->w_cursor, &col, NULL, NULL); @@ -2915,12 +2915,12 @@ static void nv_zet(cmdarg_T *cap) // "ze" - scroll screen, cursor at the end case 'e': if (!curwin->w_p_wrap) { - if (hasFolding(curwin->w_cursor.lnum, NULL, NULL)) { + if (hasFolding(curwin, curwin->w_cursor.lnum, NULL, NULL)) { col = 0; // like the cursor is in col 0 } else { getvcol(curwin, &curwin->w_cursor, NULL, NULL, &col); } - int n = curwin->w_width_inner - curwin_col_off(); + int n = curwin->w_width_inner - win_col_off(curwin); if (col + siso < n) { col = 0; } else { @@ -2980,7 +2980,7 @@ static void nv_zet(cmdarg_T *cap) case 'E': if (foldmethodIsManual(curwin)) { clearFolding(curwin); - changed_window_setting(); + changed_window_setting(curwin); } else if (foldmethodIsMarker(curwin)) { deleteFold(curwin, 1, curbuf->b_ml.ml_line_count, true, false); } else { @@ -3005,7 +3005,7 @@ static void nv_zet(cmdarg_T *cap) // "za": open closed fold or close open fold at cursor case 'a': - if (hasFolding(curwin->w_cursor.lnum, NULL, NULL)) { + if (hasFolding(curwin, curwin->w_cursor.lnum, NULL, NULL)) { openFold(curwin->w_cursor, cap->count1); } else { closeFold(curwin->w_cursor, cap->count1); @@ -3015,7 +3015,7 @@ static void nv_zet(cmdarg_T *cap) // "zA": open fold at cursor recursively case 'A': - if (hasFolding(curwin->w_cursor.lnum, NULL, NULL)) { + if (hasFolding(curwin, curwin->w_cursor.lnum, NULL, NULL)) { openFoldRecurse(curwin->w_cursor); } else { closeFoldRecurse(curwin->w_cursor); @@ -3151,11 +3151,11 @@ static void nv_zet(cmdarg_T *cap) FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp != curwin && foldmethodIsDiff(wp) && wp->w_p_scb) { wp->w_p_fen = curwin->w_p_fen; - changed_window_setting_win(wp); + changed_window_setting(wp); } } } - changed_window_setting(); + changed_window_setting(curwin); } // Redraw when 'foldlevel' changed. @@ -3639,7 +3639,7 @@ static void nv_scroll(cmdarg_T *cap) // Count a fold for one screen line. for (n = cap->count1 - 1; n > 0 && curwin->w_cursor.lnum > curwin->w_topline; n--) { - hasFolding(curwin->w_cursor.lnum, + hasFolding(curwin, curwin->w_cursor.lnum, &curwin->w_cursor.lnum, NULL); if (curwin->w_cursor.lnum > curwin->w_topline) { curwin->w_cursor.lnum--; @@ -3668,7 +3668,7 @@ static void nv_scroll(cmdarg_T *cap) if (used >= half) { break; } - if (hasFolding(curwin->w_topline + n, NULL, &lnum)) { + if (hasFolding(curwin, curwin->w_topline + n, NULL, &lnum)) { n = lnum - curwin->w_topline; } } @@ -3681,7 +3681,7 @@ static void nv_scroll(cmdarg_T *cap) // Count a fold for one screen line. lnum = curwin->w_topline; while (n-- > 0 && lnum < curwin->w_botline - 1) { - hasFolding(lnum, NULL, &lnum); + hasFolding(curwin, lnum, NULL, &lnum); lnum++; } n = lnum - curwin->w_topline; @@ -3695,7 +3695,7 @@ static void nv_scroll(cmdarg_T *cap) // Correct for 'so', except when an operator is pending. if (cap->oap->op_type == OP_NOP) { - cursor_correct(); + cursor_correct(curwin); } beginline(BL_SOL | BL_FIX); } @@ -3720,7 +3720,7 @@ static void nv_right(cmdarg_T *cap) // In virtual edit mode, there's no such thing as "past_line", as lines // are (theoretically) infinitely long. - if (virtual_active()) { + if (virtual_active(curwin)) { past_line = false; } @@ -3763,7 +3763,7 @@ static void nv_right(cmdarg_T *cap) break; } else if (past_line) { curwin->w_set_curswant = true; - if (virtual_active()) { + if (virtual_active(curwin)) { oneright(); } else { curwin->w_cursor.col += utfc_ptr2len(get_cursor_pos_ptr()); @@ -3805,7 +3805,7 @@ static void nv_left(cmdarg_T *cap) || (cap->cmdchar == K_LEFT && vim_strchr(p_ww, '<') != NULL)) && curwin->w_cursor.lnum > 1) { curwin->w_cursor.lnum--; - coladvance(MAXCOL); + coladvance(curwin, MAXCOL); curwin->w_set_curswant = true; // When the NL before the first char has to be deleted we @@ -3940,7 +3940,7 @@ static void nv_dollar(cmdarg_T *cap) // In virtual mode when off the edge of a line and an operator // is pending (whew!) keep the cursor where it is. // Otherwise, send it to the end of the line. - if (!virtual_active() || gchar_cursor() != NUL + if (!virtual_active(curwin) || gchar_cursor() != NUL || cap->oap->op_type == OP_NOP) { curwin->w_curswant = MAXCOL; // so we stay at the end } @@ -4034,7 +4034,7 @@ static int normal_search(cmdarg_T *cap, int dir, char *pat, int opt, int *wrappe // "/$" will put the cursor after the end of the line, may need to // correct that here - check_cursor(); + check_cursor(curwin); return i; } @@ -4060,7 +4060,7 @@ static void nv_csearch(cmdarg_T *cap) curwin->w_set_curswant = true; // Include a Tab for "tx" and for "dfx". - if (gchar_cursor() == TAB && virtual_active() && cap->arg == FORWARD + if (gchar_cursor() == TAB && virtual_active(curwin) && cap->arg == FORWARD && (t_cmd || cap->oap->op_type != OP_NOP)) { colnr_T scol, ecol; @@ -4516,7 +4516,7 @@ static void nv_replace(cmdarg_T *cap) } // Break tabs, etc. - if (virtual_active()) { + if (virtual_active(curwin)) { if (u_save_cursor() == false) { return; } @@ -4628,7 +4628,7 @@ static void v_swap_corners(int cmdchar) pos_T old_cursor = curwin->w_cursor; getvcols(curwin, &old_cursor, &VIsual, &left, &right); curwin->w_cursor.lnum = VIsual.lnum; - coladvance(left); + coladvance(curwin, left); VIsual = curwin->w_cursor; curwin->w_cursor.lnum = old_cursor.lnum; @@ -4638,20 +4638,20 @@ static void v_swap_corners(int cmdchar) if (old_cursor.lnum >= VIsual.lnum && *p_sel == 'e') { curwin->w_curswant++; } - coladvance(curwin->w_curswant); + coladvance(curwin, curwin->w_curswant); if (curwin->w_cursor.col == old_cursor.col - && (!virtual_active() + && (!virtual_active(curwin) || curwin->w_cursor.coladd == old_cursor.coladd)) { curwin->w_cursor.lnum = VIsual.lnum; if (old_cursor.lnum <= VIsual.lnum && *p_sel == 'e') { right++; } - coladvance(right); + coladvance(curwin, right); VIsual = curwin->w_cursor; curwin->w_cursor.lnum = old_cursor.lnum; - coladvance(left); + coladvance(curwin, left); curwin->w_curswant = left; } } else { @@ -4681,8 +4681,8 @@ static void nv_Replace(cmdarg_T *cap) if (!MODIFIABLE(curbuf)) { emsg(_(e_modifiable)); } else { - if (virtual_active()) { - coladvance(getviscol()); + if (virtual_active(curwin)) { + coladvance(curwin, getviscol()); } invoke_edit(cap, false, cap->arg ? 'V' : 'R', false); } @@ -4716,8 +4716,8 @@ static void nv_vreplace(cmdarg_T *cap) } stuffcharReadbuff(cap->extra_char); stuffcharReadbuff(ESC); - if (virtual_active()) { - coladvance(getviscol()); + if (virtual_active(curwin)) { + coladvance(curwin, getviscol()); } invoke_edit(cap, true, 'v', false); } @@ -4764,7 +4764,7 @@ static void n_swapchar(cmdarg_T *cap) } } - check_cursor(); + check_cursor(curwin); curwin->w_set_curswant = true; if (did_change) { changed_lines(curbuf, startpos.lnum, startpos.col, curwin->w_cursor.lnum + 1, @@ -4896,7 +4896,7 @@ static void nv_gomark(cmdarg_T *cap) move_res = nv_mark_move_to(cap, flags, fm); // May need to clear the coladd that a mark includes. - if (!virtual_active()) { + if (!virtual_active(curwin)) { curwin->w_cursor.coladd = 0; } @@ -5025,7 +5025,7 @@ static void nv_visual(cmdarg_T *cap) // was only one -- webb if (resel_VIsual_mode != 'v' || resel_VIsual_line_count > 1) { curwin->w_cursor.lnum += resel_VIsual_line_count * cap->count0 - 1; - check_cursor(); + check_cursor(curwin); } VIsual_mode = resel_VIsual_mode; if (VIsual_mode == 'v') { @@ -5039,11 +5039,11 @@ static void nv_visual(cmdarg_T *cap) } else { curwin->w_curswant = resel_VIsual_vcol; } - coladvance(curwin->w_curswant); + coladvance(curwin, curwin->w_curswant); } if (resel_VIsual_vcol == MAXCOL) { curwin->w_curswant = MAXCOL; - coladvance(MAXCOL); + coladvance(curwin, MAXCOL); } else if (VIsual_mode == Ctrl_V) { // Update curswant on the original line, that is where "col" is valid. linenr_T lnum = curwin->w_cursor.lnum; @@ -5052,7 +5052,7 @@ static void nv_visual(cmdarg_T *cap) assert(cap->count0 >= INT_MIN && cap->count0 <= INT_MAX); curwin->w_curswant += resel_VIsual_vcol * cap->count0 - 1; curwin->w_cursor.lnum = lnum; - coladvance(curwin->w_curswant); + coladvance(curwin, curwin->w_curswant); } else { curwin->w_set_curswant = true; } @@ -5104,9 +5104,9 @@ static void n_start_visual_mode(int c) // Corner case: the 0 position in a tab may change when going into // virtualedit. Recalculate curwin->w_cursor to avoid bad highlighting. // - if (c == Ctrl_V && (get_ve_flags() & VE_BLOCK) && gchar_cursor() == TAB) { - validate_virtcol(); - coladvance(curwin->w_virtcol); + if (c == Ctrl_V && (get_ve_flags(curwin) & VE_BLOCK) && gchar_cursor() == TAB) { + validate_virtcol(curwin); + coladvance(curwin, curwin->w_virtcol); } VIsual = curwin->w_cursor; @@ -5194,10 +5194,10 @@ static void nv_gv_cmd(cmdarg_T *cap) // Set Visual to the start and w_cursor to the end of the Visual // area. Make sure they are on an existing character. - check_cursor(); + check_cursor(curwin); VIsual = curwin->w_cursor; curwin->w_cursor = tpos; - check_cursor(); + check_cursor(curwin); update_topline(curwin); // When called from normal "g" command: start Select mode when @@ -5224,10 +5224,10 @@ static void nv_g_home_m_cmd(cmdarg_T *cap) cap->oap->motion_type = kMTCharWise; cap->oap->inclusive = false; if (curwin->w_p_wrap && curwin->w_width_inner != 0) { - int width1 = curwin->w_width_inner - curwin_col_off(); - int width2 = width1 + curwin_col_off2(); + int width1 = curwin->w_width_inner - win_col_off(curwin); + int width2 = width1 + win_col_off2(curwin); - validate_virtcol(); + validate_virtcol(curwin); i = 0; if (curwin->w_virtcol >= (colnr_T)width1 && width2 > 0) { i = (curwin->w_virtcol - width1) / width2 * width2 + width1; @@ -5239,10 +5239,10 @@ static void nv_g_home_m_cmd(cmdarg_T *cap) // 'relativenumber' is on and lines are wrapping the middle can be more // to the left. if (cap->nchar == 'm') { - i += (curwin->w_width_inner - curwin_col_off() - + ((curwin->w_p_wrap && i > 0) ? curwin_col_off2() : 0)) / 2; + i += (curwin->w_width_inner - win_col_off(curwin) + + ((curwin->w_p_wrap && i > 0) ? win_col_off2(curwin) : 0)) / 2; } - coladvance((colnr_T)i); + coladvance(curwin, (colnr_T)i); if (flag) { do { i = gchar_cursor(); @@ -5284,7 +5284,7 @@ static void nv_g_dollar_cmd(cmdarg_T *cap) { oparg_T *oap = cap->oap; int i; - int col_off = curwin_col_off(); + int col_off = win_col_off(curwin); const bool flag = cap->nchar == K_END || cap->nchar == K_KEND; oap->motion_type = kMTCharWise; @@ -5293,14 +5293,14 @@ static void nv_g_dollar_cmd(cmdarg_T *cap) curwin->w_curswant = MAXCOL; // so we stay at the end if (cap->count1 == 1) { int width1 = curwin->w_width_inner - col_off; - int width2 = width1 + curwin_col_off2(); + int width2 = width1 + win_col_off2(curwin); - validate_virtcol(); + validate_virtcol(curwin); i = width1 - 1; if (curwin->w_virtcol >= (colnr_T)width1) { i += ((curwin->w_virtcol - width1) / width2 + 1) * width2; } - coladvance((colnr_T)i); + coladvance(curwin, (colnr_T)i); // Make sure we stick in this column. update_curswant_force(); @@ -5321,7 +5321,7 @@ static void nv_g_dollar_cmd(cmdarg_T *cap) cursor_down(cap->count1 - 1, false); } i = curwin->w_leftcol + curwin->w_width_inner - col_off - 1; - coladvance((colnr_T)i); + coladvance(curwin, (colnr_T)i); // if the character doesn't fit move one back if (curwin->w_cursor.col > 0 && utf_ptr2cells(get_cursor_pos_ptr()) > 1) { @@ -5352,7 +5352,7 @@ static void nv_gi_cmd(cmdarg_T *cap) check_cursor_lnum(curwin); int i = (int)get_cursor_line_len(); if (curwin->w_cursor.col > (colnr_T)i) { - if (virtual_active()) { + if (virtual_active(curwin)) { curwin->w_cursor.coladd += curwin->w_cursor.col - i; } curwin->w_cursor.col = i; @@ -5480,9 +5480,9 @@ static void nv_g_cmd(cmdarg_T *cap) oap->inclusive = false; i = linetabsize(curwin, curwin->w_cursor.lnum); if (cap->count0 > 0 && cap->count0 <= 100) { - coladvance((colnr_T)(i * cap->count0 / 100)); + coladvance(curwin, (colnr_T)(i * cap->count0 / 100)); } else { - coladvance((colnr_T)(i / 2)); + coladvance(curwin, (colnr_T)(i / 2)); } curwin->w_set_curswant = true; break; @@ -5705,11 +5705,11 @@ static void n_opencmd(cmdarg_T *cap) if (cap->cmdchar == 'O') { // Open above the first line of a folded sequence of lines - hasFolding(curwin->w_cursor.lnum, + hasFolding(curwin, curwin->w_cursor.lnum, &curwin->w_cursor.lnum, NULL); } else { // Open below the last line of a folded sequence of lines - hasFolding(curwin->w_cursor.lnum, + hasFolding(curwin, curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum); } // trigger TextChangedI for the 'o/O' command @@ -5890,7 +5890,7 @@ static void nv_pipe(cmdarg_T *cap) cap->oap->inclusive = false; beginline(0); if (cap->count0 > 0) { - coladvance((colnr_T)(cap->count0 - 1)); + coladvance(curwin, (colnr_T)(cap->count0 - 1)); curwin->w_curswant = (colnr_T)(cap->count0 - 1); } else { curwin->w_curswant = 0; @@ -5986,8 +5986,8 @@ static void adjust_cursor(oparg_T *oap) // - 'virtualedit' is not "all" and not "onemore". if (curwin->w_cursor.col > 0 && gchar_cursor() == NUL && (!VIsual_active || *p_sel == 'o') - && !virtual_active() - && (get_ve_flags() & VE_ONEMORE) == 0) { + && !virtual_active(curwin) + && (get_ve_flags(curwin) & VE_ONEMORE) == 0) { curwin->w_cursor.col--; // prevent cursor from moving on the trail byte mb_adjust_cursor(); @@ -6150,7 +6150,7 @@ static void nv_esc(cmdarg_T *cap) if (VIsual_active) { end_visual_mode(); // stop Visual - check_cursor_col(); // make sure cursor is not beyond EOL + check_cursor_col(curwin); // make sure cursor is not beyond EOL curwin->w_set_curswant = true; redraw_curbuf_later(UPD_INVERTED); } else if (no_reason) { @@ -6163,12 +6163,12 @@ static void nv_esc(cmdarg_T *cap) void set_cursor_for_append_to_line(void) { curwin->w_set_curswant = true; - if (get_ve_flags() == VE_ALL) { + if (get_ve_flags(curwin) == VE_ALL) { const int save_State = State; // Pretend Insert mode here to allow the cursor on the // character past the end of the line State = MODE_INSERT; - coladvance(MAXCOL); + coladvance(curwin, MAXCOL); State = save_State; } else { curwin->w_cursor.col += (colnr_T)strlen(get_cursor_pos_ptr()); @@ -6206,7 +6206,7 @@ static void nv_edit(cmdarg_T *cap) case 'a': // "a"ppend is like "i"nsert on the next character. // increment coladd when in virtual space, increment the // column otherwise, also to append after an unprintable char - if (virtual_active() + if (virtual_active(curwin) && (curwin->w_cursor.coladd > 0 || *get_cursor_pos_ptr() == NUL || *get_cursor_pos_ptr() == TAB)) { @@ -6223,7 +6223,7 @@ static void nv_edit(cmdarg_T *cap) // Pretend Insert mode here to allow the cursor on the // character past the end of the line State = MODE_INSERT; - coladvance(getviscol()); + coladvance(curwin, getviscol()); State = save_State; } @@ -6578,7 +6578,7 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent) // line. if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; - coladvance(MAXCOL); + coladvance(curwin, MAXCOL); } } auto_format(false, true); diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 4e27c44262..af5f2fae34 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -1587,7 +1587,7 @@ int op_delete(oparg_T *oap) kExtmarkUndo); } - check_cursor_col(); + check_cursor_col(curwin); changed_lines(curbuf, curwin->w_cursor.lnum, curwin->w_cursor.col, oap->end.lnum + 1, 0, true); oap->line_count = 0; // no lines deleted @@ -1637,7 +1637,7 @@ int op_delete(oparg_T *oap) coladvance_force(getviscol2(oap->start.col, oap->start.coladd)); oap->start = curwin->w_cursor; if (oap->line_count == 1) { - coladvance(endcol); + coladvance(curwin, endcol); oap->end.col = curwin->w_cursor.col; oap->end.coladd = curwin->w_cursor.coladd; curwin->w_cursor = oap->start; @@ -1840,7 +1840,7 @@ static int op_replace(oparg_T *oap, int c) pos_T vpos; vpos.lnum = curwin->w_cursor.lnum; - getvpos(&vpos, oap->start_vcol); + getvpos(curwin, &vpos, oap->start_vcol); bd.startspaces += vpos.coladd; n = bd.startspaces; } else { @@ -1975,7 +1975,7 @@ static int op_replace(oparg_T *oap, int c) } coladvance_force(getviscol()); if (curwin->w_cursor.lnum == oap->end.lnum) { - getvpos(&oap->end, end_vcol); + getvpos(curwin, &oap->end, end_vcol); } } // with "coladd" set may move to just after a TAB @@ -2018,7 +2018,7 @@ static int op_replace(oparg_T *oap, int c) } curwin->w_cursor = oap->start; - check_cursor(); + check_cursor(curwin); changed_lines(curbuf, oap->start.lnum, oap->start.col, oap->end.lnum + 1, 0, true); if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) { @@ -2260,7 +2260,7 @@ void op_insert(oparg_T *oap, int count1) } } else { curwin->w_cursor = oap->end; - check_cursor_col(); + check_cursor_col(curwin); // Works just like an 'i'nsert on the next character. if (!LINEEMPTY(curwin->w_cursor.lnum) @@ -2393,7 +2393,7 @@ void op_insert(oparg_T *oap, int count1) } curwin->w_cursor.col = oap->start.col; - check_cursor(); + check_cursor(curwin); xfree(ins_text); } } @@ -2488,7 +2488,7 @@ int op_change(oparg_T *oap) // initial coladd offset as part of "startspaces" if (bd.is_short) { vpos.lnum = linenr; - getvpos(&vpos, oap->start_vcol); + getvpos(curwin, &vpos, oap->start_vcol); } else { vpos.coladd = 0; } @@ -2509,7 +2509,7 @@ int op_change(oparg_T *oap) 0, vpos.coladd + ins_len, kExtmarkUndo); } } - check_cursor(); + check_cursor(curwin); changed_lines(curbuf, oap->start.lnum + 1, 0, oap->end.lnum + 1, 0, true); xfree(ins_text); } @@ -2843,7 +2843,7 @@ void do_put(int regname, yankreg_T *reg, int dir, int count, int flags) bool allocated = false; const pos_T orig_start = curbuf->b_op_start; const pos_T orig_end = curbuf->b_op_end; - unsigned cur_ve_flags = get_ve_flags(); + unsigned cur_ve_flags = get_ve_flags(curwin); if (flags & PUT_FIXINDENT) { orig_indent = get_indent(); @@ -3064,9 +3064,9 @@ void do_put(int regname, yankreg_T *reg, int dir, int count, int flags) // Correct line number for closed fold. Don't move the cursor yet, // u_save() uses it. if (dir == BACKWARD) { - hasFolding(lnum, &lnum, NULL); + hasFolding(curwin, lnum, &lnum, NULL); } else { - hasFolding(lnum, NULL, &lnum); + hasFolding(curwin, lnum, NULL, &lnum); } if (dir == FORWARD) { lnum++; @@ -3362,7 +3362,7 @@ void do_put(int regname, yankreg_T *reg, int dir, int count, int flags) pos_T pos = { .lnum = lnum, }; - if (getvpos(&pos, vcol) == OK) { + if (getvpos(curwin, &pos, vcol) == OK) { col = pos.col; } else { col = MAXCOL; @@ -3616,7 +3616,7 @@ end: /// there move it left. void adjust_cursor_eol(void) { - unsigned cur_ve_flags = get_ve_flags(); + unsigned cur_ve_flags = get_ve_flags(curwin); const bool adj_cursor = (curwin->w_cursor.col > 0 && gchar_cursor() == NUL @@ -4078,7 +4078,7 @@ int do_join(size_t count, bool insert_space, bool save_undo, bool use_formatopti // vim: use the column of the last join curwin->w_cursor.col = (vim_strchr(p_cpo, CPO_JOINCOL) != NULL ? currsize : col); - check_cursor_col(); + check_cursor_col(curwin); curwin->w_cursor.coladd = 0; curwin->w_set_curswant = true; @@ -4445,7 +4445,7 @@ bool do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) // "Unsigned" const bool do_unsigned = vim_strchr(curbuf->b_p_nf, 'u') != NULL; - if (virtual_active()) { + if (virtual_active(curwin)) { save_coladd = pos->coladd; pos->coladd = 0; } @@ -4767,7 +4767,7 @@ theend: curwin->w_cursor = save_cursor; } else if (did_change) { curwin->w_set_curswant = true; - } else if (virtual_active()) { + } else if (virtual_active(curwin)) { curwin->w_cursor.coladd = save_coladd; } @@ -5349,7 +5349,7 @@ void cursor_pos_info(dict_T *dict) switch (l_VIsual_mode) { case Ctrl_V: - virtual_op = virtual_active(); + virtual_op = virtual_active(curwin); block_prep(&oparg, &bd, lnum, false); virtual_op = kNone; s = bd.textstart; @@ -5438,7 +5438,7 @@ void cursor_pos_info(dict_T *dict) } } else { char *p = get_cursor_line_ptr(); - validate_virtcol(); + validate_virtcol(curwin); col_print(buf1, sizeof(buf1), (int)curwin->w_cursor.col + 1, (int)curwin->w_virtcol + 1); col_print(buf2, sizeof(buf2), (int)strlen(p), linetabsize_str(p)); @@ -5523,7 +5523,7 @@ static void op_colon(oparg_T *oap) // When using !! on a closed fold the range ".!" works best to operate // on, it will be made the whole closed fold later. linenr_T endOfStartFold = oap->start.lnum; - hasFolding(oap->start.lnum, NULL, &endOfStartFold); + hasFolding(curwin, oap->start.lnum, NULL, &endOfStartFold); if (oap->end.lnum != oap->start.lnum && oap->end.lnum != endOfStartFold) { // Make it a range with the end line. stuffcharReadbuff(','); @@ -5534,7 +5534,7 @@ static void op_colon(oparg_T *oap) } else if (oap->start.lnum == curwin->w_cursor.lnum // do not use ".+number" for a closed fold, it would count // folded lines twice - && !hasFolding(oap->end.lnum, NULL, NULL)) { + && !hasFolding(curwin, oap->end.lnum, NULL, NULL)) { stuffReadbuff(".+"); stuffnumReadbuff(oap->line_count - 1); } else { @@ -5696,11 +5696,11 @@ static void get_op_vcol(oparg_T *oap, colnr_T redo_VIsual_vcol, bool initial) // (Actually, this does convert column positions into character // positions) curwin->w_cursor.lnum = oap->end.lnum; - coladvance(oap->end_vcol); + coladvance(curwin, oap->end_vcol); oap->end = curwin->w_cursor; curwin->w_cursor = oap->start; - coladvance(oap->start_vcol); + coladvance(curwin, oap->start_vcol); oap->start = curwin->w_cursor; } @@ -5825,7 +5825,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) if (redo_VIsual.rv_vcol == MAXCOL || VIsual_mode == 'v') { if (VIsual_mode == 'v') { if (redo_VIsual.rv_line_count <= 1) { - validate_virtcol(); + validate_virtcol(curwin); curwin->w_curswant = curwin->w_virtcol + redo_VIsual.rv_vcol - 1; } else { curwin->w_curswant = redo_VIsual.rv_vcol; @@ -5833,7 +5833,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) } else { curwin->w_curswant = MAXCOL; } - coladvance(curwin->w_curswant); + coladvance(curwin, curwin->w_curswant); } cap->count0 = redo_VIsual.rv_count; cap->count1 = (cap->count0 == 0 ? 1 : cap->count0); @@ -5880,13 +5880,13 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) if (lt(oap->start, curwin->w_cursor)) { // Include folded lines completely. if (!VIsual_active) { - if (hasFolding(oap->start.lnum, &oap->start.lnum, NULL)) { + if (hasFolding(curwin, oap->start.lnum, &oap->start.lnum, NULL)) { oap->start.col = 0; } if ((curwin->w_cursor.col > 0 || oap->inclusive || oap->motion_type == kMTLineWise) - && hasFolding(curwin->w_cursor.lnum, NULL, + && hasFolding(curwin, curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum)) { curwin->w_cursor.col = (colnr_T)strlen(get_cursor_line_ptr()); } @@ -5901,11 +5901,11 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) } else { // Include folded lines completely. if (!VIsual_active && oap->motion_type == kMTLineWise) { - if (hasFolding(curwin->w_cursor.lnum, &curwin->w_cursor.lnum, + if (hasFolding(curwin, curwin->w_cursor.lnum, &curwin->w_cursor.lnum, NULL)) { curwin->w_cursor.col = 0; } - if (hasFolding(oap->start.lnum, NULL, &oap->start.lnum)) { + if (hasFolding(curwin, oap->start.lnum, NULL, &oap->start.lnum)) { oap->start.col = (colnr_T)strlen(ml_get(oap->start.lnum)); } } @@ -5918,7 +5918,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) oap->line_count = oap->end.lnum - oap->start.lnum + 1; // Set "virtual_op" before resetting VIsual_active. - virtual_op = virtual_active(); + virtual_op = virtual_active(curwin); if (VIsual_active || redo_VIsual_busy) { get_op_vcol(oap, redo_VIsual.rv_vcol, true); @@ -6149,7 +6149,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) oap->excl_tr_ws = cap->cmdchar == 'z'; op_yank(oap, !gui_yank); } - check_cursor_col(); + check_cursor_col(curwin); break; case OP_CHANGE: @@ -6223,7 +6223,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) } else { op_tilde(oap); } - check_cursor_col(); + check_cursor_col(curwin); break; case OP_FORMAT: @@ -6341,7 +6341,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) op_addsub(oap, (linenr_T)cap->count1, redo_VIsual.rv_arg); VIsual_active = false; } - check_cursor_col(); + check_cursor_col(curwin); break; default: clearopbeep(oap); @@ -6353,7 +6353,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) && (oap->op_type == OP_LSHIFT || oap->op_type == OP_RSHIFT || oap->op_type == OP_DELETE)) { reset_lbr(); - coladvance(curwin->w_curswant = old_col); + coladvance(curwin, curwin->w_curswant = old_col); } } else { curwin->w_cursor = old_cursor; diff --git a/src/nvim/option.c b/src/nvim/option.c index 4f1ec59e77..3d7fdefdeb 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -1937,7 +1937,7 @@ static const char *did_set_arabic(optset_T *args) // set rightleft mode if (!win->w_p_rl) { win->w_p_rl = true; - changed_window_setting(); + changed_window_setting(curwin); } // Enable Arabic shaping (major part of what Arabic requires) @@ -1968,7 +1968,7 @@ static const char *did_set_arabic(optset_T *args) // reset rightleft mode if (win->w_p_rl) { win->w_p_rl = false; - changed_window_setting(); + changed_window_setting(curwin); } // 'arabicshape' isn't reset, it is a global option and @@ -3029,7 +3029,7 @@ void check_redraw_for(buf_T *buf, win_T *win, uint32_t flags) if (flags & P_HLONLY) { redraw_later(win, UPD_NOT_VALID); } else { - changed_window_setting_win(win); + changed_window_setting(win); } } if (flags & P_RBUF) { @@ -6104,9 +6104,9 @@ char *get_flp_value(buf_T *buf) } /// Get the local or global value of the 'virtualedit' flags. -unsigned get_ve_flags(void) +unsigned get_ve_flags(win_T *wp) { - return (curwin->w_ve_flags ? curwin->w_ve_flags : ve_flags) & ~(VE_NONE | VE_NONEU); + return (wp->w_ve_flags ? wp->w_ve_flags : ve_flags) & ~(VE_NONE | VE_NONEU); } /// Get the local or global value of 'showbreak'. diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c index 4be08b28f5..f80926726a 100644 --- a/src/nvim/optionstr.c +++ b/src/nvim/optionstr.c @@ -2479,9 +2479,8 @@ const char *did_set_virtualedit(optset_T *args) } else if (strcmp(ve, args->os_oldval.string.data) != 0) { // Recompute cursor position in case the new 've' setting // changes something. - validate_virtcol_win(win); - // XXX: this only works when win == curwin - coladvance(win->w_virtcol); + validate_virtcol(win); + coladvance(win, win->w_virtcol); } } return NULL; diff --git a/src/nvim/plines.c b/src/nvim/plines.c index eca07f0144..ec13e5b5b7 100644 --- a/src/nvim/plines.c +++ b/src/nvim/plines.c @@ -540,7 +540,7 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *en if (ci.chr.value == TAB && (State & MODE_NORMAL) && !wp->w_p_list - && !virtual_active() + && !virtual_active(wp) && !(VIsual_active && ((*p_sel == 'e') || ltoreq(*pos, VIsual)))) { // cursor at end *cursor = vcol + incr - 1; @@ -583,7 +583,7 @@ void getvvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *e { colnr_T col; - if (virtual_active()) { + if (virtual_active(wp)) { // For virtual mode, only want one value getvcol(wp, pos, &col, NULL, NULL); @@ -902,7 +902,7 @@ int64_t win_text_height(win_T *const wp, const linenr_T start_lnum, const int64_ if (start_vcol >= 0) { linenr_T lnum_next = lnum; - const bool folded = hasFoldingWin(wp, lnum, &lnum, &lnum_next, true, NULL); + const bool folded = hasFolding(wp, lnum, &lnum, &lnum_next); height_cur_nofill = folded ? 1 : plines_win_nofill(wp, lnum, false); height_sum_nofill += height_cur_nofill; const int64_t row_off = (start_vcol < width1 || width2 <= 0) @@ -914,7 +914,7 @@ int64_t win_text_height(win_T *const wp, const linenr_T start_lnum, const int64_ while (lnum <= end_lnum) { linenr_T lnum_next = lnum; - const bool folded = hasFoldingWin(wp, lnum, &lnum, &lnum_next, true, NULL); + const bool folded = hasFolding(wp, lnum, &lnum, &lnum_next); height_sum_fill += win_get_fill(wp, lnum); height_cur_nofill = folded ? 1 : plines_win_nofill(wp, lnum, false); height_sum_nofill += height_cur_nofill; diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c index e34d6fd97f..5e8fb7f5de 100644 --- a/src/nvim/popupmenu.c +++ b/src/nvim/popupmenu.c @@ -140,7 +140,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i // to avoid that must_redraw is set when 'cursorcolumn' is on. pum_is_visible = true; pum_is_drawn = true; - validate_cursor_col(); + validate_cursor_col(curwin); int above_row = 0; int below_row = cmdline_row; @@ -273,7 +273,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i context_lines = 0; } else { // Leave two lines of context if possible - validate_cheight(); + validate_cheight(curwin); if (curwin->w_cline_row + curwin->w_cline_height - curwin->w_wrow >= 3) { context_lines = 3; } else { @@ -995,7 +995,7 @@ static bool pum_set_selected(int n, int repeat) } // Return cursor to where we were - validate_cursor(); + validate_cursor(curwin); redraw_later(curwin, UPD_SOME_VALID); // When the preview window was resized we need to diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 0a4427f3c1..bf53dca167 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -2889,12 +2889,12 @@ static void qf_jump_goto_line(linenr_T qf_lnum, int qf_col, char qf_viscol, char if (qf_col > 0) { curwin->w_cursor.coladd = 0; if (qf_viscol == true) { - coladvance(qf_col - 1); + coladvance(curwin, qf_col - 1); } else { curwin->w_cursor.col = qf_col - 1; } curwin->w_set_curswant = true; - check_cursor(); + check_cursor(curwin); } else { beginline(BL_WHITE | BL_FIX); } @@ -3831,7 +3831,7 @@ void ex_copen(exarg_T *eap) curwin->w_cursor.lnum = lnum; curwin->w_cursor.col = 0; - check_cursor(); + check_cursor(curwin); update_topline(curwin); // scroll to show the line } diff --git a/src/nvim/search.c b/src/nvim/search.c index 2fea28ba7c..a3c9da8e3b 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -1076,11 +1076,11 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char *pat, int count, in // If the cursor is in a closed fold, don't find another match in the same // fold. if (dirc == '/') { - if (hasFolding(pos.lnum, NULL, &pos.lnum)) { + if (hasFolding(curwin, pos.lnum, NULL, &pos.lnum)) { pos.col = MAXCOL - 2; // avoid overflow when adding 1 } } else { - if (hasFolding(pos.lnum, &pos.lnum, NULL)) { + if (hasFolding(curwin, pos.lnum, &pos.lnum, NULL)) { pos.col = 0; } } @@ -1389,7 +1389,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char *pat, int count, in show_top_bot_msg, msgbuf, (count != 1 || has_offset || (!(fdo_flags & FDO_SEARCH) - && hasFolding(curwin->w_cursor.lnum, NULL, + && hasFolding(curwin, curwin->w_cursor.lnum, NULL, NULL))), SEARCH_STAT_DEF_MAX_COUNT, SEARCH_STAT_DEF_TIMEOUT); @@ -4034,7 +4034,7 @@ search_line: setpcmark(); } curwin->w_cursor.lnum = lnum; - check_cursor(); + check_cursor(curwin); } else { if (!GETFILE_SUCCESS(getfile(0, files[depth].name, NULL, true, files[depth].lnum, forceit))) { @@ -4053,7 +4053,7 @@ search_line: if (l_g_do_tagpreview != 0 && curwin != curwin_save && win_valid(curwin_save)) { // Return cursor to where we were - validate_cursor(); + validate_cursor(curwin); redraw_later(curwin, UPD_VALID); win_enter(curwin_save, true); } diff --git a/src/nvim/state.c b/src/nvim/state.c index 0df060ecf4..baa1e75775 100644 --- a/src/nvim/state.c +++ b/src/nvim/state.c @@ -135,9 +135,9 @@ void state_handle_k_event(void) } /// Return true if in the current mode we need to use virtual. -bool virtual_active(void) +bool virtual_active(win_T *wp) { - unsigned cur_ve_flags = get_ve_flags(); + unsigned cur_ve_flags = get_ve_flags(wp); // While an operator is being executed we return "virtual_op", because // VIsual_active has already been reset, thus we can't check for "block" diff --git a/src/nvim/state.h b/src/nvim/state.h index 9002f018d2..8220d90a67 100644 --- a/src/nvim/state.h +++ b/src/nvim/state.h @@ -1,6 +1,7 @@ #pragma once #include "nvim/state_defs.h" // IWYU pragma: keep +#include "nvim/types_defs.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "state.h.generated.h" diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 776498fa29..0265d2d822 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -449,7 +449,7 @@ void do_tag(char *tag, int type, int count, int forceit, bool verbose) } curwin->w_cursor.col = saved_fmark.mark.col; curwin->w_set_curswant = true; - check_cursor(); + check_cursor(curwin); if ((fdo_flags & FDO_TAG) && old_KeyTyped) { foldOpenCursor(); } @@ -3002,7 +3002,7 @@ static int jumpto_tag(const char *lbuf_arg, int forceit, bool keep_help) // A search command may have positioned the cursor beyond the end // of the line. May need to correct that here. - check_cursor(); + check_cursor(curwin); } else { const int save_secure = secure; @@ -3047,7 +3047,7 @@ static int jumpto_tag(const char *lbuf_arg, int forceit, bool keep_help) if (l_g_do_tagpreview != 0 && curwin != curwin_save && win_valid(curwin_save)) { // Return cursor to where we were - validate_cursor(); + validate_cursor(curwin); redraw_later(curwin, UPD_VALID); win_enter(curwin_save, true); } diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index b5a3cffe2f..5a343b4972 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -612,7 +612,7 @@ static void terminal_check_cursor(void) row_to_linenr(term, term->cursor.row)); // Nudge cursor when returning to normal-mode. int off = is_focused(term) ? 0 : (curwin->w_p_rl ? 1 : -1); - coladvance(MAX(0, term->cursor.col + off)); + coladvance(curwin, MAX(0, term->cursor.col + off)); } // Function executed before each iteration of terminal mode. @@ -626,7 +626,7 @@ static int terminal_check(VimState *state) } terminal_check_cursor(); - validate_cursor(); + validate_cursor(curwin); if (must_redraw) { update_screen(); diff --git a/src/nvim/textformat.c b/src/nvim/textformat.c index 2cb08df7b5..41fb543994 100644 --- a/src/nvim/textformat.c +++ b/src/nvim/textformat.c @@ -157,7 +157,7 @@ void internal_format(int textwidth, int second_indent, int flags, bool format_on } // find column of textwidth border - coladvance((colnr_T)textwidth); + coladvance(curwin, (colnr_T)textwidth); wantcol = curwin->w_cursor.col; // If startcol is large (a long line), formatting takes too much @@ -690,9 +690,9 @@ void auto_format(bool trailblank, bool prev_line) if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { // "cannot happen" curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; - coladvance(MAXCOL); + coladvance(curwin, MAXCOL); } else { - check_cursor_col(); + check_cursor_col(curwin); } // Insert mode: If the cursor is now after the end of the line while it @@ -715,7 +715,7 @@ void auto_format(bool trailblank, bool prev_line) } } - check_cursor(); + check_cursor(curwin); } /// When an extra space was added to continue a paragraph for auto-formatting, @@ -839,7 +839,7 @@ void op_format(oparg_T *oap, bool keep_cursor) saved_cursor.lnum = 0; // formatting may have made the cursor position invalid - check_cursor(); + check_cursor(curwin); } if (oap->is_VIsual) { @@ -1063,7 +1063,7 @@ void format_lines(linenr_T line_count, bool avoid_fex) // put cursor on last non-space State = MODE_NORMAL; // don't go past end-of-line - coladvance(MAXCOL); + coladvance(curwin, MAXCOL); while (curwin->w_cursor.col && ascii_isspace(gchar_cursor())) { dec_cursor(); } diff --git a/src/nvim/textobject.c b/src/nvim/textobject.c index d9c2b3b111..8ac63aad16 100644 --- a/src/nvim/textobject.c +++ b/src/nvim/textobject.c @@ -187,7 +187,7 @@ bool findpar(bool *pincl, int dir, int count, int what, bool both) // skip folded lines fold_skipped = false; - if (first && hasFolding(curr, &fold_first, &fold_last)) { + if (first && hasFolding(curwin, curr, &fold_first, &fold_last)) { curr = ((dir > 0) ? fold_last : fold_first) + dir; fold_skipped = true; } @@ -318,8 +318,8 @@ int fwd_word(int count, bool bigword, bool eol) while (--count >= 0) { // When inside a range of folded lines, move to the last char of the // last line. - if (hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum)) { - coladvance(MAXCOL); + if (hasFolding(curwin, curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum)) { + coladvance(curwin, MAXCOL); } int sclass = cls(); // starting class @@ -374,7 +374,7 @@ int bck_word(int count, bool bigword, bool stop) while (--count >= 0) { // When inside a range of folded lines, move to the first char of the // first line. - if (hasFolding(curwin->w_cursor.lnum, &curwin->w_cursor.lnum, NULL)) { + if (hasFolding(curwin, curwin->w_cursor.lnum, &curwin->w_cursor.lnum, NULL)) { curwin->w_cursor.col = 0; } sclass = cls(); @@ -431,8 +431,8 @@ int end_word(int count, bool bigword, bool stop, bool empty) while (--count >= 0) { // When inside a range of folded lines, move to the last char of the // last line. - if (hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum)) { - coladvance(MAXCOL); + if (hasFolding(curwin, curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum)) { + coladvance(curwin, MAXCOL); } sclass = cls(); if (inc_cursor() == -1) { diff --git a/src/nvim/undo.c b/src/nvim/undo.c index e9170ba858..ba720c9f6a 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -477,7 +477,7 @@ int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, bool r uhp->uh_entry = NULL; uhp->uh_getbot_entry = NULL; uhp->uh_cursor = curwin->w_cursor; // save cursor pos. for undo - if (virtual_active() && curwin->w_cursor.coladd > 0) { + if (virtual_active(curwin) && curwin->w_cursor.coladd > 0) { uhp->uh_cursor_vcol = getviscol(); } else { uhp->uh_cursor_vcol = -1; @@ -2488,8 +2488,8 @@ static void u_undoredo(bool undo, bool do_buf_event) if (curwin->w_cursor.lnum <= curbuf->b_ml.ml_line_count) { if (curhead->uh_cursor.lnum == curwin->w_cursor.lnum) { curwin->w_cursor.col = curhead->uh_cursor.col; - if (virtual_active() && curhead->uh_cursor_vcol >= 0) { - coladvance(curhead->uh_cursor_vcol); + if (virtual_active(curwin) && curhead->uh_cursor_vcol >= 0) { + coladvance(curwin, curhead->uh_cursor_vcol); } else { curwin->w_cursor.coladd = 0; } @@ -2506,7 +2506,7 @@ static void u_undoredo(bool undo, bool do_buf_event) } // Make sure the cursor is on an existing line and column. - check_cursor(); + check_cursor(curwin); // Remember where we are for "g-" and ":earlier 10s". curbuf->b_u_seq_cur = curhead->uh_seq; @@ -3073,7 +3073,7 @@ void u_undoline(void) } curwin->w_cursor.col = t; curwin->w_cursor.lnum = curbuf->b_u_line_lnum; - check_cursor_col(); + check_cursor_col(curwin); } /// Allocate memory and copy curbuf line into it. diff --git a/src/nvim/window.c b/src/nvim/window.c index 9468207d41..6eee98fc35 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -766,7 +766,7 @@ void win_set_buf(win_T *win, buf_T *buf, bool noautocmd, Error *err) // If window is not current, state logic will not validate its cursor. So do it now. // Still needed if do_buffer returns FAIL (e.g: autocmds abort script after buffer was set). - validate_cursor(); + validate_cursor(curwin); cleanup: restore_win_noblock(&switchwin, true); @@ -2872,7 +2872,7 @@ int win_close(win_T *win, bool free_buf, bool force) // The cursor position may be invalid if the buffer changed after last // using the window. - check_cursor(); + check_cursor(curwin); } if (!was_floating) { @@ -4921,8 +4921,8 @@ static void win_enter_ext(win_T *const wp, const int flags) curwin = wp; curbuf = wp->w_buffer; - check_cursor(); - if (!virtual_active()) { + check_cursor(curwin); + if (!virtual_active(curwin)) { curwin->w_cursor.coladd = 0; } if (*p_spk == 'c') { @@ -6638,7 +6638,7 @@ void scroll_to_fraction(win_T *wp, int prev_height) } } else if (sline > 0) { while (sline > 0 && lnum > 1) { - hasFoldingWin(wp, lnum, &lnum, NULL, true, NULL); + hasFolding(wp, lnum, &lnum, NULL); if (lnum == 1) { // first line in buffer is folded line_size = 1; @@ -6658,7 +6658,7 @@ void scroll_to_fraction(win_T *wp, int prev_height) if (sline < 0) { // Line we want at top would go off top of screen. Use next // line instead. - hasFoldingWin(wp, lnum, NULL, &lnum, true, NULL); + hasFolding(wp, lnum, NULL, &lnum); lnum++; wp->w_wrow -= line_size + sline; } else if (sline > 0) { @@ -6699,7 +6699,7 @@ void win_set_inner_size(win_T *wp, bool valid_cursor) if (wp == curwin && *p_spk == 'c') { // w_wrow needs to be valid. When setting 'laststatus' this may // call win_new_height() recursively. - validate_cursor(); + validate_cursor(curwin); } if (wp->w_height_inner != prev_height) { return; // Recursive call already changed the size, bail out. diff --git a/test/client/uv_stream.lua b/test/client/uv_stream.lua index 0540c44eb2..adf002ba1e 100644 --- a/test/client/uv_stream.lua +++ b/test/client/uv_stream.lua @@ -136,7 +136,7 @@ function ChildProcessStream.spawn(argv, env, io_extra) end --- @diagnostic disable-next-line:missing-fields self._proc, self._pid = uv.spawn(prog, { - stdio = { self._child_stdin, self._child_stdout, 2, io_extra }, + stdio = { self._child_stdin, self._child_stdout, 1, io_extra }, args = args, --- @diagnostic disable-next-line:assign-type-mismatch env = env, diff --git a/test/functional/api/buffer_spec.lua b/test/functional/api/buffer_spec.lua index 78d220ff57..f46cf7a315 100644 --- a/test/functional/api/buffer_spec.lua +++ b/test/functional/api/buffer_spec.lua @@ -121,6 +121,66 @@ describe('api/buf', function() eq({ 5, 2 }, api.nvim_win_get_cursor(win2)) end) + it('cursor position is maintained consistently with viewport', function() + local screen = Screen.new(20, 12) + screen:set_default_attr_ids { + [1] = { bold = true, foreground = Screen.colors.Blue1 }, + [2] = { reverse = true, bold = true }, + [3] = { reverse = true }, + } + screen:attach() + + local lines = { 'line1', 'line2', 'line3', 'line4', 'line5', 'line6' } + local buf = api.nvim_get_current_buf() + + api.nvim_buf_set_lines(buf, 0, -1, true, lines) + + command('6') + command('new') + screen:expect { + grid = [[ + ^ | + {1:~ }|*4 + {2:[No Name] }| + line5 | + line6 | + {1:~ }|*2 + {3:[No Name] [+] }| + | + ]], + } + + api.nvim_buf_set_lines(buf, 0, -1, true, lines) + screen:expect { + grid = [[ + ^ | + {1:~ }|*4 + {2:[No Name] }| + line3 | + line4 | + line5 | + line6 | + {3:[No Name] [+] }| + | + ]], + } + + command('wincmd w') + screen:expect { + grid = [[ + | + {1:~ }|*4 + {3:[No Name] }| + line3 | + line4 | + line5 | + ^line6 | + {2:[No Name] [+] }| + | + ]], + } + end) + it('line_count has defined behaviour for unloaded buffers', function() -- we'll need to know our bufnr for when it gets unloaded local bufnr = api.nvim_buf_get_number(0) @@ -323,20 +383,20 @@ describe('api/buf', function() ]], } - -- inserting just before topline scrolls up api.nvim_buf_set_lines(buf, 3, 3, true, { 'mmm' }) screen:expect { grid = [[ ^ | {1:~ }|*4 {2:[No Name] }| - mmm | wwweeee | xxx | yyy | + zzz | {3:[No Name] [+] }| | ]], + unchanged = true, } end) @@ -402,7 +462,6 @@ describe('api/buf', function() ]], } - -- inserting just before topline scrolls up api.nvim_buf_set_lines(buf, 3, 3, true, { 'mmm' }) screen:expect { grid = [[ @@ -412,10 +471,10 @@ describe('api/buf', function() mmm | wwweeee | {2:[No Name] [+] }| - mmm | wwweeee | xxx | yyy | + zzz | {3:[No Name] [+] }| | ]], diff --git a/test/functional/lua/ffi_spec.lua b/test/functional/lua/ffi_spec.lua index c9e8e9d4ca..4229e4af9b 100644 --- a/test/functional/lua/ffi_spec.lua +++ b/test/functional/lua/ffi_spec.lua @@ -13,15 +13,19 @@ describe('ffi.cdef', function() eq( 12, - exec_lua [[ + exec_lua [=[ local ffi = require('ffi') - ffi.cdef('int curwin_col_off(void);') + ffi.cdef [[ + typedef struct window_S win_T; + int win_col_off(win_T *wp); + extern win_T *curwin; + ]] vim.cmd('set number numberwidth=4 signcolumn=yes:4') - return ffi.C.curwin_col_off() - ]] + return ffi.C.win_col_off(ffi.C.curwin) + ]=] ) eq( @@ -30,7 +34,6 @@ describe('ffi.cdef', function() local ffi = require('ffi') ffi.cdef[[ - typedef struct window_S win_T; typedef struct {} stl_hlrec_t; typedef struct {} StlClickRecord; typedef struct {} statuscol_T; -- cgit From bbb68e2a034ad3aaea99178c09301ca458ee8dff Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Wed, 13 Mar 2024 22:06:39 +0000 Subject: vim-patch:9.1.0175: wrong window positions with 'winfix{width,height}' (#27845) Problem: winframe functions incorrectly recompute window positions if the altframe wasn't adjacent to the closed frame, which is possible if adjacent windows had 'winfix{width,height}' set. Solution: recompute for windows within the parent of the altframe and closed frame. Skip this (as before) if the altframe was top/left, but only if adjacent to the closed frame, as positions won't change in that case. Also correct the return value documentation for win_screenpos. (Sean Dewar) The issue revealed itself after removing the win_comp_pos call below winframe_restore in win_splitmove. Similarly, wrong positions could result from windows closed in other tabpages, as win_free_mem uses winframe_remove (at least until it is entered later, where enter_tabpage calls win_comp_pos). NOTE: As win_comp_pos handles only curtab, it's possible via other means for positions in non-current tabpages to be wrong (e.g: after changing 'laststatus', 'showtabline', etc.). Given enter_tabpage recomputes it, maybe it's intentional as an optimization? Should probably be documented in win_screenpos then, but I won't address that here. closes: vim/vim#14191 Nvim: don't reuse "wp" for "topleft" in winframe_remove, so the change integrates better with the call to winframe_find_altwin before it. https://github.com/vim/vim/commit/5866bc3a0f54115d5982fdc09bdbe4c45069265a --- runtime/doc/builtin.txt | 3 +- runtime/lua/vim/_meta/vimfn.lua | 3 +- src/nvim/eval.lua | 4 +- src/nvim/window.c | 35 +++++++++-------- test/old/testdir/test_window_cmd.vim | 73 ++++++++++++++++++++++++++++++++++-- 5 files changed, 91 insertions(+), 27 deletions(-) diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index 3df24a3b5f..68d1874542 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -8898,8 +8898,7 @@ win_screenpos({nr}) *win_screenpos()* [1, 1], unless there is a tabline, then it is [2, 1]. {nr} can be the window number or the |window-ID|. Use zero for the current window. - Returns [0, 0] if the window cannot be found in the current - tabpage. + Returns [0, 0] if the window cannot be found. win_splitmove({nr}, {target} [, {options}]) *win_splitmove()* Temporarily switch to window {target}, then move window {nr} diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index 2b93ea7d4e..fb5e2a727e 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -10592,8 +10592,7 @@ function vim.fn.win_move_statusline(nr, offset) end --- [1, 1], unless there is a tabline, then it is [2, 1]. --- {nr} can be the window number or the |window-ID|. Use zero --- for the current window. ---- Returns [0, 0] if the window cannot be found in the current ---- tabpage. +--- Returns [0, 0] if the window cannot be found. --- --- @param nr integer --- @return any diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 96dc32259f..810cd2286b 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -12688,9 +12688,7 @@ M.funcs = { [1, 1], unless there is a tabline, then it is [2, 1]. {nr} can be the window number or the |window-ID|. Use zero for the current window. - Returns [0, 0] if the window cannot be found in the current - tabpage. - + Returns [0, 0] if the window cannot be found. ]=], name = 'win_screenpos', params = { { 'nr', 'integer' } }, diff --git a/src/nvim/window.c b/src/nvim/window.c index 6eee98fc35..c9c2124730 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -3148,6 +3148,13 @@ win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp, frame_T **unflat_al } frame_T *frp_close = win->w_frame; + + // Save the position of the containing frame (which will also contain the + // altframe) before we remove anything, to recompute window positions later. + const win_T *const topleft = frame2win(frp_close->fr_parent); + int row = topleft->w_winrow; + int col = topleft->w_wincol; + // Remove this frame from the list of frames. frame_remove(frp_close); @@ -3160,13 +3167,10 @@ win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp, frame_T **unflat_al altfr == frp_close->fr_next, false); } - // If rows/columns go to a window below/right its positions need to be - // updated. Can only be done after the sizes have been updated. - if (altfr == frp_close->fr_next) { - int row = win->w_winrow; - int col = win->w_wincol; - - frame_comp_pos(altfr, &row, &col); + // If the altframe wasn't adjacent and left/above, resizing it will have + // changed window positions within the parent frame. Recompute them. + if (altfr != frp_close->fr_prev) { + frame_comp_pos(frp_close->fr_parent, &row, &col); } if (unflat_altfr == NULL) { @@ -3354,25 +3358,24 @@ void winframe_restore(win_T *wp, int dir, frame_T *unflat_altfr) } } - int row = wp->w_winrow; - int col = wp->w_wincol; - // Restore the lost room that was redistributed to the altframe. Also // adjusts window sizes to fit restored statuslines/separators, if needed. if (dir == 'v') { frame_new_height(unflat_altfr, unflat_altfr->fr_height - frp->fr_height, unflat_altfr == frp->fr_next, false); - row += frp->fr_height; } else if (dir == 'h') { frame_new_width(unflat_altfr, unflat_altfr->fr_width - frp->fr_width, unflat_altfr == frp->fr_next, false); - col += frp->fr_width; } - // If rows/columns went to a window below/right, its positions need to be - // restored. Can only be done after the sizes have been updated. - if (unflat_altfr == frp->fr_next) { - frame_comp_pos(unflat_altfr, &row, &col); + // Recompute window positions within the parent frame to restore them. + // Positions were unchanged if the altframe was adjacent and left/above. + if (unflat_altfr != frp->fr_prev) { + const win_T *const topleft = frame2win(frp->fr_parent); + int row = topleft->w_winrow; + int col = topleft->w_wincol; + + frame_comp_pos(frp->fr_parent, &row, &col); } } diff --git a/test/old/testdir/test_window_cmd.vim b/test/old/testdir/test_window_cmd.vim index 77de5edf8f..50da2beb40 100644 --- a/test/old/testdir/test_window_cmd.vim +++ b/test/old/testdir/test_window_cmd.vim @@ -200,11 +200,11 @@ func Test_window_split_edit_bufnr() %bw! endfunc -func s:win_layout_info() abort +func s:win_layout_info(tp = tabpagenr()) abort return #{ - \ layout: winlayout(), - \ pos_sizes: range(1, winnr('$')) - \ ->map({_, nr -> win_getid(nr)->getwininfo()[0]}) + \ layout: winlayout(a:tp), + \ pos_sizes: range(1, tabpagewinnr(a:tp, '$')) + \ ->map({_, nr -> win_getid(nr, a:tp)->getwininfo()[0]}) \ ->map({_, wininfo -> #{id: wininfo.winid, \ row: wininfo.winrow, \ col: wininfo.wincol, @@ -2145,4 +2145,69 @@ func Test_win_gotoid_splitmove_textlock_cmdwin() \ .. ":call assert_equal('', win_gettype(winnr('#')))\", 'ntx') endfunc +func Test_winfixsize_positions() + " Check positions are correct when closing a window in a non-current tabpage + " causes non-adjacent window to fill the space due to 'winfix{width,height}'. + tabnew + vsplit + wincmd | + split + set winfixheight + split foo + tabfirst + + bwipe! foo + " Save actual values before entering the tabpage. + let info = s:win_layout_info(2) + tabnext + " Compare it with the expected value (after win_comp_pos) from entering. + call assert_equal(s:win_layout_info(), info) + + $tabnew + split + split + wincmd k + belowright vsplit + set winfixwidth + belowright vsplit foo + tabprevious + + bwipe! foo + " Save actual values before entering the tabpage. + let info = s:win_layout_info(3) + tabnext + " Compare it with the expected value (after win_comp_pos) from entering. + call assert_equal(s:win_layout_info(), info) + + " Check positions unchanged when failing to move a window, if 'winfix{width, + " height}' would otherwise cause a non-adjacent window to fill the space. + %bwipe + call assert_fails('execute "split|"->repeat(&lines)', 'E36:') + wincmd p + vsplit + set winfixwidth + vsplit + set winfixwidth + vsplit + vsplit + set winfixwidth + wincmd p + + let info = s:win_layout_info() + call assert_fails('wincmd J', 'E36:') + call assert_equal(info, s:win_layout_info()) + + only + call assert_fails('execute "vsplit|"->repeat(&columns)', 'E36:') + belowright split + set winfixheight + belowright split + + let info = s:win_layout_info() + call assert_fails('wincmd H', 'E36:') + call assert_equal(info, s:win_layout_info()) + + %bwipe +endfunc + " vim: shiftwidth=2 sts=2 expandtab -- cgit From b17be231a61f69b52eb809b6c72b20d3b089495d Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 14 Mar 2024 06:44:50 +0800 Subject: vim-patch:9.1.0178: E1513 might be confusing (#27846) Problem: E1513 might be confusing (Christoph Thoma) Solution: reword error message, fix test to not depend on the actual message fixes: vim/vim#14189 https://github.com/vim/vim/commit/0a32b8854b52381fd17a6fcc5e38a3b9e77c8923 Co-authored-by: Christian Brabandt --- runtime/doc/message.txt | 2 +- src/nvim/globals.h | 2 +- test/old/testdir/test_winfixbuf.vim | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/runtime/doc/message.txt b/runtime/doc/message.txt index 16d88407d5..afe64300e7 100644 --- a/runtime/doc/message.txt +++ b/runtime/doc/message.txt @@ -115,7 +115,7 @@ You cannot have two buffers with exactly the same name. This includes the path leading to the file. *E1513* > - Cannot edit buffer. 'winfixbuf' is enabled + Cannot switch buffer. 'winfixbuf' is enabled If a window has 'winfixbuf' enabled, you cannot change that window's current buffer. You need to set 'nowinfixbuf' before continuing. You may use [!] to diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 06fb95b577..bb9aca38b7 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -972,7 +972,7 @@ EXTERN const char e_undobang_cannot_redo_or_move_branch[] INIT(= N_("E5767: Cannot use :undo! to redo or move to a different undo branch")); EXTERN const char e_winfixbuf_cannot_go_to_buffer[] -INIT(= N_("E1513: Cannot edit buffer. 'winfixbuf' is enabled")); +INIT(= N_("E1513: Cannot switch buffer. 'winfixbuf' is enabled")); EXTERN const char e_trustfile[] INIT(= N_("E5570: Cannot update trust file: %s")); diff --git a/test/old/testdir/test_winfixbuf.vim b/test/old/testdir/test_winfixbuf.vim index 112eccf3ef..320e73f378 100644 --- a/test/old/testdir/test_winfixbuf.vim +++ b/test/old/testdir/test_winfixbuf.vim @@ -493,7 +493,7 @@ func Test_browse_edit_fail() try browse edit! other call assert_equal(l:other, bufnr()) - catch /E338:/ + catch /^Vim\%((\a\+)\)\=:E338:/ " Ignore E338, which occurs if console Vim is built with +browse. " Console Vim without +browse will treat this as a regular :edit. endtry @@ -511,7 +511,7 @@ func Test_browse_edit_pass() try browse write other - catch /E338:/ + catch /^Vim\%((\a\+)\)\=:E338:/ " Ignore E338, which occurs if console Vim is built with +browse. " Console Vim without +browse will treat this as a regular :write. endtry @@ -2532,7 +2532,7 @@ EOF try pyxdo test_winfixbuf_Test_pythonx_pyxdo_set_buffer() - catch /pynvim\.api\.common\.NvimError: E1513: Cannot edit buffer\. 'winfixbuf' is enabled/ + catch /pynvim\.api\.common\.NvimError: E1513:/ let l:caught = 1 endtry @@ -2563,7 +2563,7 @@ func Test_pythonx_pyxfile() try pyxfile file.py - catch /pynvim\.api\.common\.NvimError: E1513: Cannot edit buffer\. 'winfixbuf' is enabled/ + catch /pynvim\.api\.common\.NvimError: E1513:/ let l:caught = 1 endtry @@ -2596,7 +2596,7 @@ import vim buffer = vim.vars["_previous_buffer"] vim.current.buffer = vim.buffers[buffer] EOF - catch /pynvim\.api\.common\.NvimError: E1513: Cannot edit buffer\. 'winfixbuf' is enabled/ + catch /pynvim\.api\.common\.NvimError: E1513:/ let l:caught = 1 endtry -- cgit From 9599e5d28d31cfb0cb16e878f123b74283cca26b Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 14 Mar 2024 06:23:10 +0800 Subject: vim-patch:9.1.0174: 'cursorline' and 'wincolor' hl missing with conceal and wrap Problem: 'cursorline' and 'wincolor' highlight missing with concealed and wrapped lines. Solution: Apply 'cursorline' and 'wincolor' highlight to boguscols. (zeertzjq) Since 'cursorline' and 'wincolor' highlight apply after the end of the line, it is more consistent to have them also apply to boguscols. Assigning MAXCOL to values in ScreenCols[] make mouse click behave the same with 'cursorline' and 'nocursorline', but such behavior may be incorrect, as it puts the cursor on the next screen line. That may be fixed in a future PR. closes: vim/vim#14192 https://github.com/vim/vim/commit/21b0a3df8c4abb884489dfcc0c92b1bbe058f291 --- src/nvim/drawline.c | 13 +++++++ test/functional/legacy/conceal_spec.lua | 62 +++++++++++++++++++++++++++++++++ test/old/testdir/test_conceal.vim | 51 +++++++++++++++++++++++++++ 3 files changed, 126 insertions(+) diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index a7b1d561b6..8b8932be2d 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -2851,6 +2851,19 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s && !wp->w_p_rl; // Not right-to-left. int draw_col = wlv.col - wlv.boguscols; + + // Apply 'cursorline' highlight. + if (wlv.boguscols != 0 && (wlv.line_attr_lowprio != 0 || wlv.line_attr != 0)) { + int attr = hl_combine_attr(wlv.line_attr_lowprio, wlv.line_attr); + while (draw_col < grid->cols) { + linebuf_char[wlv.off] = schar_from_char(' '); + linebuf_attr[wlv.off] = attr; + linebuf_vcol[wlv.off] = MAXCOL; // TODO(zeertzjq): this is wrong + wlv.off++; + draw_col++; + } + } + if (virt_line_offset >= 0) { draw_virt_text_item(buf, virt_line_offset, kv_A(virt_lines, virt_line_index).line, kHlModeReplace, grid->cols, 0); diff --git a/test/functional/legacy/conceal_spec.lua b/test/functional/legacy/conceal_spec.lua index 9a23d16c5b..a7badf6910 100644 --- a/test/functional/legacy/conceal_spec.lua +++ b/test/functional/legacy/conceal_spec.lua @@ -433,6 +433,68 @@ describe('Conceal', function() ]]) end) + -- oldtest: Test_conceal_wrapped_cursorline_wincolor() + it('CursorLine highlight on wrapped lines', function() + local screen = Screen.new(40, 4) + screen:set_default_attr_ids({ + [0] = { bold = true, foreground = Screen.colors.Blue }, -- NonText + [1] = { background = Screen.colors.Green }, -- CursorLine (low-priority) + [2] = { foreground = Screen.colors.Red }, -- CursorLine (high-priority) + }) + screen:attach() + exec([[ + call setline(1, 'one one one |hidden| one one one one one one one one') + syntax match test /|hidden|/ conceal + set conceallevel=2 concealcursor=n cursorline + normal! g$ + hi! CursorLine guibg=Green + ]]) + screen:expect([[ + {1:one one one one one one one on^e }| + {1: one one one }| + {0:~ }| + | + ]]) + command('hi! CursorLine guibg=NONE guifg=Red') + screen:expect([[ + {2:one one one one one one one on^e }| + {2: one one one }| + {0:~ }| + | + ]]) + end) + + -- oldtest: Test_conceal_wrapped_cursorline_wincolor_rightleft() + it('CursorLine highlight on wrapped lines with rightleft', function() + local screen = Screen.new(40, 4) + screen:set_default_attr_ids({ + [0] = { bold = true, foreground = Screen.colors.Blue }, -- NonText + [1] = { background = Screen.colors.Green }, -- CursorLine (low-priority) + [2] = { foreground = Screen.colors.Red }, -- CursorLine (high-priority) + }) + screen:attach() + exec([[ + call setline(1, 'one one one |hidden| one one one one one one one one') + syntax match test /|hidden|/ conceal + set conceallevel=2 concealcursor=n cursorline rightleft + normal! g$ + hi! CursorLine guibg=Green + ]]) + screen:expect([[ + {1: ^eno eno eno eno eno eno eno eno}| + {1: eno eno eno }| + {0: ~}| + | + ]]) + command('hi! CursorLine guibg=NONE guifg=Red') + screen:expect([[ + {2: ^eno eno eno eno eno eno eno eno}| + {2: eno eno eno }| + {0: ~}| + | + ]]) + end) + -- oldtest: Test_conceal_resize_term() it('resize editor', function() local screen = Screen.new(75, 6) diff --git a/test/old/testdir/test_conceal.vim b/test/old/testdir/test_conceal.vim index a679061544..d5a2bcce46 100644 --- a/test/old/testdir/test_conceal.vim +++ b/test/old/testdir/test_conceal.vim @@ -169,6 +169,57 @@ func Test_conceal_with_cursorcolumn() call StopVimInTerminal(buf) endfunc +" Check that 'cursorline' and 'wincolor' apply to the whole line in presence +" of wrapped lines containing concealed text. +func Test_conceal_wrapped_cursorline_wincolor() + CheckScreendump + + let code =<< trim [CODE] + call setline(1, 'one one one |hidden| one one one one one one one one') + syntax match test /|hidden|/ conceal + set conceallevel=2 concealcursor=n cursorline + normal! g$ + [CODE] + + call writefile(code, 'XTest_conceal_cul_wcr', 'D') + let buf = RunVimInTerminal('-S XTest_conceal_cul_wcr', {'rows': 4, 'cols': 40}) + call VerifyScreenDump(buf, 'Test_conceal_cul_wcr_01', {}) + + call term_sendkeys(buf, ":set wincolor=ErrorMsg\n") + call VerifyScreenDump(buf, 'Test_conceal_cul_wcr_02', {}) + + call term_sendkeys(buf, ":set nocursorline\n") + call VerifyScreenDump(buf, 'Test_conceal_cul_wcr_03', {}) + + " clean up + call StopVimInTerminal(buf) +endfunc + +" Same as Test_conceal_wrapped_cursorline_wincolor(), but with 'rightleft'. +func Test_conceal_wrapped_cursorline_wincolor_rightleft() + CheckScreendump + + let code =<< trim [CODE] + call setline(1, 'one one one |hidden| one one one one one one one one') + syntax match test /|hidden|/ conceal + set conceallevel=2 concealcursor=n cursorline rightleft + normal! g$ + [CODE] + + call writefile(code, 'XTest_conceal_cul_wcr_rl', 'D') + let buf = RunVimInTerminal('-S XTest_conceal_cul_wcr_rl', {'rows': 4, 'cols': 40}) + call VerifyScreenDump(buf, 'Test_conceal_cul_wcr_rl_01', {}) + + call term_sendkeys(buf, ":set wincolor=ErrorMsg\n") + call VerifyScreenDump(buf, 'Test_conceal_cul_wcr_rl_02', {}) + + call term_sendkeys(buf, ":set nocursorline\n") + call VerifyScreenDump(buf, 'Test_conceal_cul_wcr_rl_03', {}) + + " clean up + call StopVimInTerminal(buf) +endfunc + func Test_conceal_resize_term() CheckScreendump -- cgit From 2af1dc0116c1914684fa0c3d94032c2698a5230d Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 14 Mar 2024 06:44:41 +0800 Subject: vim-patch:9.1.0176: Cursor column wrong with 'virtualedit' and conceal Problem: Cursor column wrong with 'virtualedit' and conceal. Solution: Correct cursor column at end of line if never reached. (zeertzjq) closes: vim/vim#14190 https://github.com/vim/vim/commit/253ff4dece4e6cc4a9ff3ed935bc78f832b6fb7c --- src/nvim/drawline.c | 8 ++- test/functional/legacy/conceal_spec.lua | 88 +++++++++++++++++++++++++++++++++ test/old/testdir/test_conceal.vim | 54 ++++++++++++++++++++ 3 files changed, 149 insertions(+), 1 deletion(-) diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index 8b8932be2d..8b6c3d58b2 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -2451,11 +2451,17 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s // In the cursor line and we may be concealing characters: correct // the cursor column when we reach its position. + // With 'virtualedit' we may never reach cursor position, but we still + // need to correct the cursor column, so do that at end of line. if (!did_wcol && wp == curwin && lnum == wp->w_cursor.lnum && conceal_cursor_line(wp) - && (int)wp->w_virtcol <= wlv.vcol + wlv.skip_cells) { + && (wlv.vcol + wlv.skip_cells >= wp->w_virtcol || mb_schar == NUL)) { wp->w_wcol = wlv.col - wlv.boguscols; + if (wlv.vcol + wlv.skip_cells < wp->w_virtcol) { + // Cursor beyond end of the line with 'virtualedit'. + wp->w_wcol += wp->w_virtcol - wlv.vcol - wlv.skip_cells; + } wp->w_wrow = wlv.row; did_wcol = true; wp->w_valid |= VALID_WCOL|VALID_WROW|VALID_VIRTCOL; diff --git a/test/functional/legacy/conceal_spec.lua b/test/functional/legacy/conceal_spec.lua index a7badf6910..63ec644a10 100644 --- a/test/functional/legacy/conceal_spec.lua +++ b/test/functional/legacy/conceal_spec.lua @@ -632,4 +632,92 @@ describe('Conceal', function() feed('$') expect_pos(9, 26) end) + + -- oldtest: Test_conceal_virtualedit_after_eol() + it('cursor drawn at correct column with virtualedit', function() + local screen = Screen.new(75, 3) + screen:set_default_attr_ids({ + [0] = { bold = true, foreground = Screen.colors.Blue }, -- NonText + }) + screen:attach() + exec([[ + call setline(1, 'abcdefgh|hidden|ijklmnpop') + syntax match test /|hidden|/ conceal + set conceallevel=2 concealcursor=n virtualedit=all + normal! $ + ]]) + screen:expect([[ + abcdefghijklmnpo^p | + {0:~ }| + | + ]]) + feed('l') + screen:expect([[ + abcdefghijklmnpop^ | + {0:~ }| + | + ]]) + feed('l') + screen:expect([[ + abcdefghijklmnpop ^ | + {0:~ }| + | + ]]) + feed('l') + screen:expect([[ + abcdefghijklmnpop ^ | + {0:~ }| + | + ]]) + feed('rr') + screen:expect([[ + abcdefghijklmnpop ^r | + {0:~ }| + | + ]]) + end) + + -- oldtest: Test_conceal_virtualedit_after_eol_rightleft() + it('cursor drawn correctly with virtualedit and rightleft', function() + local screen = Screen.new(75, 3) + screen:set_default_attr_ids({ + [0] = { bold = true, foreground = Screen.colors.Blue }, -- NonText + }) + screen:attach() + exec([[ + call setline(1, 'abcdefgh|hidden|ijklmnpop') + syntax match test /|hidden|/ conceal + set conceallevel=2 concealcursor=n virtualedit=all rightleft + normal! $ + ]]) + screen:expect([[ + ^popnmlkjihgfedcba| + {0: ~}| + | + ]]) + feed('h') + screen:expect([[ + ^ popnmlkjihgfedcba| + {0: ~}| + | + ]]) + feed('h') + screen:expect([[ + ^ popnmlkjihgfedcba| + {0: ~}| + | + ]]) + feed('h') + screen:expect([[ + ^ popnmlkjihgfedcba| + {0: ~}| + | + ]]) + feed('rr') + screen:expect([[ + ^r popnmlkjihgfedcba| + {0: ~}| + | + ]]) + end) end) diff --git a/test/old/testdir/test_conceal.vim b/test/old/testdir/test_conceal.vim index d5a2bcce46..94d5d1d470 100644 --- a/test/old/testdir/test_conceal.vim +++ b/test/old/testdir/test_conceal.vim @@ -460,4 +460,58 @@ func Test_conceal_mouse_click() set mouse& virtualedit& endfunc +" Test that cursor is drawn at the correct column when it is after end of the +" line with 'virtualedit' and concealing. +func Test_conceal_virtualedit_after_eol() + CheckScreendump + + let code =<< trim [CODE] + call setline(1, 'abcdefgh|hidden|ijklmnpop') + syntax match test /|hidden|/ conceal + set conceallevel=2 concealcursor=n virtualedit=all + normal! $ + [CODE] + call writefile(code, 'XTest_conceal_ve_after_eol', 'D') + let buf = RunVimInTerminal('-S XTest_conceal_ve_after_eol', {'rows': 3}) + call VerifyScreenDump(buf, 'Test_conceal_ve_after_eol_1', {}) + call term_sendkeys(buf, "l") + call VerifyScreenDump(buf, 'Test_conceal_ve_after_eol_2', {}) + call term_sendkeys(buf, "l") + call VerifyScreenDump(buf, 'Test_conceal_ve_after_eol_3', {}) + call term_sendkeys(buf, "l") + call VerifyScreenDump(buf, 'Test_conceal_ve_after_eol_4', {}) + call term_sendkeys(buf, "rr") + call VerifyScreenDump(buf, 'Test_conceal_ve_after_eol_5', {}) + + " clean up + call StopVimInTerminal(buf) +endfunc + +" Same as Test_conceal_virtualedit_after_eol(), but with 'rightleft' set. +func Test_conceal_virtualedit_after_eol_rightleft() + CheckFeature rightleft + CheckScreendump + + let code =<< trim [CODE] + call setline(1, 'abcdefgh|hidden|ijklmnpop') + syntax match test /|hidden|/ conceal + set conceallevel=2 concealcursor=n virtualedit=all rightleft + normal! $ + [CODE] + call writefile(code, 'XTest_conceal_ve_after_eol_rl', 'D') + let buf = RunVimInTerminal('-S XTest_conceal_ve_after_eol_rl', {'rows': 3}) + call VerifyScreenDump(buf, 'Test_conceal_ve_after_eol_rl_1', {}) + call term_sendkeys(buf, "h") + call VerifyScreenDump(buf, 'Test_conceal_ve_after_eol_rl_2', {}) + call term_sendkeys(buf, "h") + call VerifyScreenDump(buf, 'Test_conceal_ve_after_eol_rl_3', {}) + call term_sendkeys(buf, "h") + call VerifyScreenDump(buf, 'Test_conceal_ve_after_eol_rl_4', {}) + call term_sendkeys(buf, "rr") + call VerifyScreenDump(buf, 'Test_conceal_ve_after_eol_rl_5', {}) + + " clean up + call StopVimInTerminal(buf) +endfunc + " vim: shiftwidth=2 sts=2 expandtab -- cgit From 3502aa63f0f4ea8d8982aea81a819424e71029bc Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 14 Mar 2024 12:40:17 +0800 Subject: vim-patch:8.2.4950: text properties position wrong after shifting text (#27849) Problem: Text properties position wrong after shifting text. Solution: Adjust the text properties when shifting a block of text. (closes vim/vim#10418) https://github.com/vim/vim/commit/4b93674159d60c985de906c30f45dbaf2b64056f Most of the patch is already merged. Add an assertion in place of "added". Co-authored-by: LemonBoy --- src/nvim/ops.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/nvim/ops.c b/src/nvim/ops.c index af5f2fae34..3dcbe597fe 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -408,6 +408,7 @@ static void shift_block(oparg_T *oap, int amount) memset(newp + bd.textcol + tabs, ' ', (size_t)spaces); // Note that STRMOVE() copies the trailing NUL. STRMOVE(newp + bd.textcol + tabs + spaces, bd.textstart); + assert(newlen - oldlen == (colnr_T)new_line_len - get_cursor_line_len()); } else { // left char *verbatim_copy_end; // end of the part of the line which is // copied verbatim @@ -494,6 +495,7 @@ static void shift_block(oparg_T *oap, int amount) memset(newp + verbatim_diff, ' ', fill); // Note that STRMOVE() copies the trailing NUL. STRMOVE(newp + verbatim_diff + fill, non_white); + assert(newlen - oldlen == (colnr_T)new_line_len - get_cursor_line_len()); } // replace the line ml_replace(curwin->w_cursor.lnum, newp, false); -- cgit From 090d1fd0b86897d2f5a80a600becf1525398ef30 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Wed, 13 Mar 2024 15:00:43 +0800 Subject: vim-patch:9.1.0172: More code can use ml_get_buf_len() instead of STRLEN() Problem: More code can use ml_get_buf_len() instead of STRLEN(). Solution: Change more STRLEN() calls to ml_get_buf_len(). Also do not set ml_line_textlen in ml_replace_len() if "has_props" is set, because "len_arg" also includes the size of text properties in that case. (zeertzjq) closes: vim/vim#14183 https://github.com/vim/vim/commit/94b7c3233ef534acc669b3083ed1fe59cf3a090b --- src/nvim/change.c | 4 +-- src/nvim/cursor.c | 11 +++--- src/nvim/diff.c | 2 +- src/nvim/drawline.c | 69 ++++++++++++++----------------------- src/nvim/edit.c | 8 ++--- src/nvim/ex_cmds.c | 35 ++++++++++--------- src/nvim/fileio.c | 2 +- src/nvim/fold.c | 7 ++-- src/nvim/insexpand.c | 2 +- src/nvim/mark.c | 2 +- src/nvim/memline.c | 10 ++++-- src/nvim/ops.c | 92 +++++++++++++++++++++++-------------------------- src/nvim/plines.c | 2 +- src/nvim/quickfix.c | 7 ++-- src/nvim/search.c | 14 ++++---- src/nvim/spell.c | 4 +-- src/nvim/spellfile.c | 7 ++-- src/nvim/spellsuggest.c | 7 ++-- src/nvim/statusline.c | 6 ++-- src/nvim/syntax.c | 10 +++--- src/nvim/textformat.c | 13 ++++--- src/nvim/textobject.c | 2 +- 22 files changed, 148 insertions(+), 168 deletions(-) diff --git a/src/nvim/change.c b/src/nvim/change.c index 673907fa27..b6f2be547f 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -1044,7 +1044,7 @@ bool copy_indent(int size, char *src) if (p == NULL) { // Allocate memory for the result: the copied indent, new indent // and the rest of the line. - line_len = (int)strlen(get_cursor_line_ptr()) + 1; + line_len = get_cursor_line_len() + 1; assert(ind_len + line_len >= 0); size_t line_size; STRICT_ADD(ind_len, line_len, &line_size, size_t); @@ -1865,7 +1865,7 @@ bool open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) } if (did_append) { // bail out and just get the final length of the line we just manipulated - bcount_t extra = (bcount_t)strlen(ml_get(curwin->w_cursor.lnum)); + bcount_t extra = ml_get_len(curwin->w_cursor.lnum); extmark_splice(curbuf, (int)curwin->w_cursor.lnum - 1, 0, 0, 0, 0, 1, 0, 1 + extra, kExtmarkUndo); changed_lines(curbuf, curwin->w_cursor.lnum, 0, curwin->w_cursor.lnum, 1, true); diff --git a/src/nvim/cursor.c b/src/nvim/cursor.c index c3f5a36500..91bd217ae8 100644 --- a/src/nvim/cursor.c +++ b/src/nvim/cursor.c @@ -108,9 +108,10 @@ static int coladvance2(win_T *wp, pos_T *pos, bool addspaces, bool finetune, col || ((get_ve_flags(wp) & VE_ONEMORE) && wcol < MAXCOL); char *line = ml_get_buf(wp->w_buffer, pos->lnum); + int linelen = ml_get_buf_len(wp->w_buffer, pos->lnum); if (wcol >= MAXCOL) { - idx = (int)strlen(line) - 1 + one_more; + idx = linelen - 1 + one_more; col = wcol; if ((addspaces || finetune) && !VIsual_active) { @@ -188,7 +189,6 @@ static int coladvance2(win_T *wp, pos_T *pos, bool addspaces, bool finetune, col col = wcol; } else { // Break a tab - int linelen = (int)strlen(line); int correct = wcol - col - csize + 1; // negative!! char *newline; @@ -315,8 +315,7 @@ void check_pos(buf_T *buf, pos_T *pos) } if (pos->col > 0) { - char *line = ml_get_buf(buf, pos->lnum); - colnr_T len = (colnr_T)strlen(line); + colnr_T len = ml_get_buf_len(buf, pos->lnum); if (pos->col > len) { pos->col = len; } @@ -347,7 +346,7 @@ void check_cursor_col(win_T *win) colnr_T oldcoladd = win->w_cursor.col + win->w_cursor.coladd; unsigned cur_ve_flags = get_ve_flags(win); - colnr_T len = (colnr_T)strlen(ml_get_buf(win->w_buffer, win->w_cursor.lnum)); + colnr_T len = ml_get_buf_len(win->w_buffer, win->w_cursor.lnum); if (len == 0) { win->w_cursor.col = 0; } else if (win->w_cursor.col >= len) { @@ -413,7 +412,7 @@ void check_visual_pos(void) VIsual.col = 0; VIsual.coladd = 0; } else { - int len = (int)strlen(ml_get(VIsual.lnum)); + int len = ml_get_len(VIsual.lnum); if (VIsual.col > len) { VIsual.col = len; diff --git a/src/nvim/diff.c b/src/nvim/diff.c index bc91c1e4c2..c680600d39 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -756,7 +756,7 @@ static int diff_write_buffer(buf_T *buf, mmfile_t *m, linenr_T start, linenr_T e // xdiff requires one big block of memory with all the text. for (linenr_T lnum = start; lnum <= end; lnum++) { - len += strlen(ml_get_buf(buf, lnum)) + 1; + len += (size_t)ml_get_buf_len(buf, lnum) + 1; } char *ptr = try_malloc(len); if (ptr == NULL) { diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index 8b6c3d58b2..4eaea4c6be 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -850,43 +850,6 @@ static void handle_inline_virtual_text(win_T *wp, winlinevars_T *wlv, ptrdiff_t } } -static colnr_T get_trailcol(win_T *wp, const char *ptr, const char *line) -{ - colnr_T trailcol = MAXCOL; - // find start of trailing whitespace - if (wp->w_p_lcs_chars.trail) { - trailcol = (colnr_T)strlen(ptr); - while (trailcol > 0 && ascii_iswhite(ptr[trailcol - 1])) { - trailcol--; - } - trailcol += (colnr_T)(ptr - line); - } - - return trailcol; -} - -static colnr_T get_leadcol(win_T *wp, const char *ptr, const char *line) -{ - colnr_T leadcol = 0; - - // find end of leading whitespace - if (wp->w_p_lcs_chars.lead || wp->w_p_lcs_chars.leadmultispace != NULL) { - leadcol = 0; - while (ascii_iswhite(ptr[leadcol])) { - leadcol++; - } - if (ptr[leadcol] == NUL) { - // in a line full of spaces all of them are treated as trailing - leadcol = 0; - } else { - // keep track of the first column not filled with spaces - leadcol += (colnr_T)(ptr - line + 1); - } - } - - return leadcol; -} - /// Start a screen line at column zero. static void win_line_start(win_T *wp, winlinevars_T *wlv) { @@ -1298,17 +1261,17 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s nextlinecol = MAXCOL; nextline_idx = 0; } else { - const size_t line_len = strlen(line); + const colnr_T line_len = ml_get_buf_len(wp->w_buffer, lnum); if (line_len < SPWORDLEN) { // Short line, use it completely and append the start of the // next line. nextlinecol = 0; - memmove(nextline, line, line_len); + memmove(nextline, line, (size_t)line_len); STRMOVE(nextline + line_len, nextline + SPWORDLEN); - nextline_idx = (int)line_len + 1; + nextline_idx = line_len + 1; } else { // Long line, use only the last SPWORDLEN bytes. - nextlinecol = (int)line_len - SPWORDLEN; + nextlinecol = line_len - SPWORDLEN; memmove(nextline, line + nextlinecol, SPWORDLEN); nextline_idx = SPWORDLEN + 1; } @@ -1336,8 +1299,28 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s || wp->w_p_lcs_chars.nbsp) { extra_check = true; } - trailcol = get_trailcol(wp, ptr, line); - leadcol = get_leadcol(wp, ptr, line); + // find start of trailing whitespace + if (wp->w_p_lcs_chars.trail) { + trailcol = ml_get_buf_len(wp->w_buffer, lnum); + while (trailcol > 0 && ascii_iswhite(ptr[trailcol - 1])) { + trailcol--; + } + trailcol += (colnr_T)(ptr - line); + } + // find end of leading whitespace + if (wp->w_p_lcs_chars.lead || wp->w_p_lcs_chars.leadmultispace != NULL) { + leadcol = 0; + while (ascii_iswhite(ptr[leadcol])) { + leadcol++; + } + if (ptr[leadcol] == NUL) { + // in a line full of spaces all of them are treated as trailing + leadcol = 0; + } else { + // keep track of the first column not filled with spaces + leadcol += (colnr_T)(ptr - line + 1); + } + } } // 'nowrap' or 'wrap' and a single line that doesn't fit: Advance to the diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 5b62ab4215..df0c075306 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -2968,7 +2968,7 @@ static void replace_do_bs(int limit_col) } del_char_after_col(limit_col); if (l_State & VREPLACE_FLAG) { - orig_len = (int)strlen(get_cursor_pos_ptr()); + orig_len = get_cursor_pos_len(); } replace_push(cc); replace_pop_ins(); @@ -2976,7 +2976,7 @@ static void replace_do_bs(int limit_col) if (l_State & VREPLACE_FLAG) { // Get the number of screen cells used by the inserted characters char *p = get_cursor_pos_ptr(); - int ins_len = (int)strlen(p) - orig_len; + int ins_len = get_cursor_pos_len() - orig_len; int vcol = start_vcol; for (int i = 0; i < ins_len; i++) { vcol += win_chartabsize(curwin, p + i, vcol); @@ -3760,7 +3760,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) return false; } Insstart.lnum--; - Insstart.col = (colnr_T)strlen(ml_get(Insstart.lnum)); + Insstart.col = ml_get_len(Insstart.lnum); } // In replace mode: // cc < 0: NL was inserted, delete it @@ -4480,7 +4480,7 @@ bool ins_eol(int c) // NL in reverse insert will always start in the end of current line. if (revins_on) { - curwin->w_cursor.col += (colnr_T)strlen(get_cursor_pos_ptr()); + curwin->w_cursor.col += get_cursor_pos_len(); } AppendToRedobuff(NL_STR); diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 9f48312ec6..7c49189602 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -541,7 +541,7 @@ void ex_sort(exarg_T *eap) // Also get the longest line length for allocating "sortbuf". for (linenr_T lnum = eap->line1; lnum <= eap->line2; lnum++) { char *s = ml_get(lnum); - int len = (int)strlen(s); + int len = ml_get_len(lnum); if (maxlen < len) { maxlen = len; } @@ -643,8 +643,8 @@ void ex_sort(exarg_T *eap) } char *s = ml_get(get_lnum); - size_t bytelen = strlen(s) + 1; // include EOL in bytelen - old_count += (bcount_t)bytelen; + colnr_T bytelen = ml_get_len(get_lnum) + 1; // include EOL in bytelen + old_count += bytelen; if (!unique || i == 0 || string_compare(s, sortbuf1) != 0) { // Copy the line into a buffer, it may become invalid in // ml_append(). And it's needed for "unique". @@ -652,7 +652,7 @@ void ex_sort(exarg_T *eap) if (ml_append(lnum++, sortbuf1, 0, false) == FAIL) { break; } - new_count += (bcount_t)bytelen; + new_count += bytelen; } fast_breakcheck(); if (got_int) { @@ -740,7 +740,7 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest) return FAIL; } for (extra = 0, l = line1; l <= line2; l++) { - char *str = xstrdup(ml_get(l + extra)); + char *str = xstrnsave(ml_get(l + extra), (size_t)ml_get_len(l + extra)); ml_append(dest + l - line1, str, 0, false); xfree(str); if (dest < line1) { @@ -876,9 +876,8 @@ void ex_copy(linenr_T line1, linenr_T line2, linenr_T n) curwin->w_cursor.lnum = n; while (line1 <= line2) { - // need to use xstrdup() because the line will be unlocked within - // ml_append() - char *p = xstrdup(ml_get(line1)); + // need to make a copy because the line will be unlocked within ml_append() + char *p = xstrnsave(ml_get(line1), (size_t)ml_get_len(line1)); ml_append(curwin->w_cursor.lnum, p, 0, false); xfree(p); @@ -3300,7 +3299,8 @@ static int do_sub(exarg_T *eap, const proftime_T timeout, const int cmdpreview_n if (nmatch > 1) { \ sub_firstlnum += (linenr_T)nmatch - 1; \ xfree(sub_firstline); \ - sub_firstline = xstrdup(ml_get(sub_firstlnum)); \ + sub_firstline = xstrnsave(ml_get(sub_firstlnum), \ + (size_t)ml_get_len(sub_firstlnum)); \ /* When going beyond the last line, stop substituting. */ \ if (sub_firstlnum <= line2) { \ do_again = true; \ @@ -3628,7 +3628,8 @@ static int do_sub(exarg_T *eap, const proftime_T timeout, const int cmdpreview_n break; } if (sub_firstline == NULL) { - sub_firstline = xstrdup(ml_get(sub_firstlnum)); + sub_firstline = xstrnsave(ml_get(sub_firstlnum), + (size_t)ml_get_len(sub_firstlnum)); } // Save the line number of the last change for the final @@ -3765,7 +3766,7 @@ static int do_sub(exarg_T *eap, const proftime_T timeout, const int cmdpreview_n // really update the line, it would change // what matches. Temporarily replace the line // and change it back afterwards. - orig_line = xstrdup(ml_get(lnum)); + orig_line = xstrnsave(ml_get(lnum), (size_t)ml_get_len(lnum)); char *new_line = concat_str(new_start, sub_firstline + copycol); // Position the cursor relative to the end of the line, the @@ -4626,8 +4627,8 @@ static int show_sub(exarg_T *eap, pos_T old_cusr, PreviewLines *preview_lines, i } char *str = NULL; // construct the line to show in here - size_t old_line_size = 0; - size_t line_size = 0; + colnr_T old_line_size = 0; + colnr_T line_size = 0; linenr_T linenr_preview = 0; // last line added to preview buffer linenr_T linenr_origbuf = 0; // last line added to original buffer linenr_T next_linenr = 0; // next line to show for the match @@ -4666,21 +4667,21 @@ static int show_sub(exarg_T *eap, pos_T old_cusr, PreviewLines *preview_lines, i line = ""; } else { line = ml_get_buf(orig_buf, next_linenr); - line_size = strlen(line) + (size_t)col_width + 1; + line_size = ml_get_buf_len(orig_buf, next_linenr) + col_width + 1; // Reallocate if line not long enough if (line_size > old_line_size) { - str = xrealloc(str, line_size * sizeof(char)); + str = xrealloc(str, (size_t)line_size * sizeof(char)); old_line_size = line_size; } } // Put "|lnum| line" into `str` and append it to the preview buffer. - snprintf(str, line_size, "|%*" PRIdLINENR "| %s", col_width - 3, + snprintf(str, (size_t)line_size, "|%*" PRIdLINENR "| %s", col_width - 3, next_linenr, line); if (linenr_preview == 0) { ml_replace_buf(cmdpreview_buf, 1, str, true, false); } else { - ml_append_buf(cmdpreview_buf, linenr_preview, str, (colnr_T)line_size, false); + ml_append_buf(cmdpreview_buf, linenr_preview, str, line_size, false); } linenr_preview += 1; } diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 4150d0997d..f032604a59 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -957,7 +957,7 @@ retry: int tlen = 0; while (true) { p = (uint8_t *)ml_get(read_buf_lnum) + read_buf_col; - int n = (int)strlen((char *)p); + int n = ml_get_len(read_buf_lnum) - read_buf_col; if (tlen + n + 1 > size) { // Filled up to "size", append partial line. // Change NL to NUL to reverse the effect done diff --git a/src/nvim/fold.c b/src/nvim/fold.c index 15aba432c4..e70a05ed9a 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -1017,8 +1017,7 @@ void foldAdjustVisual(void) return; } - char *ptr = ml_get(end->lnum); - end->col = (colnr_T)strlen(ptr); + end->col = ml_get_len(end->lnum); if (end->col > 0 && *p_sel == 'o') { end->col--; } @@ -1605,7 +1604,7 @@ static void foldAddMarker(buf_T *buf, pos_T pos, const char *marker, size_t mark // Allocate a new line: old-line + 'cms'-start + marker + 'cms'-end char *line = ml_get_buf(buf, lnum); - size_t line_len = strlen(line); + size_t line_len = (size_t)ml_get_buf_len(buf, lnum); size_t added = 0; if (u_save(lnum - 1, lnum + 1) != OK) { @@ -1686,7 +1685,7 @@ static void foldDelMarker(buf_T *buf, linenr_T lnum, char *marker, size_t marker } if (u_save(lnum - 1, lnum + 1) == OK) { // Make new line: text-before-marker + text-after-marker - char *newline = xmalloc(strlen(line) - len + 1); + char *newline = xmalloc((size_t)ml_get_buf_len(buf, lnum) - len + 1); assert(p >= line); memcpy(newline, line, (size_t)(p - line)); STRCPY(newline + (p - line), p + len); diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index a1f341f404..526a17cfd4 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -2937,7 +2937,7 @@ static int process_next_cpt_value(ins_compl_next_state_T *st, int *compl_type_ar // buffer, so that word at start of buffer is found // correctly. st->first_match_pos.lnum = st->ins_buf->b_ml.ml_line_count; - st->first_match_pos.col = (colnr_T)strlen(ml_get(st->first_match_pos.lnum)); + st->first_match_pos.col = ml_get_len(st->first_match_pos.lnum); } st->last_match_pos = st->first_match_pos; compl_type = 0; diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 0ecdd88ebd..1273093d8b 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -1715,7 +1715,7 @@ void mark_mb_adjustpos(buf_T *buf, pos_T *lp) { if (lp->col > 0 || lp->coladd > 1) { const char *const p = ml_get_buf(buf, lp->lnum); - if (*p == NUL || (int)strlen(p) < lp->col) { + if (*p == NUL || ml_get_buf_len(buf, lp->lnum) < lp->col) { lp->col = 0; } else { lp->col -= utf_head_off(p, p + lp->col); diff --git a/src/nvim/memline.c b/src/nvim/memline.c index ca47f6aa98..9fa6261987 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -1840,6 +1840,12 @@ colnr_T ml_get_len(linenr_T lnum) return ml_get_buf_len(curbuf, lnum); } +/// @return length (excluding the NUL) of the text after position "pos". +colnr_T ml_get_pos_len(pos_T *pos) +{ + return ml_get_buf_len(curbuf, curwin->w_cursor.lnum) - pos->col; +} + /// @return length (excluding the NUL) of the given line in the given buffer. colnr_T ml_get_buf_len(buf_T *buf, linenr_T lnum) { @@ -4133,7 +4139,7 @@ int dec(pos_T *lp) if (lp->col == MAXCOL) { // past end of line char *p = ml_get(lp->lnum); - lp->col = (colnr_T)strlen(p); + lp->col = ml_get_len(lp->lnum); lp->col -= utf_head_off(p, p + lp->col); return 0; } @@ -4149,7 +4155,7 @@ int dec(pos_T *lp) // there is a prior line lp->lnum--; char *p = ml_get(lp->lnum); - lp->col = (colnr_T)strlen(p); + lp->col = ml_get_len(lp->lnum); lp->col -= utf_head_off(p, p + lp->col); return 1; } diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 3dcbe597fe..81b10f30a9 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -266,7 +266,7 @@ void op_shift(oparg_T *oap, bool curs_top, int amount) // Set "'[" and "']" marks. curbuf->b_op_start = oap->start; curbuf->b_op_end.lnum = oap->end.lnum; - curbuf->b_op_end.col = (colnr_T)strlen(ml_get(oap->end.lnum)); + curbuf->b_op_end.col = ml_get_len(oap->end.lnum); if (curbuf->b_op_end.col > 0) { curbuf->b_op_end.col--; } @@ -564,8 +564,8 @@ static void block_insert(oparg_T *oap, char *s, bool b_insert, struct block_def assert(count >= 0); // Make sure the allocated size matches what is actually copied below. - newp = xmalloc(strlen(oldp) + (size_t)spaces + s_len - + (spaces > 0 && !bdp->is_short ? (size_t)ts_val - (size_t)spaces : 0) + newp = xmalloc((size_t)ml_get_len(lnum) + (size_t)spaces + s_len + + (spaces > 0 && !bdp->is_short ? (size_t)(ts_val - spaces) : 0) + (size_t)count + 1); // copy up to shifted part @@ -1572,7 +1572,7 @@ int op_delete(oparg_T *oap) // Thus the number of characters may increase! int n = bd.textlen - bd.startspaces - bd.endspaces; char *oldp = ml_get(lnum); - char *newp = xmalloc(strlen(oldp) - (size_t)n + 1); + char *newp = xmalloc((size_t)ml_get_len(lnum) - (size_t)n + 1); // copy up to deleted part memmove(newp, oldp, (size_t)bd.textcol); // insert spaces @@ -1681,8 +1681,7 @@ int op_delete(oparg_T *oap) if (virtual_op) { // fix up things for virtualedit-delete: // break the tabs which are going to get in our way - char *curline = get_cursor_line_ptr(); - int len = (int)strlen(curline); + int len = get_cursor_line_len(); if (oap->end.coladd != 0 && (int)oap->end.col >= len - 1 @@ -1875,7 +1874,7 @@ static int op_replace(oparg_T *oap, int c) numc *= utf_char2len(c); char *oldp = get_cursor_line_ptr(); - colnr_T oldlen = (int)strlen(oldp); + colnr_T oldlen = get_cursor_line_len(); size_t newp_size = (size_t)bd.textcol + (size_t)bd.startspaces; if (had_ctrl_v_cr || (c != '\r' && c != '\n')) { @@ -1939,7 +1938,7 @@ static int op_replace(oparg_T *oap, int c) if (oap->motion_type == kMTLineWise) { oap->start.col = 0; curwin->w_cursor.col = 0; - oap->end.col = (colnr_T)strlen(ml_get(oap->end.lnum)); + oap->end.col = ml_get_len(oap->end.lnum); if (oap->end.col) { oap->end.col--; } @@ -2058,7 +2057,7 @@ void op_tilde(oparg_T *oap) if (oap->motion_type == kMTLineWise) { oap->start.col = 0; pos.col = 0; - oap->end.col = (colnr_T)strlen(ml_get(oap->end.lnum)); + oap->end.col = ml_get_len(oap->end.lnum); if (oap->end.col) { oap->end.col--; } @@ -2073,7 +2072,7 @@ void op_tilde(oparg_T *oap) while (true) { did_change |= swapchars(oap->op_type, &pos, pos.lnum == oap->end.lnum ? oap->end.col + 1 - : (int)strlen(ml_get_pos(&pos))); + : ml_get_pos_len(&pos)); if (ltoreq(oap->end, pos) || inc(&pos) == -1) { break; } @@ -2232,12 +2231,11 @@ void op_insert(oparg_T *oap, int count1) // Get indent information ind_pre_col = (colnr_T)getwhitecols_curline(); ind_pre_vcol = get_indent(); - char *firstline = ml_get(oap->start.lnum) + bd.textcol; + pre_textlen = ml_get_len(oap->start.lnum) - bd.textcol; if (oap->op_type == OP_APPEND) { - firstline += bd.textlen; + pre_textlen -= bd.textlen; } - pre_textlen = (int)strlen(firstline); } if (oap->op_type == OP_APPEND) { @@ -2364,7 +2362,7 @@ void op_insert(oparg_T *oap, int count1) // Subsequent calls to ml_get() flush the firstline data - take a // copy of the required string. char *firstline = ml_get(oap->start.lnum); - const size_t len = strlen(firstline); + colnr_T len = ml_get_len(oap->start.lnum); colnr_T add = bd.textcol; colnr_T offset = 0; // offset when cursor was moved in insert mode if (oap->op_type == OP_APPEND) { @@ -2381,12 +2379,12 @@ void op_insert(oparg_T *oap, int count1) } } } - if ((size_t)add > len) { - firstline += len; // short line, point to the NUL - } else { - firstline += add; + if (add > len) { + add = len; // short line, point to the NUL } - int ins_len = (int)strlen(firstline) - pre_textlen - offset; + firstline += add; + len -= add; + int ins_len = len - pre_textlen - offset; if (pre_textlen >= 0 && ins_len > 0) { char *ins_text = xmemdupz(firstline, (size_t)ins_len); // block handled here @@ -2441,7 +2439,7 @@ int op_change(oparg_T *oap) coladvance_force(getviscol()); } firstline = ml_get(oap->start.lnum); - pre_textlen = (int)strlen(firstline); + pre_textlen = ml_get_len(oap->start.lnum); pre_indent = (int)getwhitecols(firstline); bd.textcol = curwin->w_cursor.col; } @@ -2474,7 +2472,7 @@ int op_change(oparg_T *oap) bd.textcol += (colnr_T)(new_indent - pre_indent); } - ins_len = (int)strlen(firstline) - pre_textlen; + ins_len = ml_get_len(oap->start.lnum) - pre_textlen; if (ins_len > 0) { // Subsequent calls to ml_get() flush the firstline data - take a // copy of the inserted text. @@ -2495,8 +2493,8 @@ int op_change(oparg_T *oap) vpos.coladd = 0; } char *oldp = ml_get(linenr); - char *newp = xmalloc(strlen(oldp) + (size_t)vpos.coladd - + (size_t)ins_len + 1); + char *newp = xmalloc((size_t)ml_get_len(linenr) + + (size_t)vpos.coladd + (size_t)ins_len + 1); // copy up to block start memmove(newp, oldp, (size_t)bd.textcol); int offset = bd.textcol; @@ -3173,6 +3171,7 @@ void do_put(int regname, yankreg_T *reg, int dir, int count, int flags) } // get the old line and advance to the position to insert at char *oldp = get_cursor_line_ptr(); + colnr_T oldlen = get_cursor_line_len(); CharsizeArg csarg; CSType cstype = init_charsize_arg(&csarg, curwin, curwin->w_cursor.lnum, oldp); @@ -3183,7 +3182,6 @@ void do_put(int regname, yankreg_T *reg, int dir, int count, int flags) vcol += incr; ci = utfc_next(ci); } - size_t oldlen = (size_t)(ci.ptr - oldp) + strlen(ci.ptr); char *ptr = ci.ptr; bd.textcol = (colnr_T)(ptr - oldp); @@ -3233,7 +3231,7 @@ void do_put(int regname, yankreg_T *reg, int dir, int count, int flags) totlen = (size_t)count * (size_t)(yanklen + spaces) + (size_t)bd.startspaces + (size_t)bd.endspaces; - char *newp = xmalloc(totlen + oldlen + 1); + char *newp = xmalloc(totlen + (size_t)oldlen + 1); // copy part up to cursor to new line ptr = newp; @@ -3263,7 +3261,7 @@ void do_put(int regname, yankreg_T *reg, int dir, int count, int flags) ptr += bd.endspaces; // move the text after the cursor to the end of the line. - int columns = (int)oldlen - bd.textcol - delcount + 1; + int columns = oldlen - bd.textcol - delcount + 1; assert(columns >= 0); memmove(ptr, oldp + bd.textcol + delcount, (size_t)columns); ml_replace(curwin->w_cursor.lnum, newp, false); @@ -3295,7 +3293,7 @@ void do_put(int regname, yankreg_T *reg, int dir, int count, int flags) curwin->w_cursor.col++; // in Insert mode we might be after the NUL, correct for that - colnr_T len = (colnr_T)strlen(get_cursor_line_ptr()); + colnr_T len = get_cursor_line_len(); if (curwin->w_cursor.col > len) { curwin->w_cursor.col = len; } @@ -3359,7 +3357,7 @@ void do_put(int regname, yankreg_T *reg, int dir, int count, int flags) totlen = (size_t)count * (size_t)yanklen; do { char *oldp = ml_get(lnum); - size_t oldlen = strlen(oldp); + colnr_T oldlen = ml_get_len(lnum); if (lnum > start_lnum) { pos_T pos = { .lnum = lnum, @@ -3370,11 +3368,11 @@ void do_put(int regname, yankreg_T *reg, int dir, int count, int flags) col = MAXCOL; } } - if (VIsual_active && col > (colnr_T)oldlen) { + if (VIsual_active && col > oldlen) { lnum++; continue; } - char *newp = xmalloc(totlen + oldlen + 1); + char *newp = xmalloc(totlen + (size_t)oldlen + 1); memmove(newp, oldp, (size_t)col); char *ptr = newp + col; for (size_t i = 0; i < (size_t)count; i++) { @@ -3431,7 +3429,7 @@ void do_put(int regname, yankreg_T *reg, int dir, int count, int flags) lnum = new_cursor.lnum; char *ptr = ml_get(lnum) + col; totlen = strlen(y_array[y_size - 1]); - char *newp = xmalloc((size_t)(strlen(ptr) + totlen + 1)); + char *newp = xmalloc((size_t)ml_get_len(lnum) - (size_t)col + totlen + 1); STRCPY(newp, y_array[y_size - 1]); STRCAT(newp, ptr); // insert second line @@ -3465,7 +3463,7 @@ void do_put(int regname, yankreg_T *reg, int dir, int count, int flags) curwin->w_cursor.lnum = lnum; char *ptr = ml_get(lnum); if (cnt == count && i == y_size - 1) { - lendiff = (int)strlen(ptr); + lendiff = ml_get_len(lnum); } if (*ptr == '#' && preprocs_left()) { indent = 0; // Leave # lines at start @@ -3482,7 +3480,7 @@ void do_put(int regname, yankreg_T *reg, int dir, int count, int flags) curwin->w_cursor = old_pos; // remember how many chars were removed if (cnt == count && i == y_size - 1) { - lendiff -= (int)strlen(ml_get(lnum)); + lendiff -= ml_get_len(lnum); } } } @@ -3588,7 +3586,7 @@ error: curwin->w_set_curswant = true; // Make sure the cursor is not after the NUL. - int len = (int)strlen(get_cursor_line_ptr()); + int len = get_cursor_line_len(); if (curwin->w_cursor.col > len) { if (cur_ve_flags == VE_ALL) { curwin->w_cursor.coladd = curwin->w_cursor.col - len; @@ -4301,7 +4299,7 @@ void charwise_block_prep(pos_T start, pos_T end, struct block_def *bdp, linenr_T } } if (endcol == MAXCOL) { - endcol = (colnr_T)strlen(p); + endcol = ml_get_len(lnum); } if (startcol > endcol || is_oneChar) { bdp->textlen = 0; @@ -4358,20 +4356,20 @@ void op_addsub(oparg_T *oap, linenr_T Prenum1, bool g_cmd) } else if (oap->motion_type == kMTLineWise) { curwin->w_cursor.col = 0; pos.col = 0; - length = (colnr_T)strlen(ml_get(pos.lnum)); + length = ml_get_len(pos.lnum); } else { // oap->motion_type == kMTCharWise if (pos.lnum == oap->start.lnum && !oap->inclusive) { dec(&(oap->end)); } - length = (colnr_T)strlen(ml_get(pos.lnum)); + length = ml_get_len(pos.lnum); pos.col = 0; if (pos.lnum == oap->start.lnum) { pos.col += oap->start.col; length -= oap->start.col; } if (pos.lnum == oap->end.lnum) { - length = (int)strlen(ml_get(oap->end.lnum)); + length = ml_get_len(oap->end.lnum); if (oap->end.col >= length) { oap->end.col = length - 1; } @@ -5443,7 +5441,7 @@ void cursor_pos_info(dict_T *dict) validate_virtcol(curwin); col_print(buf1, sizeof(buf1), (int)curwin->w_cursor.col + 1, (int)curwin->w_virtcol + 1); - col_print(buf2, sizeof(buf2), (int)strlen(p), linetabsize_str(p)); + col_print(buf2, sizeof(buf2), get_cursor_line_len(), linetabsize_str(p)); if (char_count_cursor == byte_count_cursor && char_count == byte_count) { @@ -5857,11 +5855,10 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) && cap->oap->op_type != OP_DELETE) { if (lt(VIsual, curwin->w_cursor)) { VIsual.col = 0; - curwin->w_cursor.col = - (colnr_T)strlen(ml_get(curwin->w_cursor.lnum)); + curwin->w_cursor.col = ml_get_len(curwin->w_cursor.lnum); } else { curwin->w_cursor.col = 0; - VIsual.col = (colnr_T)strlen(ml_get(VIsual.lnum)); + VIsual.col = ml_get_len(VIsual.lnum); } VIsual_mode = 'v'; } else if (VIsual_mode == 'v') { @@ -5890,7 +5887,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) || oap->motion_type == kMTLineWise) && hasFolding(curwin, curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum)) { - curwin->w_cursor.col = (colnr_T)strlen(get_cursor_line_ptr()); + curwin->w_cursor.col = get_cursor_line_len(); } } oap->end = curwin->w_cursor; @@ -5908,7 +5905,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) curwin->w_cursor.col = 0; } if (hasFolding(curwin, oap->start.lnum, NULL, &oap->start.lnum)) { - oap->start.col = (colnr_T)strlen(ml_get(oap->start.lnum)); + oap->start.col = ml_get_len(oap->start.lnum); } } oap->end = oap->start; @@ -6092,7 +6089,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) if (inindent(0)) { oap->motion_type = kMTLineWise; } else { - oap->end.col = (colnr_T)strlen(ml_get(oap->end.lnum)); + oap->end.col = ml_get_len(oap->end.lnum); if (oap->end.col) { oap->end.col--; oap->inclusive = true; @@ -6866,14 +6863,13 @@ bcount_t get_region_bytecount(buf_T *buf, linenr_T start_lnum, linenr_T end_lnum if (start_lnum == end_lnum) { return end_col - start_col; } - const char *first = ml_get_buf(buf, start_lnum); - bcount_t deleted_bytes = (bcount_t)strlen(first) - start_col + 1; + bcount_t deleted_bytes = ml_get_buf_len(buf, start_lnum) - start_col + 1; for (linenr_T i = 1; i <= end_lnum - start_lnum - 1; i++) { if (start_lnum + i > max_lnum) { return deleted_bytes; } - deleted_bytes += (bcount_t)strlen(ml_get_buf(buf, start_lnum + i)) + 1; + deleted_bytes += ml_get_buf_len(buf, start_lnum + i) + 1; } if (end_lnum > max_lnum) { return deleted_bytes; diff --git a/src/nvim/plines.c b/src/nvim/plines.c index ec13e5b5b7..44b06a0403 100644 --- a/src/nvim/plines.c +++ b/src/nvim/plines.c @@ -593,7 +593,7 @@ void getvvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *e // Cannot put the cursor on part of a wide character. char *ptr = ml_get_buf(wp->w_buffer, pos->lnum); - if (pos->col < (colnr_T)strlen(ptr)) { + if (pos->col < ml_get_buf_len(wp->w_buffer, pos->lnum)) { int c = utf_ptr2char(ptr + pos->col); if ((c != TAB) && vim_isprintc(c)) { endadd = (colnr_T)(char2cells(c) - 1); diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index bf53dca167..a2029a6ae5 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -773,9 +773,9 @@ static int qf_get_next_buf_line(qfstate_T *state) return QF_END_OF_INPUT; } char *p_buf = ml_get_buf(state->buf, state->buflnum); + size_t len = (size_t)ml_get_buf_len(state->buf, state->buflnum); state->buflnum += 1; - size_t len = strlen(p_buf); if (len > IOSIZE - 2) { state->linebuf = qf_grow_linebuf(state, len); } else { @@ -5356,12 +5356,13 @@ static bool vgr_match_buflines(qf_list_T *qfl, char *fname, buf_T *buf, char *sp break; } col = regmatch->endpos[0].col + (col == regmatch->endpos[0].col); - if (col > (colnr_T)strlen(ml_get_buf(buf, lnum))) { + if (col > ml_get_buf_len(buf, lnum)) { break; } } } else { char *const str = ml_get_buf(buf, lnum); + const int line_len = ml_get_buf_len(buf, lnum); int score; uint32_t matches[MAX_FUZZY_MATCHES]; const size_t sz = sizeof(matches) / sizeof(matches[0]); @@ -5400,7 +5401,7 @@ static bool vgr_match_buflines(qf_list_T *qfl, char *fname, buf_T *buf, char *sp break; } col = (colnr_T)matches[pat_len - 1] + col + 1; - if (col > (colnr_T)strlen(str)) { + if (col > line_len) { break; } } diff --git a/src/nvim/search.c b/src/nvim/search.c index a3c9da8e3b..1720274e61 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -604,7 +604,7 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, && pos->col < MAXCOL - 2) { // Watch out for the "col" being MAXCOL - 2, used in a closed fold. ptr = ml_get_buf(buf, pos->lnum); - if ((int)strlen(ptr) <= pos->col) { + if (ml_get_buf_len(buf, pos->lnum) <= pos->col) { start_char_len = 1; } else { start_char_len = utfc_ptr2len(ptr + pos->col); @@ -851,7 +851,7 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, if (endpos.col == 0) { if (pos->lnum > 1) { // just in case pos->lnum--; - pos->col = (colnr_T)strlen(ml_get_buf(buf, pos->lnum)); + pos->col = ml_get_buf_len(buf, pos->lnum); } } else { pos->col--; @@ -969,7 +969,7 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, // A pattern like "\n\zs" may go past the last line. if (pos->lnum > buf->b_ml.ml_line_count) { pos->lnum = buf->b_ml.ml_line_count; - pos->col = (int)strlen(ml_get_buf(buf, pos->lnum)); + pos->col = ml_get_buf_len(buf, pos->lnum); if (pos->col > 0) { pos->col--; } @@ -1554,7 +1554,7 @@ int searchc(cmdarg_T *cap, bool t_cmd) char *p = get_cursor_line_ptr(); int col = curwin->w_cursor.col; - int len = (int)strlen(p); + int len = get_cursor_line_len(); while (count--) { while (true) { @@ -1958,7 +1958,7 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) } linep = ml_get(pos.lnum); - pos.col = (colnr_T)strlen(linep); // pos.col on trailing NUL + pos.col = ml_get_len(pos.lnum); // pos.col on trailing NUL do_quotes = -1; line_breakcheck(); @@ -2105,7 +2105,7 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) } if (pos.lnum > 1) { ptr = ml_get(pos.lnum - 1); - if (*ptr && *(ptr + strlen(ptr) - 1) == '\\') { + if (*ptr && *(ptr + ml_get_len(pos.lnum - 1) - 1) == '\\') { do_quotes = 1; if (start_in_quotes == kNone) { inquote = at_start; @@ -2472,7 +2472,7 @@ int current_search(int count, bool forward) } else { // try again from end of buffer // searching backwards, so set pos to last line and col pos.lnum = curwin->w_buffer->b_ml.ml_line_count; - pos.col = (colnr_T)strlen(ml_get(curwin->w_buffer->b_ml.ml_line_count)); + pos.col = ml_get_len(curwin->w_buffer->b_ml.ml_line_count); } } } diff --git a/src/nvim/spell.c b/src/nvim/spell.c index c2091c6bae..04c4f47a60 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -1342,7 +1342,7 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att while (!got_int) { char *line = ml_get_buf(wp->w_buffer, lnum); - len = strlen(line); + len = (size_t)ml_get_buf_len(wp->w_buffer, lnum); if (buflen < len + MAXWLEN + 2) { xfree(buf); buflen = len + MAXWLEN + 2; @@ -2682,7 +2682,7 @@ void ex_spellrepall(exarg_T *eap) char *line = get_cursor_line_ptr(); if (addlen <= 0 || strncmp(line + curwin->w_cursor.col, repl_to, repl_to_len) != 0) { - char *p = xmalloc(strlen(line) + (size_t)addlen + 1); + char *p = xmalloc((size_t)get_cursor_line_len() + (size_t)addlen + 1); memmove(p, line, (size_t)curwin->w_cursor.col); STRCPY(p + curwin->w_cursor.col, repl_to); STRCAT(p, line + curwin->w_cursor.col + repl_from_len); diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index 1c632d2700..45e1b37818 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -5114,13 +5114,12 @@ static void sug_write(spellinfo_T *spin, char *fname) for (linenr_T lnum = 1; lnum <= wcount; lnum++) { // : ... NUL char *line = ml_get_buf(spin->si_spellbuf, lnum); - size_t len = strlen(line) + 1; - if (fwrite(line, len, 1, fd) == 0) { + int len = ml_get_buf_len(spin->si_spellbuf, lnum) + 1; + if (fwrite(line, (size_t)len, 1, fd) == 0) { emsg(_(e_write)); goto theend; } - assert((size_t)spin->si_memtot + len <= INT_MAX); - spin->si_memtot += (int)len; + spin->si_memtot += len; } // Write another byte to check for errors. diff --git a/src/nvim/spellsuggest.c b/src/nvim/spellsuggest.c index 5c0e295f88..0cbd3d9795 100644 --- a/src/nvim/spellsuggest.c +++ b/src/nvim/spellsuggest.c @@ -480,9 +480,8 @@ void spell_suggest(int count) badlen++; end_visual_mode(); // make sure we don't include the NUL at the end of the line - char *line = get_cursor_line_ptr(); - if (badlen > (int)strlen(line) - (int)curwin->w_cursor.col) { - badlen = (int)strlen(line) - (int)curwin->w_cursor.col; + if (badlen > get_cursor_line_len() - curwin->w_cursor.col) { + badlen = get_cursor_line_len() - curwin->w_cursor.col; } // Find the start of the badly spelled word. } else if (spell_move_to(curwin, FORWARD, true, true, NULL) == 0 @@ -514,7 +513,7 @@ void spell_suggest(int count) int need_cap = check_need_cap(curwin, curwin->w_cursor.lnum, curwin->w_cursor.col); // Make a copy of current line since autocommands may free the line. - char *line = xstrdup(get_cursor_line_ptr()); + char *line = xstrnsave(get_cursor_line_ptr(), (size_t)get_cursor_line_len()); spell_suggest_timeout = 5000; // Get the list of suggestions. Limit to 'lines' - 2 or the number in diff --git a/src/nvim/statusline.c b/src/nvim/statusline.c index b403f840ca..1d914dd105 100644 --- a/src/nvim/statusline.c +++ b/src/nvim/statusline.c @@ -1002,11 +1002,11 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, OptIndex op // Get the byte value now, in case we need it below. This is more // efficient than making a copy of the line. int byteval; - const size_t len = strlen(line_ptr); - if (wp->w_cursor.col > (colnr_T)len) { + const colnr_T len = ml_get_buf_len(wp->w_buffer, lnum); + if (wp->w_cursor.col > len) { // Line may have changed since checking the cursor column, or the lnum // was adjusted above. - wp->w_cursor.col = (colnr_T)len; + wp->w_cursor.col = len; wp->w_cursor.coladd = 0; byteval = 0; } else { diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index ab8ab62b90..76824879d7 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -549,8 +549,8 @@ static void syn_sync(win_T *wp, linenr_T start_lnum, synstate_T *last_valid) // Skip lines that end in a backslash. for (; start_lnum > 1; start_lnum--) { - char *line = ml_get(start_lnum - 1); - if (*line == NUL || *(line + strlen(line) - 1) != '\\') { + char *l = ml_get(start_lnum - 1); + if (*l == NUL || *(l + ml_get_len(start_lnum - 1) - 1) != '\\') { break; } } @@ -2352,7 +2352,6 @@ static void find_endpos(int idx, lpos_T *startpos, lpos_T *m_endpos, lpos_T *hl_ regmmatch_T regmatch; regmmatch_T best_regmatch; // startpos/endpos of best match lpos_T pos; - char *line; bool had_match = false; char buf_chartab[32]; // chartab array for syn option iskeyword @@ -2457,8 +2456,7 @@ static void find_endpos(int idx, lpos_T *startpos, lpos_T *m_endpos, lpos_T *hl_ break; } - line = ml_get_buf(syn_buf, startpos->lnum); - int line_len = (int)strlen(line); + int line_len = ml_get_buf_len(syn_buf, startpos->lnum); // take care of an empty match or negative offset if (pos.col <= matchcol) { @@ -2635,7 +2633,7 @@ static void syn_add_start_off(lpos_T *result, regmmatch_T *regmatch, synpat_T *s if (result->lnum > syn_buf->b_ml.ml_line_count) { // a "\n" at the end of the pattern may take us below the last line result->lnum = syn_buf->b_ml.ml_line_count; - col = (int)strlen(ml_get_buf(syn_buf, result->lnum)); + col = ml_get_buf_len(syn_buf, result->lnum); } if (off != 0) { base = ml_get_buf(syn_buf, result->lnum); diff --git a/src/nvim/textformat.c b/src/nvim/textformat.c index 41fb543994..c427206764 100644 --- a/src/nvim/textformat.c +++ b/src/nvim/textformat.c @@ -445,7 +445,7 @@ void internal_format(int textwidth, int second_indent, int flags, bool format_on // Check if cursor is not past the NUL off the line, cindent // may have added or removed indent. curwin->w_cursor.col += startcol; - colnr_T len = (colnr_T)strlen(get_cursor_line_ptr()); + colnr_T len = get_cursor_line_len(); if (curwin->w_cursor.col > len) { curwin->w_cursor.col = len; } @@ -506,12 +506,11 @@ static int fmt_check_par(linenr_T lnum, int *leader_len, char **leader_flags, bo static bool ends_in_white(linenr_T lnum) { char *s = ml_get(lnum); - size_t l; if (*s == NUL) { return false; } - l = strlen(s) - 1; + colnr_T l = ml_get_len(lnum) - 1; return ascii_iswhite((uint8_t)s[l]); } @@ -544,7 +543,7 @@ static bool same_leader(linenr_T lnum, int leader1_len, char *leader1_flags, int return false; } if (*p == COM_START) { - int line_len = (int)strlen(ml_get(lnum)); + int line_len = ml_get_len(lnum); if (line_len <= leader1_len) { return false; } @@ -647,7 +646,7 @@ void auto_format(bool trailblank, bool prev_line) // in 'formatoptions' and there is a single character before the cursor. // Otherwise the line would be broken and when typing another non-white // next they are not joined back together. - int wasatend = (pos.col == (colnr_T)strlen(old)); + bool wasatend = (pos.col == get_cursor_line_len()); if (*old != NUL && !trailblank && wasatend) { dec_cursor(); int cc = gchar_cursor(); @@ -701,7 +700,7 @@ void auto_format(bool trailblank, bool prev_line) // formatted. if (!wasatend && has_format_option(FO_WHITE_PAR)) { char *linep = get_cursor_line_ptr(); - colnr_T len = (colnr_T)strlen(linep); + colnr_T len = get_cursor_line_len(); if (curwin->w_cursor.col == len) { char *plinep = xstrnsave(linep, (size_t)len + 2); plinep[len] = ' '; @@ -1119,7 +1118,7 @@ void format_lines(linenr_T line_count, bool avoid_fex) } first_par_line = false; // If the line is getting long, format it next time - if (strlen(get_cursor_line_ptr()) > (size_t)max_len) { + if (get_cursor_line_len() > max_len) { force_format = true; } else { force_format = false; diff --git a/src/nvim/textobject.c b/src/nvim/textobject.c index 8ac63aad16..3c81a840ea 100644 --- a/src/nvim/textobject.c +++ b/src/nvim/textobject.c @@ -218,7 +218,7 @@ bool findpar(bool *pincl, int dir, int count, int what, bool both) // Put the cursor on the last character in the last line and make the // motion inclusive. - if ((curwin->w_cursor.col = (colnr_T)strlen(line)) != 0) { + if ((curwin->w_cursor.col = ml_get_len(curr)) != 0) { curwin->w_cursor.col--; curwin->w_cursor.col -= utf_head_off(line, line + curwin->w_cursor.col); *pincl = true; -- cgit From 61b48e91b941258e6945e3eafadc777dccef5b75 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 14 Mar 2024 12:41:25 +0800 Subject: vim-patch:9.1.0177: Coverity reports dead code Problem: Coverity reports dead code. Solution: Remove the dead code. Also fix a mistake in ml_get_pos_len() and update some comments (zeertzjq). closes: vim/vim#14189 https://github.com/vim/vim/commit/8c55d60658b7ee3458dca57fc5eec90ca9bb9bf3 --- src/nvim/fold.c | 4 ++-- src/nvim/memline.c | 2 +- src/nvim/ops.c | 10 ++++------ src/nvim/quickfix.c | 4 ++-- test/old/testdir/test_normal.vim | 8 ++++++++ 5 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/nvim/fold.c b/src/nvim/fold.c index e70a05ed9a..be3295c44c 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -518,7 +518,7 @@ static bool checkCloseRec(garray_T *gap, linenr_T lnum, int level) return retval; } -// foldCreateAllowed() {{{2 +// foldManualAllowed() {{{2 /// @return true if it's allowed to manually create or delete a fold or, /// give an error message and return false if not. int foldManualAllowed(bool create) @@ -1025,7 +1025,7 @@ void foldAdjustVisual(void) mb_adjust_cursor(); } -// cursor_foldstart() {{{2 +// foldAdjustCursor() {{{2 /// Move the cursor to the first line of a closed fold. void foldAdjustCursor(win_T *wp) { diff --git a/src/nvim/memline.c b/src/nvim/memline.c index 9fa6261987..70304f6816 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -1843,7 +1843,7 @@ colnr_T ml_get_len(linenr_T lnum) /// @return length (excluding the NUL) of the text after position "pos". colnr_T ml_get_pos_len(pos_T *pos) { - return ml_get_buf_len(curbuf, curwin->w_cursor.lnum) - pos->col; + return ml_get_buf_len(curbuf, pos->lnum) - pos->col; } /// @return length (excluding the NUL) of the given line in the given buffer. diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 81b10f30a9..2decb11d25 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -1616,7 +1616,7 @@ int op_delete(oparg_T *oap) beginline(0); // cursor in column 0 } truncate_line(false); // delete the rest of the line, - // leave cursor past last char in line + // leaving cursor past last char in line if (oap->line_count > 1) { u_clearline(curbuf); // "U" command not possible after "2cc" } @@ -2232,7 +2232,6 @@ void op_insert(oparg_T *oap, int count1) ind_pre_col = (colnr_T)getwhitecols_curline(); ind_pre_vcol = get_indent(); pre_textlen = ml_get_len(oap->start.lnum) - bd.textcol; - if (oap->op_type == OP_APPEND) { pre_textlen -= bd.textlen; } @@ -4452,9 +4451,10 @@ bool do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) curwin->w_cursor = *pos; char *ptr = ml_get(pos->lnum); + int linelen = ml_get_len(pos->lnum); int col = pos->col; - if (*ptr == NUL || col + !!save_coladd >= (int)strlen(ptr)) { + if (col + !!save_coladd >= linelen) { goto theend; } @@ -4593,9 +4593,7 @@ bool do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) // get the number value (unsigned) if (visual && VIsual_mode != 'V') { - maxlen = (curbuf->b_visual.vi_curswant == MAXCOL - ? (int)strlen(ptr) - col - : length); + maxlen = curbuf->b_visual.vi_curswant == MAXCOL ? linelen - col : length; } bool overflow = false; diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index a2029a6ae5..0f639bf86a 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -5362,7 +5362,7 @@ static bool vgr_match_buflines(qf_list_T *qfl, char *fname, buf_T *buf, char *sp } } else { char *const str = ml_get_buf(buf, lnum); - const int line_len = ml_get_buf_len(buf, lnum); + const colnr_T linelen = ml_get_buf_len(buf, lnum); int score; uint32_t matches[MAX_FUZZY_MATCHES]; const size_t sz = sizeof(matches) / sizeof(matches[0]); @@ -5401,7 +5401,7 @@ static bool vgr_match_buflines(qf_list_T *qfl, char *fname, buf_T *buf, char *sp break; } col = (colnr_T)matches[pat_len - 1] + col + 1; - if (col > line_len) { + if (col > linelen) { break; } } diff --git a/test/old/testdir/test_normal.vim b/test/old/testdir/test_normal.vim index 91c058df9e..d31ae488d9 100644 --- a/test/old/testdir/test_normal.vim +++ b/test/old/testdir/test_normal.vim @@ -2370,6 +2370,14 @@ func Test_normal30_changecase() %d call assert_beeps('norm! ~') + " Test with multiple lines + call setline(1, ['AA', 'BBBB', 'CCCCCC', 'DDDDDDDD']) + norm! ggguG + call assert_equal(['aa', 'bbbb', 'cccccc', 'dddddddd'], getline(1, '$')) + norm! GgUgg + call assert_equal(['AA', 'BBBB', 'CCCCCC', 'DDDDDDDD'], getline(1, '$')) + %d + " Test for changing case across lines using 'whichwrap' call setline(1, ['aaaaaa', 'aaaaaa']) normal! gg10~ -- cgit From 12faaf40f487132b9397d9f3e59e44840985612c Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Wed, 13 Mar 2024 14:40:41 +0000 Subject: fix(treesitter): highlight injections properly `on_line_impl` doesn't highlight single lines, so using pattern indexes to offset priority doesn't work. --- runtime/lua/vim/treesitter/highlighter.lua | 26 ++++++++++---------- runtime/lua/vim/treesitter/languagetree.lua | 16 ++++++++----- test/functional/treesitter/highlight_spec.lua | 34 +++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 20 deletions(-) diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index cbab5e990e..6175977b49 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -57,6 +57,7 @@ end ---@field next_row integer ---@field iter vim.treesitter.highlighter.Iter? ---@field highlighter_query vim.treesitter.highlighter.Query +---@field level integer Injection level ---@nodoc ---@class vim.treesitter.highlighter @@ -192,12 +193,20 @@ function TSHighlighter:prepare_highlight_states(srow, erow) return end + local level = 0 + local t = tree + while t do + t = t:parent() + level = level + 1 + end + -- _highlight_states should be a list so that the highlights are added in the same order as -- for_each_tree traversal. This ensures that parents' highlight don't override children's. table.insert(self._highlight_states, { tstree = tstree, next_row = 0, iter = nil, + level = level, highlighter_query = highlighter_query, }) end) @@ -248,14 +257,10 @@ end ---@param line integer ---@param is_spell_nav boolean local function on_line_impl(self, buf, line, is_spell_nav) - -- Track the maximum pattern index encountered in each tree. For subsequent - -- trees, the subpriority passed to nvim_buf_set_extmark is offset by the - -- largest pattern index from the prior tree. This ensures that extmarks - -- from subsequent trees always appear "on top of" extmarks from previous - -- trees (e.g. injections should always appear over base highlights). - local pattern_offset = 0 - self:for_each_highlight_state(function(state) + -- Use the injection level to offset the subpriority passed to nvim_buf_set_extmark + -- so injections always appear over base highlights. + local pattern_offset = state.level * 1000 local root_node = state.tstree:root() local root_start_row, _, root_end_row, _ = root_node:range() @@ -270,14 +275,9 @@ local function on_line_impl(self, buf, line, is_spell_nav) :iter_matches(root_node, self.bufnr, line, root_end_row + 1, { all = true }) end - local max_pattern_index = 0 while line >= state.next_row do local pattern, match, metadata = state.iter() - if pattern and pattern > max_pattern_index then - max_pattern_index = pattern - end - if not match then state.next_row = root_end_row + 1 end @@ -343,8 +343,6 @@ local function on_line_impl(self, buf, line, is_spell_nav) end end end - - pattern_offset = pattern_offset + max_pattern_index end) end diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 62714d3f1b..ec933f5194 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -81,7 +81,7 @@ local TSCallbackNames = { ---List of regions this tree should manage and parse. If nil then regions are ---taken from _trees. This is mostly a short-lived cache for included_regions() ---@field private _lang string Language name ----@field private _parent_lang? string Parent language name +---@field private _parent? vim.treesitter.LanguageTree Parent LanguageTree ---@field private _source (integer|string) Buffer or string to parse ---@field private _trees table Reference to parsed tree (one for each language). ---Each key is the index of region, which is synced with _regions and _valid. @@ -106,9 +106,8 @@ LanguageTree.__index = LanguageTree ---@param source (integer|string) Buffer or text string to parse ---@param lang string Root language of this tree ---@param opts vim.treesitter.LanguageTree.new.Opts? ----@param parent_lang? string Parent language name of this tree ---@return vim.treesitter.LanguageTree parser object -function LanguageTree.new(source, lang, opts, parent_lang) +function LanguageTree.new(source, lang, opts) language.add(lang) opts = opts or {} @@ -122,7 +121,6 @@ function LanguageTree.new(source, lang, opts, parent_lang) local self = { _source = source, _lang = lang, - _parent_lang = parent_lang, _children = {}, _trees = {}, _opts = opts, @@ -505,19 +503,25 @@ function LanguageTree:add_child(lang) self:remove_child(lang) end - local child = LanguageTree.new(self._source, lang, self._opts, self:lang()) + local child = LanguageTree.new(self._source, lang, self._opts) -- Inherit recursive callbacks for nm, cb in pairs(self._callbacks_rec) do vim.list_extend(child._callbacks_rec[nm], cb) end + child._parent = self self._children[lang] = child self:_do_callback('child_added', self._children[lang]) return self._children[lang] end +--- @package +function LanguageTree:parent() + return self._parent +end + --- Removes a child language from this |LanguageTree|. --- ---@private @@ -792,7 +796,7 @@ function LanguageTree:_get_injection(match, metadata) local combined = metadata['injection.combined'] ~= nil local injection_lang = metadata['injection.language'] --[[@as string?]] local lang = metadata['injection.self'] ~= nil and self:lang() - or metadata['injection.parent'] ~= nil and self._parent_lang + or metadata['injection.parent'] ~= nil and self._parent or (injection_lang and resolve_lang(injection_lang)) local include_children = metadata['injection.include-children'] ~= nil diff --git a/test/functional/treesitter/highlight_spec.lua b/test/functional/treesitter/highlight_spec.lua index 7f2b5751ae..8b405615e0 100644 --- a/test/functional/treesitter/highlight_spec.lua +++ b/test/functional/treesitter/highlight_spec.lua @@ -867,6 +867,40 @@ describe('treesitter highlighting (help)', function() ]], } end) + + it('correctly redraws injections subpriorities', function() + -- The top level string node will be highlighted first + -- with an extmark spanning multiple lines. + -- When the next line is drawn, which includes an injection, + -- make sure the highlight appears above the base tree highlight + + insert([=[ + local s = [[ + local also = lua + ]] + ]=]) + + exec_lua [[ + parser = vim.treesitter.get_parser(0, "lua", { + injections = { + lua = '(string content: (_) @injection.content (#set! injection.language lua))' + } + }) + + vim.treesitter.highlighter.new(parser) + ]] + + screen:expect { + grid = [=[ + {3:local} {4:s} {3:=} {5:[[} | + {5: }{3:local}{5: }{4:also}{5: }{3:=}{5: }{4:lua} | + {5:]]} | + ^ | + {2:~ }| + | + ]=], + } + end) end) describe('treesitter highlighting (nested injections)', function() -- cgit From 00c4962cd241044c9f02de39b34ca24b2711de43 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Wed, 13 Mar 2024 14:56:11 +0000 Subject: refactor(treesitter): move some logic into functions --- runtime/lua/vim/treesitter/highlighter.lua | 62 +++++++++++++++++++----------- 1 file changed, 40 insertions(+), 22 deletions(-) diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index 6175977b49..7bc6e5c019 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -252,6 +252,44 @@ function TSHighlighter:get_query(lang) return self._queries[lang] end +--- @param match table +--- @param bufnr integer +--- @param capture integer +--- @param metadata vim.treesitter.query.TSMetadata +--- @return string? +local function get_url(match, bufnr, capture, metadata) + ---@type string|number|nil + local url = metadata[capture] and metadata[capture].url + + if not url or type(url) == 'string' then + return url + end + + if not match or not match[url] then + return + end + + -- Assume there is only one matching node. If there is more than one, take the URL + -- from the first. + local other_node = match[url][1] + + return vim.treesitter.get_node_text(other_node, bufnr, { + metadata = metadata[url], + }) +end + +--- @param capture_name string +--- @return boolean?, integer +local function get_spell(capture_name) + if capture_name == 'spell' then + return true, 0 + elseif capture_name == 'nospell' then + -- Give nospell a higher priority so it always overrides spell captures. + return false, 1 + end + return nil, 0 +end + ---@param self vim.treesitter.highlighter ---@param buf integer ---@param line integer @@ -284,37 +322,17 @@ local function on_line_impl(self, buf, line, is_spell_nav) for capture, nodes in pairs(match or {}) do local capture_name = state.highlighter_query:query().captures[capture] - local spell = nil ---@type boolean? - if capture_name == 'spell' then - spell = true - elseif capture_name == 'nospell' then - spell = false - end + local spell, spell_pri_offset = get_spell(capture_name) local hl = state.highlighter_query:get_hl_from_capture(capture) - -- Give nospell a higher priority so it always overrides spell captures. - local spell_pri_offset = capture_name == 'nospell' and 1 or 0 - -- The "priority" attribute can be set at the pattern level or on a particular capture local priority = ( tonumber(metadata.priority or metadata[capture] and metadata[capture].priority) or vim.highlight.priorities.treesitter ) + spell_pri_offset - local url = metadata[capture] and metadata[capture].url ---@type string|number|nil - if type(url) == 'number' then - if match and match[url] then - -- Assume there is only one matching node. If there is more than one, take the URL - -- from the first. - local other_node = match[url][1] - url = vim.treesitter.get_node_text(other_node, buf, { - metadata = metadata[url], - }) - else - url = nil - end - end + local url = get_url(match, buf, capture, metadata) -- The "conceal" attribute can be set at the pattern level or on a particular capture local conceal = metadata.conceal or metadata[capture] and metadata[capture].conceal -- cgit From ff972b88db89927c8e0c1a5d76c999bb80636d92 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 14 Mar 2024 13:37:29 +0800 Subject: vim-patch:760f664213de runtime(mswin): revert back the check for clipboard_working support Commit d9ebd46bd090c598adc82e6 changed the condition to check if the clipboard is available from: ``` has('clipboard') ``` to ``` has('clipboard_working') ``` Assuming that is the more accurate test because even when clipboard support is enabled at compile time it may not be actually working (e.g. if no X11 environment is available, or when working on a remote server). However it seems that condition does not evaluate to true, when the GUI has not been started up yet (and there was no X11 Connection yet possible). So let's just revert back the check to `has('clipboard')`, since that has been proven to be working well enough. related: vim/vim#13809 https://github.com/vim/vim/commit/760f664213dea9a300454992ba1589f4601d622f Co-authored-by: Christian Brabandt --- runtime/mswin.vim | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/runtime/mswin.vim b/runtime/mswin.vim index 107a2acc2e..d419146958 100644 --- a/runtime/mswin.vim +++ b/runtime/mswin.vim @@ -1,7 +1,7 @@ " Set options and add mapping such that Vim behaves a lot like MS-Windows " " Maintainer: The Vim Project -" Last Change: 2024 Mar 3 +" Last Change: 2024 Mar 13 " Former Maintainer: Bram Moolenaar " Bail out if this isn't wanted. @@ -27,7 +27,10 @@ set backspace=indent,eol,start whichwrap+=<,>,[,] " backspace in Visual mode deletes selection vnoremap d -if has("clipboard_working") +" the better solution would be to use has("clipboard_working"), +" but that may not be available yet while starting up, so let's just check if +" clipboard support has been compiled in and assume it will be working :/ +if has("clipboard") " CTRL-X and SHIFT-Del are Cut vnoremap "+x vnoremap "+x @@ -43,7 +46,7 @@ if has("clipboard_working") cmap + cmap + else - " Use unnamed register while clipboard not exist + " Use the unnamed register when clipboard support not available " CTRL-X and SHIFT-Del are Cut vnoremap x -- cgit From 2aa84ce210af8ec8f80dd4972926dbc60971526b Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 14 Mar 2024 15:04:14 +0800 Subject: vim-patch:45da32964d6e runtime(mswin): still another clipboard_working test Commit 760f664213dea9a300454992ba1589f4601d622f missed to revert back another test for `if has('clipboard_working')` So change the remaining check around the inoremap mappings. fixes vim/vim#14195 https://github.com/vim/vim/commit/45da32964d6e7e635af8fcf0b42e974b0b536ed3 Co-authored-by: Christian Brabandt --- runtime/mswin.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/mswin.vim b/runtime/mswin.vim index d419146958..689bc792cf 100644 --- a/runtime/mswin.vim +++ b/runtime/mswin.vim @@ -70,7 +70,7 @@ endif " Uses the paste.vim autoload script. " Use CTRL-G u to have CTRL-Z only undo the paste. -if has("clipboard_working") +if has("clipboard") exe 'inoremap