diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index 074ae9f372..c394b06610 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -2684,11 +2684,6 @@ void do_autocmd_uienter(uint64_t chanid, bool attached) { static bool recursive = false; -#ifdef EXITFREE - if (entered_free_all_mem) { - return; - } -#endif if (starting == NO_SCREEN) { return; // user config hasn't been sourced yet } diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index 913d6968e8..847c732d3b 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -494,32 +494,31 @@ static void rpc_close_event(void **argv) channel_decref(channel); + // No more I/O can happen on this channel. Remove UI if there is one attached. + // Do this here instead of in rpc_free() which isn't always called on exit, so that + // UILeave events behave consistently. + remote_ui_disconnect(channel->id, NULL, false); + bool is_ui_client = ui_client_channel_id && channel->id == ui_client_channel_id; - if (is_ui_client || channel->streamtype == kChannelStreamStdio) { - if (!is_ui_client) { - // Avoid hanging when there are no other UIs and a prompt is triggered on exit. - remote_ui_disconnect(channel->id, NULL, false); - } else { - ui_client_attach_to_restarted_server(); - if (ui_client_channel_id != channel->id) { - // Attached to new server. Don't exit. - return; - } + if (is_ui_client) { + ui_client_attach_to_restarted_server(); + if (ui_client_channel_id != channel->id) { + // Attached to new server. Don't exit. + return; } - if (!channel->detach) { - if (channel->streamtype == kChannelStreamProc && ui_client_error_exit < 0) { - // Wait for the embedded server to exit instead of exiting immediately, - // as it's necessary to get the server's exit code in on_proc_exit(). - } else { - exit_on_closed_chan(0); - } + if (channel->streamtype == kChannelStreamProc && ui_client_error_exit < 0) { + // Wait for the embedded server to exit instead of exiting immediately, + // as it's necessary to get the server's exit code in on_proc_exit(). + return; } + exit_on_closed_chan(0); + } else if (channel->streamtype == kChannelStreamStdio && !channel->detach) { + exit_on_closed_chan(0); } } void rpc_free(Channel *channel) { - remote_ui_disconnect(channel->id, NULL, false); unpacker_teardown(channel->rpc.unpacker); xfree(channel->rpc.unpacker); diff --git a/test/functional/api/ui_spec.lua b/test/functional/api/ui_spec.lua index c456cd4bb6..3fcad28b3a 100644 --- a/test/functional/api/ui_spec.lua +++ b/test/functional/api/ui_spec.lua @@ -162,16 +162,65 @@ it('autocmds UIEnter/UILeave', function() autocmd UIEnter * call add(g:evs, "UIEnter") | let g:uienter_ev = deepcopy(v:event) autocmd UILeave * call add(g:evs, "UILeave") | let g:uileave_ev = deepcopy(v:event) autocmd VimEnter * call add(g:evs, "VimEnter") + autocmd VimLeave * call add(g:evs, "VimLeave") ]]) + local screen = Screen.new() eq({ chan = 1 }, eval('g:uienter_ev')) + eq({ 'VimEnter', 'UIEnter' }, eval('g:evs')) + screen:detach() eq({ chan = 1 }, eval('g:uileave_ev')) - eq({ - 'VimEnter', - 'UIEnter', - 'UILeave', - }, eval('g:evs')) + eq({ 'VimEnter', 'UIEnter', 'UILeave' }, eval('g:evs')) + + local servername = api.nvim_get_vvar('servername') + + local session2 = n.connect(servername) + local status2, chan2 = session2:request('nvim_get_chan_info', 0) + t.ok(status2) + + local session3 = n.connect(servername) + local status3, chan3 = session3:request('nvim_get_chan_info', 0) + t.ok(status3) + + local screen2 = Screen.new(nil, nil, nil, session2) + eq({ chan = chan2.id }, eval('g:uienter_ev')) + eq({ 'VimEnter', 'UIEnter', 'UILeave', 'UIEnter' }, eval('g:evs')) + + screen2:detach() + eq({ chan = chan2.id }, eval('g:uileave_ev')) + eq({ 'VimEnter', 'UIEnter', 'UILeave', 'UIEnter', 'UILeave' }, eval('g:evs')) + + command('let g:evs = ["…"]') + + screen2:attach(session2) + eq({ chan = chan2.id }, eval('g:uienter_ev')) + eq({ '…', 'UIEnter' }, eval('g:evs')) + + Screen.new(nil, nil, nil, session3) + eq({ chan = chan3.id }, eval('g:uienter_ev')) + eq({ '…', 'UIEnter', 'UIEnter' }, eval('g:evs')) + + screen:attach(n.get_session()) + eq({ chan = 1 }, eval('g:uienter_ev')) + eq({ '…', 'UIEnter', 'UIEnter', 'UIEnter' }, eval('g:evs')) + + session3:close() + t.retry(nil, 1000, function() + eq({}, api.nvim_get_chan_info(chan3.id)) + end) + eq({ chan = chan3.id }, eval('g:uileave_ev')) + eq({ '…', 'UIEnter', 'UIEnter', 'UIEnter', 'UILeave' }, eval('g:evs')) + + command('let g:evs = ["…"]') + command('autocmd UILeave * call writefile(g:evs, "Xevents.log")') + finally(function() + os.remove('Xevents.log') + end) + n.expect_exit(command, 'qall!') + n.check_close() -- Wait for process exit. + -- UILeave should have been triggered for both remaining UIs. + eq('…\nVimLeave\nUILeave\nUILeave\n', t.read_file('Xevents.log')) end) it('autocmds VimSuspend/VimResume #22041', function()