mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
fix(rpc)!: preseve files when stdio channel is closed (#22137)
BREAKING CHANGE: Unsaved changes are now preserved rather than discarded when stdio channel is closed.
This commit is contained in:
parent
4be6c6cf0d
commit
7d58de11f4
@ -47,6 +47,9 @@ The following changes may require adaptations in user config or plugins.
|
|||||||
|
|
||||||
• libiconv is now a required build dependency.
|
• libiconv is now a required build dependency.
|
||||||
|
|
||||||
|
• Unsaved changes are now preserved rather than discarded when |channel-stdio|
|
||||||
|
is closed.
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
NEW FEATURES *news-features*
|
NEW FEATURES *news-features*
|
||||||
|
|
||||||
|
@ -422,7 +422,12 @@ static void exit_event(void **argv)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!exiting) {
|
if (!exiting) {
|
||||||
|
if (ui_client_channel_id) {
|
||||||
os_exit(status);
|
os_exit(status);
|
||||||
|
} else {
|
||||||
|
assert(status == 0); // Called from rpc_close(), which passes 0 as status.
|
||||||
|
preserve_exit(NULL);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -674,6 +674,7 @@ void os_exit(int r)
|
|||||||
void getout(int exitval)
|
void getout(int exitval)
|
||||||
FUNC_ATTR_NORETURN
|
FUNC_ATTR_NORETURN
|
||||||
{
|
{
|
||||||
|
assert(!ui_client_channel_id);
|
||||||
exiting = true;
|
exiting = true;
|
||||||
|
|
||||||
// On error during Ex mode, exit with a non-zero code.
|
// On error during Ex mode, exit with a non-zero code.
|
||||||
@ -794,6 +795,7 @@ void getout(int exitval)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Preserve files, print contents of `errmsg`, and exit 1.
|
/// Preserve files, print contents of `errmsg`, and exit 1.
|
||||||
|
/// @param errmsg If NULL, this function will not print anything.
|
||||||
///
|
///
|
||||||
/// May be called from deadly_signal().
|
/// May be called from deadly_signal().
|
||||||
void preserve_exit(const char *errmsg)
|
void preserve_exit(const char *errmsg)
|
||||||
@ -819,19 +821,21 @@ void preserve_exit(const char *errmsg)
|
|||||||
// For TUI: exit alternate screen so that the error messages can be seen.
|
// For TUI: exit alternate screen so that the error messages can be seen.
|
||||||
ui_client_stop();
|
ui_client_stop();
|
||||||
}
|
}
|
||||||
|
if (errmsg != NULL) {
|
||||||
os_errmsg(errmsg);
|
os_errmsg(errmsg);
|
||||||
os_errmsg("\n");
|
os_errmsg("\n");
|
||||||
|
}
|
||||||
if (ui_client_channel_id) {
|
if (ui_client_channel_id) {
|
||||||
os_exit(1);
|
os_exit(1);
|
||||||
}
|
}
|
||||||
ui_flush();
|
|
||||||
|
|
||||||
ml_close_notmod(); // close all not-modified buffers
|
ml_close_notmod(); // close all not-modified buffers
|
||||||
|
|
||||||
FOR_ALL_BUFFERS(buf) {
|
FOR_ALL_BUFFERS(buf) {
|
||||||
if (buf->b_ml.ml_mfp != NULL && buf->b_ml.ml_mfp->mf_fname != NULL) {
|
if (buf->b_ml.ml_mfp != NULL && buf->b_ml.ml_mfp->mf_fname != NULL) {
|
||||||
|
if (errmsg != NULL) {
|
||||||
os_errmsg("Vim: preserving files...\r\n");
|
os_errmsg("Vim: preserving files...\r\n");
|
||||||
ui_flush();
|
}
|
||||||
ml_sync_all(false, false, true); // preserve all swap files
|
ml_sync_all(false, false, true); // preserve all swap files
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -839,7 +843,9 @@ void preserve_exit(const char *errmsg)
|
|||||||
|
|
||||||
ml_close_all(false); // close all memfiles, without deleting
|
ml_close_all(false); // close all memfiles, without deleting
|
||||||
|
|
||||||
|
if (errmsg != NULL) {
|
||||||
os_errmsg("Vim: Finished.\r\n");
|
os_errmsg("Vim: Finished.\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
getout(1);
|
getout(1);
|
||||||
}
|
}
|
||||||
|
@ -550,6 +550,10 @@ void rpc_close(Channel *channel)
|
|||||||
|
|
||||||
if (channel->streamtype == kChannelStreamStdio
|
if (channel->streamtype == kChannelStreamStdio
|
||||||
|| (channel->id == ui_client_channel_id && channel->streamtype != kChannelStreamProc)) {
|
|| (channel->id == ui_client_channel_id && channel->streamtype != kChannelStreamProc)) {
|
||||||
|
if (channel->streamtype == kChannelStreamStdio) {
|
||||||
|
// Avoid hanging when there are no other UIs and a prompt is triggered on exit.
|
||||||
|
remote_ui_disconnect(channel->id);
|
||||||
|
}
|
||||||
exit_from_channel(0);
|
exit_from_channel(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@ local Screen = require('test.functional.ui.screen')
|
|||||||
local helpers = require('test.functional.helpers')(after_each)
|
local helpers = require('test.functional.helpers')(after_each)
|
||||||
|
|
||||||
local clear = helpers.clear
|
local clear = helpers.clear
|
||||||
|
local command = helpers.command
|
||||||
|
local expect_exit = helpers.expect_exit
|
||||||
local buf, eq, feed_command = helpers.curbufmeths, helpers.eq, helpers.feed_command
|
local buf, eq, feed_command = helpers.curbufmeths, helpers.eq, helpers.feed_command
|
||||||
local feed, poke_eventloop = helpers.feed, helpers.poke_eventloop
|
local feed, poke_eventloop = helpers.feed, helpers.poke_eventloop
|
||||||
local ok = helpers.ok
|
local ok = helpers.ok
|
||||||
@ -19,6 +21,7 @@ describe(':oldfiles', function()
|
|||||||
before_each(_clear)
|
before_each(_clear)
|
||||||
|
|
||||||
after_each(function()
|
after_each(function()
|
||||||
|
expect_exit(command, 'qall!')
|
||||||
os.remove(shada_file)
|
os.remove(shada_file)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@ -42,6 +45,7 @@ describe(':oldfiles', function()
|
|||||||
|
|
|
|
||||||
Press ENTER or type command to continue^ |
|
Press ENTER or type command to continue^ |
|
||||||
]])
|
]])
|
||||||
|
feed('<CR>')
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('can be filtered with :filter', function()
|
it('can be filtered with :filter', function()
|
||||||
@ -107,6 +111,7 @@ describe(':browse oldfiles', function()
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
after_each(function()
|
after_each(function()
|
||||||
|
expect_exit(command, 'qall!')
|
||||||
os.remove(shada_file)
|
os.remove(shada_file)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@ describe(':profile', function()
|
|||||||
before_each(helpers.clear)
|
before_each(helpers.clear)
|
||||||
|
|
||||||
after_each(function()
|
after_each(function()
|
||||||
|
helpers.expect_exit(command, 'qall!')
|
||||||
if lfs.attributes(tempfile, 'uid') ~= nil then
|
if lfs.attributes(tempfile, 'uid') ~= nil then
|
||||||
os.remove(tempfile)
|
os.remove(tempfile)
|
||||||
end
|
end
|
||||||
|
@ -13,6 +13,7 @@ local nvim_prog = helpers.nvim_prog
|
|||||||
local ok = helpers.ok
|
local ok = helpers.ok
|
||||||
local rmdir = helpers.rmdir
|
local rmdir = helpers.rmdir
|
||||||
local new_argv = helpers.new_argv
|
local new_argv = helpers.new_argv
|
||||||
|
local new_pipename = helpers.new_pipename
|
||||||
local pesc = helpers.pesc
|
local pesc = helpers.pesc
|
||||||
local os_kill = helpers.os_kill
|
local os_kill = helpers.os_kill
|
||||||
local set_session = helpers.set_session
|
local set_session = helpers.set_session
|
||||||
@ -37,19 +38,8 @@ describe(':recover', function()
|
|||||||
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
describe(':preserve', function()
|
describe("preserve and (R)ecover with custom 'directory'", function()
|
||||||
local swapdir = lfs.currentdir()..'/Xtest_recover_dir'
|
local swapdir = lfs.currentdir()..'/Xtest_recover_dir'
|
||||||
before_each(function()
|
|
||||||
clear()
|
|
||||||
rmdir(swapdir)
|
|
||||||
lfs.mkdir(swapdir)
|
|
||||||
end)
|
|
||||||
after_each(function()
|
|
||||||
command('%bwipeout!')
|
|
||||||
rmdir(swapdir)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("saves to custom 'directory' and (R)ecovers #1836", function()
|
|
||||||
local testfile = 'Xtest_recover_file1'
|
local testfile = 'Xtest_recover_file1'
|
||||||
-- Put swapdir at the start of the 'directory' list. #1836
|
-- Put swapdir at the start of the 'directory' list. #1836
|
||||||
-- Note: `set swapfile` *must* go after `set directory`: otherwise it may
|
-- Note: `set swapfile` *must* go after `set directory`: otherwise it may
|
||||||
@ -59,15 +49,27 @@ describe(':preserve', function()
|
|||||||
set swapfile fileformat=unix undolevels=-1
|
set swapfile fileformat=unix undolevels=-1
|
||||||
]]
|
]]
|
||||||
|
|
||||||
|
local nvim1
|
||||||
|
before_each(function()
|
||||||
|
nvim1 = spawn(new_argv())
|
||||||
|
set_session(nvim1)
|
||||||
|
rmdir(swapdir)
|
||||||
|
lfs.mkdir(swapdir)
|
||||||
|
end)
|
||||||
|
after_each(function()
|
||||||
|
command('%bwipeout!')
|
||||||
|
rmdir(swapdir)
|
||||||
|
end)
|
||||||
|
|
||||||
|
local function setup_swapname()
|
||||||
exec(init)
|
exec(init)
|
||||||
command('edit! '..testfile)
|
command('edit! '..testfile)
|
||||||
feed('isometext<esc>')
|
feed('isometext<esc>')
|
||||||
command('preserve')
|
|
||||||
exec('redir => g:swapname | silent swapname | redir END')
|
exec('redir => g:swapname | silent swapname | redir END')
|
||||||
|
return eval('g:swapname')
|
||||||
|
end
|
||||||
|
|
||||||
local swappath1 = eval('g:swapname')
|
local function test_recover(swappath1)
|
||||||
|
|
||||||
os_kill(eval('getpid()'))
|
|
||||||
-- Start another Nvim instance.
|
-- Start another Nvim instance.
|
||||||
local nvim2 = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed'},
|
local nvim2 = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed'},
|
||||||
true)
|
true)
|
||||||
@ -90,6 +92,35 @@ describe(':preserve', function()
|
|||||||
-- Verify that :swapname was not truncated (:help 'shortmess').
|
-- Verify that :swapname was not truncated (:help 'shortmess').
|
||||||
ok(nil == string.find(swappath1, '%.%.%.'))
|
ok(nil == string.find(swappath1, '%.%.%.'))
|
||||||
ok(nil == string.find(swappath2, '%.%.%.'))
|
ok(nil == string.find(swappath2, '%.%.%.'))
|
||||||
|
end
|
||||||
|
|
||||||
|
it('with :preserve and SIGKILL', function()
|
||||||
|
local swappath1 = setup_swapname()
|
||||||
|
command('preserve')
|
||||||
|
os_kill(eval('getpid()'))
|
||||||
|
test_recover(swappath1)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('closing stdio channel without :preserve #22096', function()
|
||||||
|
local swappath1 = setup_swapname()
|
||||||
|
nvim1:close()
|
||||||
|
test_recover(swappath1)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('killing TUI process without :preserve #22096', function()
|
||||||
|
helpers.skip(helpers.is_os('win'))
|
||||||
|
local screen = Screen.new()
|
||||||
|
screen:attach()
|
||||||
|
local child_server = new_pipename()
|
||||||
|
funcs.termopen({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--listen', child_server})
|
||||||
|
screen:expect({any = pesc('[No Name]')}) -- Wait for the child process to start.
|
||||||
|
local child_session = helpers.connect(child_server)
|
||||||
|
set_session(child_session)
|
||||||
|
local swappath1 = setup_swapname()
|
||||||
|
set_session(nvim1)
|
||||||
|
command('call chanclose(&channel)') -- Kill the child process.
|
||||||
|
screen:expect({any = pesc('[Process exited 1]')}) -- Wait for the child process to stop.
|
||||||
|
test_recover(swappath1)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
end)
|
end)
|
||||||
|
@ -33,6 +33,7 @@ local function reset(o)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local clear = function()
|
local clear = function()
|
||||||
|
helpers.expect_exit(helpers.command, 'qall!')
|
||||||
os.remove(tmpname)
|
os.remove(tmpname)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -32,7 +32,6 @@ describe('ShaDa support code', function()
|
|||||||
nvim_command('rshada')
|
nvim_command('rshada')
|
||||||
eq('" Test 2', funcs.histget(':', -1))
|
eq('" Test 2', funcs.histget(':', -1))
|
||||||
eq('" Test', funcs.histget(':', -2))
|
eq('" Test', funcs.histget(':', -2))
|
||||||
expect_exit(nvim_command, 'qall')
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('respects &history when dumping',
|
it('respects &history when dumping',
|
||||||
|
@ -136,24 +136,6 @@ describe('ShaDa support code', function()
|
|||||||
eq(#msgpack, found)
|
eq(#msgpack, found)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('does not write NONE file', function()
|
|
||||||
local session = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed',
|
|
||||||
'--headless', '--cmd', 'qall'}, true)
|
|
||||||
session:close()
|
|
||||||
eq(nil, lfs.attributes('NONE'))
|
|
||||||
eq(nil, lfs.attributes('NONE.tmp.a'))
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('does not read NONE file', function()
|
|
||||||
write_file('NONE', '\005\001\015\131\161na\162rX\194\162rc\145\196\001-')
|
|
||||||
local session = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed',
|
|
||||||
'--headless'}, true)
|
|
||||||
set_session(session)
|
|
||||||
eq('', funcs.getreg('a'))
|
|
||||||
session:close()
|
|
||||||
os.remove('NONE')
|
|
||||||
end)
|
|
||||||
|
|
||||||
local marklike = {[7]=true, [8]=true, [10]=true, [11]=true}
|
local marklike = {[7]=true, [8]=true, [10]=true, [11]=true}
|
||||||
local find_file = function(fname)
|
local find_file = function(fname)
|
||||||
local found = {}
|
local found = {}
|
||||||
@ -263,3 +245,23 @@ describe('ShaDa support code', function()
|
|||||||
meths.set_option('shada', '')
|
meths.set_option('shada', '')
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
describe('ShaDa support code', function()
|
||||||
|
it('does not write NONE file', function()
|
||||||
|
local session = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed',
|
||||||
|
'--headless', '--cmd', 'qall'}, true)
|
||||||
|
session:close()
|
||||||
|
eq(nil, lfs.attributes('NONE'))
|
||||||
|
eq(nil, lfs.attributes('NONE.tmp.a'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('does not read NONE file', function()
|
||||||
|
write_file('NONE', '\005\001\015\131\161na\162rX\194\162rc\145\196\001-')
|
||||||
|
local session = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed',
|
||||||
|
'--headless'}, true)
|
||||||
|
set_session(session)
|
||||||
|
eq('', funcs.getreg('a'))
|
||||||
|
session:close()
|
||||||
|
os.remove('NONE')
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
Loading…
Reference in New Issue
Block a user