diff --git a/src/nvim/context.c b/src/nvim/context.c index 517b3ac0b4..8bc89c624c 100644 --- a/src/nvim/context.c +++ b/src/nvim/context.c @@ -564,6 +564,20 @@ bool ctx_switch(CtxSwitch *cs, win_T *wp, tabpage_T *tp, buf_T *buf, CtxSwitchFl return true; } +/// Restores curwin/curbuf and prevwin. If the saved window no longer exists, enters `fallback`. +static void ctx_restore_curwin(CtxSwitch *cs, win_T *fallback) +{ + win_T *save_curwin = win_find_by_handle(cs->cs_curwin); + if (save_curwin == NULL) { + save_curwin = fallback; // Hmm, original window disappeared. + } + if (save_curwin != NULL) { + curwin = save_curwin; + curbuf = curwin->w_buffer; + } + prevwin = win_find_by_handle(cs->cs_prevwin); +} + /// Undoes ctx_switch(): restores the previous location (if possible) and the kept state. /// /// No-op if `cs` was zero-initialized, even if ctx_switch() was not called on it: @@ -597,31 +611,18 @@ void ctx_restore(CtxSwitch *cs) } } - // Look up the window by handle: the user code may have closed it, and - // its memory been reused for another window. - win_T *const save_curwin = win_find_by_handle(cs->cs_curwin); - if (save_curwin != NULL) { - curwin = save_curwin; - curbuf = curwin->w_buffer; - } + ctx_restore_curwin(cs, NULL); } else if (cs->cs_ctxwin_idx >= 0) { win_T *cwp = ctx_win_rest(cs); - win_T *const save_curwin = win_find_by_handle(cs->cs_curwin); - if (save_curwin != NULL) { - curwin = save_curwin; - } else { - // Hmm, original window disappeared. Just use the first one. - curwin = firstwin; - } - curbuf = curwin->w_buffer; - // May need to restore insert mode for a prompt buffer. + ctx_restore_curwin(cs, firstwin); + // May need to restore insert-mode for a prompt buffer. + // Pairs with the win_enter()..leaving_window() from ctx_win_prep(). entering_window(curwin); if (bt_prompt(curbuf)) { curbuf->b_prompt_insert = cs->cs_prompt_insert; } - prevwin = win_find_by_handle(cs->cs_prevwin); vars_clear(&cwp->w_vars->dv_hashtab); // free all w: variables hash_init(&cwp->w_vars->dv_hashtab); // re-use the hashtab @@ -641,29 +642,21 @@ void ctx_restore(CtxSwitch *cs) curwin->w_topfill = 0; } } else { - // Restore curwin. Use the window ID, a window may have been closed - // and the memory re-used for another one. - win_T *const save_curwin = win_find_by_handle(cs->cs_curwin); - if (save_curwin != NULL) { - // Restore the buffer which was previously edited by curwin, if it was - // changed, we are still the same window and the buffer is valid. - if (curwin->handle == cs->cs_new_curwin - && curbuf != cs->cs_new_curbuf.br_buf - && bufref_valid(&cs->cs_new_curbuf) - && cs->cs_new_curbuf.br_buf->b_ml.ml_mfp != NULL) { - if (curwin->w_s == &curbuf->b_s) { - curwin->w_s = &cs->cs_new_curbuf.br_buf->b_s; - } - curbuf->b_nwindows--; - curbuf = cs->cs_new_curbuf.br_buf; - curwin->w_buffer = curbuf; - curbuf->b_nwindows++; + // Restore the buffer previously edited by curwin. + if (curwin->handle == cs->cs_new_curwin + && curbuf != cs->cs_new_curbuf.br_buf + && bufref_valid(&cs->cs_new_curbuf) + && cs->cs_new_curbuf.br_buf->b_ml.ml_mfp != NULL) { + if (curwin->w_s == &curbuf->b_s) { + curwin->w_s = &cs->cs_new_curbuf.br_buf->b_s; } - - curwin = save_curwin; - curbuf = curwin->w_buffer; - prevwin = win_find_by_handle(cs->cs_prevwin); + curbuf->b_nwindows--; + curbuf = cs->cs_new_curbuf.br_buf; + curwin->w_buffer = curbuf; + curbuf->b_nwindows++; } + + ctx_restore_curwin(cs, NULL); } if (!cs->cs_same_win) { diff --git a/test/functional/api/buffer_spec.lua b/test/functional/api/buffer_spec.lua index 1ed77c41ff..a44af2b4eb 100644 --- a/test/functional/api/buffer_spec.lua +++ b/test/functional/api/buffer_spec.lua @@ -2553,6 +2553,21 @@ describe('api/buf', function() end) describe('nvim_buf_call', function() + it('window keeps the buffer when the callback closes the original window', function() + local origin = api.nvim_get_current_win() + local other_buf = api.nvim_create_buf(true, false) + local other_win = api.nvim_open_win(other_buf, false, { split = 'right' }) + exec_lua(function() + vim.api.nvim_buf_call(other_buf, function() + vim.api.nvim_win_close(origin, true) + vim.cmd('enew') -- switch the context window away from other_buf + end) + end) + -- Original window is gone: stay in the nvim_buf_call window, but buffer is restored. + eq(other_win, api.nvim_get_current_win()) + eq(other_buf, api.nvim_win_get_buf(other_win)) + end) + it('preserves visual-mode, unless the callback ended it', function() -- Same-buffer: Visual survives untouched. command('normal! v') diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index f6b13edecf..13a50b502a 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -3767,6 +3767,24 @@ describe('API/win', function() end) describe('nvim_win_call', function() + it('restores prevwin', function() + local w1 = api.nvim_get_current_win() + command('split') + local w2 = api.nvim_get_current_win() + command('split') + local w3 = api.nvim_get_current_win() + -- Entry state: curwin=w3, prevwin=w2. + eq(w2, fn.win_getid(fn.winnr('#'))) + exec_lua(function() + vim.api.nvim_win_call(w1, function() + vim.api.nvim_set_current_win(w2) -- changes prevwin to w1 + end) + end) + -- No evidence of the context-switch; curwin/prevwin are restored. + eq(w3, api.nvim_get_current_win()) + eq(w2, fn.win_getid(fn.winnr('#'))) + end) + it('supports multiple returns', function() local cur = api.nvim_get_current_win() local other = api.nvim_open_win(api.nvim_create_buf(false, true), false, { split = 'left' })