diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index 5f6bcf14fd..9dd1de04e4 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -1432,9 +1432,11 @@ void aucmd_prepbuf(aco_save_T *aco, buf_T *buf) } aco->save_curwin_handle = curwin->handle; - aco->save_curbuf = curbuf; aco->save_prevwin_handle = prevwin == NULL ? 0 : prevwin->handle; aco->save_State = State; + if (bt_prompt(curbuf)) { + aco->save_prompt_insert = curbuf->b_prompt_insert; + } if (win != NULL) { // There is a window for "buf" in the current tab page, make it the @@ -1547,6 +1549,9 @@ win_found: curbuf = curwin->w_buffer; // May need to restore insert mode for a prompt buffer. entering_window(curwin); + if (bt_prompt(curbuf)) { + curbuf->b_prompt_insert = aco->save_prompt_insert; + } prevwin = win_find_by_handle(aco->save_prevwin_handle); vars_clear(&awp->w_vars->dv_hashtab); // free all w: variables diff --git a/src/nvim/autocmd.h b/src/nvim/autocmd.h index 6dbd18ba7c..10d1aede37 100644 --- a/src/nvim/autocmd.h +++ b/src/nvim/autocmd.h @@ -24,7 +24,6 @@ struct AutoPat_S; // Struct to save values in before executing autocommands for a buffer that is // not the current buffer. typedef struct { - buf_T *save_curbuf; ///< saved curbuf int use_aucmd_win_idx; ///< index in aucmd_win[] if >= 0 handle_T save_curwin_handle; ///< ID of saved curwin handle_T new_curwin_handle; ///< ID of new curwin @@ -33,6 +32,7 @@ typedef struct { char *globaldir; ///< saved value of globaldir bool save_VIsual_active; ///< saved VIsual_active int save_State; ///< saved State + int save_prompt_insert; ///< saved b_prompt_insert } aco_save_T; typedef struct AutoCmd_S AutoCmd; diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 80acabc51d..0cc84966ec 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -393,7 +393,7 @@ static int insert_check(VimState *state) Insstart_orig = Insstart; } - if (curbuf->terminal) { + if (curbuf->terminal && !stop_insert_mode) { // Exit Insert mode and go to Terminal mode. stop_insert_mode = true; restart_edit = 'I'; diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 40d294ca25..0ad105427e 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -2252,6 +2252,7 @@ static buf_T *cmdpreview_open_buf(void) /// /// @return Pointer to command preview window if succeeded, NULL if failed. static win_T *cmdpreview_open_win(buf_T *cmdpreview_buf) + FUNC_ATTR_NONNULL_ALL { win_T *save_curwin = curwin; @@ -2523,10 +2524,10 @@ static bool cmdpreview_may_show(CommandLineState *s) cmdpreview_prepare(&cpinfo); // Open preview buffer if inccommand=split. - if (!icm_split) { - cmdpreview_bufnr = 0; - } else if ((cmdpreview_buf = cmdpreview_open_buf()) == NULL) { - abort(); + if (icm_split && (cmdpreview_buf = cmdpreview_open_buf()) == NULL) { + // Failed to create preview buffer, so disable preview. + set_string_option_direct("icm", -1, "nosplit", OPT_FREE, SID_NONE); + icm_split = false; } // Setup preview namespace if it's not already set. if (!cmdpreview_ns) { diff --git a/src/nvim/window.c b/src/nvim/window.c index 7d99aafa5e..0edc2e143a 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -2477,7 +2477,7 @@ void leaving_window(win_T *const win) // When leaving the window (or closing the window) was done from a // callback we need to break out of the Insert mode loop and restart Insert // mode when entering the window again. - if (State & MODE_INSERT) { + if ((State & MODE_INSERT) && !stop_insert_mode) { stop_insert_mode = true; if (win->w_buffer->b_prompt_insert == NUL) { win->w_buffer->b_prompt_insert = 'A'; diff --git a/test/functional/legacy/prompt_buffer_spec.lua b/test/functional/legacy/prompt_buffer_spec.lua index 5c3f8a6f8c..b18b9c8055 100644 --- a/test/functional/legacy/prompt_buffer_spec.lua +++ b/test/functional/legacy/prompt_buffer_spec.lua @@ -4,6 +4,7 @@ local feed = helpers.feed local source = helpers.source local clear = helpers.clear local command = helpers.command +local expect = helpers.expect local poke_eventloop = helpers.poke_eventloop local meths = helpers.meths local eq = helpers.eq @@ -263,4 +264,36 @@ describe('prompt buffer', function() command('call DoAppend()') eq({ mode = 'i', blocking = false }, meths.get_mode()) end) + + -- oldtest: Test_prompt_leave_modify_hidden() + it('modifying hidden buffer does not prevent prompt buffer mode change', function() + source([[ + file hidden + set bufhidden=hide + enew + new prompt + set buftype=prompt + + inoremap w wincmd w + inoremap q bwipe! + autocmd BufLeave prompt call appendbufline('hidden', '$', 'Leave') + autocmd BufEnter prompt call appendbufline('hidden', '$', 'Enter') + autocmd BufWinLeave prompt call appendbufline('hidden', '$', 'Close') + ]]) + feed('a') + eq({ mode = 'i', blocking = false }, meths.get_mode()) + feed('w') + eq({ mode = 'n', blocking = false }, meths.get_mode()) + feed('w') + eq({ mode = 'i', blocking = false }, meths.get_mode()) + feed('q') + eq({ mode = 'n', blocking = false }, meths.get_mode()) + command('bwipe!') + expect([[ + + Leave + Enter + Leave + Close]]) + end) end) diff --git a/test/functional/terminal/ex_terminal_spec.lua b/test/functional/terminal/ex_terminal_spec.lua index 427146b2e5..93945b0365 100644 --- a/test/functional/terminal/ex_terminal_spec.lua +++ b/test/functional/terminal/ex_terminal_spec.lua @@ -139,6 +139,15 @@ describe(':terminal', function() eq({ blocking=false, mode='t' }, nvim('get_mode')) eq({'InsertLeave', 'TermEnter'}, eval('g:events')) end) + + it('switching to terminal buffer immediately after :stopinsert #27031', function() + command('terminal') + command('vnew') + feed('i') + eq({ blocking = false, mode = 'i' }, nvim('get_mode')) + command('stopinsert | wincmd p') + eq({ blocking = false, mode = 'nt' }, nvim('get_mode')) + end) end) local function test_terminal_with_fake_shell(backslash) diff --git a/test/functional/ui/inccommand_spec.lua b/test/functional/ui/inccommand_spec.lua index 26d759801b..848903cc80 100644 --- a/test/functional/ui/inccommand_spec.lua +++ b/test/functional/ui/inccommand_spec.lua @@ -3250,3 +3250,54 @@ it("with 'inccommand' typing :filter doesn't segfault or leak memory #19057", fu feed('i') assert_alive() end) + +it("'inccommand' value can be changed multiple times #27086", function() + clear() + local screen = Screen.new(30, 20) + common_setup(screen, 'split', 'foo1\nfoo2\nfoo3') + for _ = 1, 3 do + feed(':%s/foo/bar') + screen:expect([[ + {12:bar}1 | + {12:bar}2 | + {12:bar}3 | + {15:~ }|*7 + {11:[No Name] [+] }| + |1| {12:bar}1 | + |2| {12:bar}2 | + |3| {12:bar}3 | + {15:~ }|*4 + {10:[Preview] }| + :%s/foo/bar^ | + ]]) + feed('') + command('set inccommand=nosplit') + feed(':%s/foo/bar') + screen:expect([[ + {12:bar}1 | + {12:bar}2 | + {12:bar}3 | + {15:~ }|*16 + :%s/foo/bar^ | + ]]) + feed('') + command('set inccommand=split') + end +end) + +it("'inccommand' disables preview if preview buffer can't be created #27086", function() + clear() + meths.buf_set_name(0, '[Preview]') + local screen = Screen.new(30, 20) + common_setup(screen, 'split', 'foo1\nfoo2\nfoo3') + eq('split', meths.get_option_value('inccommand', {})) + feed(':%s/foo/bar') + screen:expect([[ + {12:bar}1 | + {12:bar}2 | + {12:bar}3 | + {15:~ }|*16 + :%s/foo/bar^ | + ]]) + eq('nosplit', meths.get_option_value('inccommand', {})) +end) diff --git a/test/functional/ui/inccommand_user_spec.lua b/test/functional/ui/inccommand_user_spec.lua index ef715ba832..f4394e36ce 100644 --- a/test/functional/ui/inccommand_user_spec.lua +++ b/test/functional/ui/inccommand_user_spec.lua @@ -1,6 +1,8 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') +local meths = helpers.meths local clear = helpers.clear +local eq = helpers.eq local exec_lua = helpers.exec_lua local insert = helpers.insert local feed = helpers.feed @@ -543,6 +545,22 @@ describe("'inccommand' for user commands", function() test_preview_break_undo() end) end) + + it('disables preview if preview buffer cannot be created #27086', function() + command('set inccommand=split') + meths.buf_set_name(0, '[Preview]') + exec_lua([[ + vim.api.nvim_create_user_command('Test', function() end, { + nargs = '*', + preview = function(_, _, _) + return 2 + end + }) + ]]) + eq('split', meths.get_option_value('inccommand', {})) + feed(':Test') + eq('nosplit', meths.get_option_value('inccommand', {})) + end) end) describe("'inccommand' with multiple buffers", function() diff --git a/test/old/testdir/test_prompt_buffer.vim b/test/old/testdir/test_prompt_buffer.vim index 2cc3f19b59..0d5b4e4499 100644 --- a/test/old/testdir/test_prompt_buffer.vim +++ b/test/old/testdir/test_prompt_buffer.vim @@ -302,4 +302,49 @@ func Test_prompt_appending_while_hidden() call StopVimInTerminal(buf) endfunc +" Modifying a hidden buffer while leaving a prompt buffer should not prevent +" stopping of Insert mode, and returning to the prompt buffer later should +" restore Insert mode. +func Test_prompt_leave_modify_hidden() + call CanTestPromptBuffer() + + let script =<< trim END + file hidden + set bufhidden=hide + enew + new prompt + set buftype=prompt + + inoremap w wincmd w + inoremap q bwipe! + autocmd BufLeave prompt call appendbufline('hidden', '$', 'Leave') + autocmd BufEnter prompt call appendbufline('hidden', '$', 'Enter') + autocmd BufWinLeave prompt call appendbufline('hidden', '$', 'Close') + END + call writefile(script, 'XpromptLeaveModifyHidden', 'D') + + let buf = RunVimInTerminal('-S XpromptLeaveModifyHidden', {'rows': 10}) + call TermWait(buf) + + call term_sendkeys(buf, "a") + call WaitForAssert({-> assert_match('-- INSERT --', term_getline(buf, 10))}) + + call term_sendkeys(buf, "w") + call WaitForAssert({-> assert_notmatch('-- INSERT --', term_getline(buf, 10))}) + + call term_sendkeys(buf, "\w") + call WaitForAssert({-> assert_match('-- INSERT --', term_getline(buf, 10))}) + + call term_sendkeys(buf, "q") + call WaitForAssert({-> assert_notmatch('-- INSERT --', term_getline(buf, 10))}) + + call term_sendkeys(buf, ":bwipe!\") + call WaitForAssert({-> assert_equal('Leave', term_getline(buf, 2))}) + call WaitForAssert({-> assert_equal('Enter', term_getline(buf, 3))}) + call WaitForAssert({-> assert_equal('Leave', term_getline(buf, 4))}) + call WaitForAssert({-> assert_equal('Close', term_getline(buf, 5))}) + + call StopVimInTerminal(buf) +endfunc + " vim: shiftwidth=2 sts=2 expandtab