feat(normal): normal-mode ZR does :restart

Make it a normal-mode command instead of a default mapping.
This commit is contained in:
Justin M. Keyes
2026-04-20 22:15:01 +02:00
parent 2551c7a8b1
commit a1c8b81672
8 changed files with 82 additions and 20 deletions

View File

@@ -1209,9 +1209,8 @@ ZZ Write current file, if modified, and close the current
ZQ Quit without checking for changes (same as ":q!").
*ZR*
[count]ZR Mapped to |:restart|. |default-mappings|
If a [count] is given, restarts without checking for
changes (same as ":restart +qall!").
[count]ZR Performs |:restart|. If a [count] is given, restarts
without checking for changes (":restart +qall!").
MULTIPLE WINDOWS AND BUFFERS *window-exit*

View File

@@ -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
@@ -88,9 +88,6 @@ Restart Nvim
Note: If no attached UI implements the "restart" UI event,
this command will lead to a dangling server process.
Also see |ZR|.
------------------------------------------------------------------------------
Connect UI to a different server

View File

@@ -115,8 +115,7 @@ BUILD
DEFAULTS
Mappings:
• |ZR| in Normal mode to |:restart|
todo
DIAGNOSTICS
@@ -126,6 +125,7 @@ EDITOR
• |gf| and |<cfile>| support `file://…` URIs.
• |:log| opens log files.
• |ZR| restarts Nvim (|:restart|).
EVENTS

View File

@@ -171,7 +171,6 @@ you never want any default mappings, call |:mapclear| early in your config.
- Treesitter defaults |treesitter-defaults|
- |v_an| |v_in|
- |v_]n| |v_[n|
- |ZR|
DEFAULT AUTOCOMMANDS
*default-autocmds*
@@ -389,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:

View File

@@ -482,14 +482,6 @@ do
end
end, { desc = 'Select child (inner) node' })
end
-- Z layer mappings
do
vim.keymap.set('n', 'ZR', function()
local args = vim.v.count >= 1 and { '+qall!' } or nil
cmd({ cmd = 'restart', args = args })
end, { desc = ':help ZR' })
end
end
--- Default menus

View File

@@ -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);
}

View File

@@ -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.

View File

@@ -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({