diff --git a/runtime/doc/editing.txt b/runtime/doc/editing.txt index 1a51a2fd8a..f2437588d1 100644 --- a/runtime/doc/editing.txt +++ b/runtime/doc/editing.txt @@ -1208,6 +1208,10 @@ ZZ Write current file, if modified, and close the current *ZQ* ZQ Quit without checking for changes (same as ":q!"). + *ZR* +[count]ZR Performs |:restart|. If a [count] is given, restarts + without checking for changes (":restart +qall!"). + MULTIPLE WINDOWS AND BUFFERS *window-exit* *:qa* *:qall* diff --git a/runtime/doc/gui.txt b/runtime/doc/gui.txt index f15ef83098..63da98d72f 100644 --- a/runtime/doc/gui.txt +++ b/runtime/doc/gui.txt @@ -69,7 +69,7 @@ Restart Nvim *:restart* :restart [+cmd] [command] - Restarts Nvim. + Restarts Nvim. See also |ZR|. 1. Stops Nvim using `:qall` (or |+cmd|, if given). 2. Starts a new Nvim server using the same |v:argv| (except diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index f287b68d29..9b45d01e11 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -125,6 +125,7 @@ EDITOR • |gf| and || support `file://…` URIs. • |:log| opens log files. +• |ZR| restarts Nvim (|:restart|). EVENTS diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index db67c2c7ca..d738638575 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -388,6 +388,7 @@ Input/Mappings: Normal commands: - |gO| shows a filetype-defined "outline" of the current buffer. - |Q| replays the last recorded macro instead of switching to Ex mode (|gQ|). +- |ZR| performs |:restart| Options: diff --git a/runtime/lua/vim/_core/defaults.lua b/runtime/lua/vim/_core/defaults.lua index a62900684d..27165bbb04 100644 --- a/runtime/lua/vim/_core/defaults.lua +++ b/runtime/lua/vim/_core/defaults.lua @@ -295,17 +295,17 @@ do ) end + --- Execute a command and print errors without a stacktrace. + --- @param opts vim.api.keyset.cmd Arguments to |nvim_cmd()| + local function cmd(opts) + local ok, err = pcall(vim.api.nvim_cmd, opts, {}) + if not ok then + vim.api.nvim_echo({ { err:sub(#'Vim:' + 1) } }, true, { err = true }) + end + end + --- vim-unimpaired style mappings. See: https://github.com/tpope/vim-unimpaired do - --- Execute a command and print errors without a stacktrace. - --- @param opts table Arguments to |nvim_cmd()| - local function cmd(opts) - local ok, err = pcall(vim.api.nvim_cmd, opts, {}) - if not ok then - vim.api.nvim_echo({ { err:sub(#'Vim:' + 1) } }, true, { err = true }) - end - end - -- Quickfix mappings vim.keymap.set('n', '[q', function() cmd({ cmd = 'cprevious', count = vim.v.count1 }) diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 9860937612..7a5a238df2 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -3299,6 +3299,15 @@ static void nv_Zet(cmdarg_T *cap) do_cmdline_cmd("q!"); break; + // "ZR": restart. With count, restart without checking for changes. + case 'R': + if (cap->count0 >= 1) { + do_cmdline_cmd("restart +qall!"); + } else { + do_cmdline_cmd("restart"); + } + break; + default: clearopbeep(cap->oap); } diff --git a/test/client/uv_stream.lua b/test/client/uv_stream.lua index 220938307a..464f52a609 100644 --- a/test/client/uv_stream.lua +++ b/test/client/uv_stream.lua @@ -122,7 +122,9 @@ function SocketStream:read_stop() end function SocketStream:close() - uv.close(self._socket) + if not self._socket:is_closing() then + uv.close(self._socket) + end end --- Stream over child process stdio. diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 5d6b264578..5ada3ebd0a 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -10,7 +10,6 @@ local Screen = require('test.functional.ui.screen') local tt = require('test.functional.testterm') local eq = t.eq -local pcall_err = t.pcall_err local feed_data = tt.feed_data local clear = n.clear local command = n.command @@ -208,10 +207,74 @@ describe('TUI :restart', function() before_each(n.clear) after_each(n.check_close) + ---@param exp boolean Restart expected + ---@param sess table Session + ---@param addr string + local function assert_restarted(exp, sess, addr) + local s = sess + retry(nil, 5000, function() + if exp then + s:close() + s = n.connect(addr) + end + + -- Cheesy but reliable: :restart drops "-- [files…]", so empty v:argf means restart happened. + -- TODO(justinmk): add `v:startreason`, `v:starttime` + local _, argf = s:request('nvim_eval', 'v:argf') + ok(vim.tbl_count(argf) == (exp and 0 or 1), exp and 'empty v:argf' or 'nonempty v:argf', argf) + end) + end + it('validation', function() eq('Vim(restart):E481: No range allowed: :1restart', t.pcall_err(n.command, ':1restart')) end) + it('ZR', function() + -- Just exercise ZR, don't need to test all :restart functionality here. + t.skip(is_os('win'), 'FIXME: --listen not preserved by :restart on Windows #38539') + local server_pipe = new_pipename() + local server_session + finally(function() + if server_session and not server_session.closed then + server_session:close() + end + end) + local screen = tt.setup_child_nvim({ + '-u', + 'NONE', + '-i', + 'NONE', + '--listen', + server_pipe, + '--cmd', + 'set notermguicolors', + '--', + 'file1.txt', -- XXX: see comment in assert_restarted() + }, { + env = vim.tbl_extend('force', env_notermguicolors, { + -- Ignore logs, because assert_restarted may log "connection refused" while it retries. + NVIM_LOG_FILE = testlog, + }), + }) + finally(function() + os.remove(testlog) + end) + screen:expect({ any = 'file1%.txt' }) + + server_session = n.connect(server_pipe) + assert_restarted(false, server_session, server_pipe) + + -- ZR on modified buffer fails with E37. + tt.feed_data('ifoo\027') + tt.feed_data('ZR') + screen:expect({ any = 'E37' }) + + -- [count]ZR discards unsaved changes. + tt.feed_data('1ZR') + + assert_restarted(true, server_session, server_pipe) + end) + it('works', function() local server_pipe = new_pipename() local screen = tt.setup_child_nvim({