From 415626d46d8bab11eeb4b99b7f4d156b077aa6c9 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 4 Apr 2026 21:57:27 +0800 Subject: [PATCH] fix(:restart): inherit stderr fd on Unix (#38755) This in turn gives TTY access to channel_from_stdio() in the new server, if the old server has access to a TTY. (cherry picked from commit 9927d9259d9a91f61fd14cec31bb848ac78d0863) --- src/nvim/ex_docmd.c | 20 +++++++++++++++++--- test/functional/terminal/tui_spec.lua | 25 ++++++++++++++++++++++--- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 93dd0f0e60..8a8f9df413 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -5023,9 +5023,13 @@ static void ex_restart(exarg_T *eap) } CallbackReader on_err = CALLBACK_READER_INIT; - // This temporary bootstrap channel is closed intentionally once we obtain - // the new server address. Don't forward child stderr to the current UI. +#ifdef MSWIN + // On Windows, don't forward stderr as it won't work after the current server exits. on_err.fwd_err = false; +#else + // On Unix, stderr fd is inherited, so it works even after the current server exits. + on_err.fwd_err = true; +#endif bool detach = true; varnumber_T exit_status; @@ -5110,8 +5114,18 @@ fail_2: api_clear_error(&err); } arena_mem_free(result_mem); + result_mem = NULL; - // Kill the new nvim server. +#ifndef MSWIN + // Before killing the new server, close its stderr to avoid polluting the current UI. + MAXSIZE_TEMP_ARRAY(chanclose_expr_args, 1); + ADD_C(chanclose_expr_args, CSTR_AS_OBJ("chanclose(v:stderr)")); + rpc_send_call(channel->id, "nvim_eval", chanclose_expr_args, &result_mem, &err); + api_clear_error(&err); + arena_mem_free(result_mem); +#endif + + // Kill the new Nvim server. proc_stop(&channel->stream.proc); if (proc_wait(&channel->stream.proc, -1, NULL) < 0) { emsg("killing new nvim server failed"); diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 9c411d02c7..c0b3860b36 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -224,7 +224,7 @@ describe('TUI :restart', function() '--cmd', 'colorscheme vim', '--cmd', - 'set laststatus=2 background=dark noruler', + 'set laststatus=2 background=dark noruler noshowcmd', -- XXX: New server starts before the UI connects to it. -- So checking screen state for this pid is not possible. -- '--cmd', @@ -315,8 +315,15 @@ describe('TUI :restart', function() assert_termguicolors_and_no_gui_running() -- Check ":restart +echo" cannot restart server. + -- Check the full screen state to ensure this doesn't pollute the current UI. tt.feed_data(':restart +echo\013') - screen:expect({ any = vim.pesc('+cmd did not quit the server') }) + screen:expect([[ + ^ | + {1:~}{18: }|*3 + {3:[No Name] }| + {9:restart failed: +cmd did not quit the server} | + {5:-- TERMINAL --} | + ]]) tt.feed_data('ithis will be removed\027') screen:expect({ any = vim.pesc('this will be remove^d') }) @@ -514,7 +521,7 @@ describe('TUI :restart', function() '-u', config_file, '--cmd', - 'set notermguicolors noswapfile laststatus=0 nowrap noruler', + 'set notermguicolors noswapfile laststatus=0 nowrap noruler noshowcmd', }, { env = env_notermguicolors }) screen:expect([[ ^ | @@ -2906,6 +2913,18 @@ describe('TUI', function() { 'tty', 'tty' }, child_exec_lua('return { vim.uv.guess_handle(0), vim.uv.guess_handle(1) }') ) + -- Also works after :restart #38745 + feed_data(':restart lua ={ vim.uv.guess_handle(0), vim.uv.guess_handle(1) }\r') + screen:expect([[ + ^ | + {100:~ }|*3 + {3:[No Name] }| + { "tty", "tty" } | + {5:-- TERMINAL --} | + ]]) + -- The server is now detached and needs to be quit explicitly. + feed_data(':qall!\r') + screen:expect({ any = vim.pesc('[Process exited 0]') }) end) end)