aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/autocmd.txt9
-rw-r--r--src/nvim/auevents.lua1
-rw-r--r--src/nvim/ex_cmds2.c4
-rw-r--r--src/nvim/ex_docmd.c58
-rw-r--r--src/nvim/testdir/test_exit.vim57
-rw-r--r--src/nvim/testdir/test_writefile.vim82
6 files changed, 189 insertions, 22 deletions
diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt
index 24bcb13e6e..2f9d8aa7f7 100644
--- a/runtime/doc/autocmd.txt
+++ b/runtime/doc/autocmd.txt
@@ -274,7 +274,8 @@ Name triggered by ~
|GUIEnter| after starting the GUI successfully
|GUIFailed| after starting the GUI failed
|TermResponse| after the terminal response to |t_RV| is received
-|QuitPre| when using `:quit`, before deciding whether to quit
+|QuitPre| when using `:quit`, before deciding whether to exit
+|ExitPre| when using a command that may make Vim exit
|VimLeavePre| before exiting Nvim, before writing the shada file
|VimLeave| before exiting Nvim, after writing the shada file
|VimResume| after Nvim is resumed
@@ -646,6 +647,11 @@ FileChangedRO Before making the first change to a read-only
*E881*
If the number of lines changes saving for undo
may fail and the change will be aborted.
+ *ExitPre*
+ExitPre When using `:quit`, `:wq` in a way it makes
+ Vim exit, or using `:qall`, just after
+ |QuitPre|. Can be used to close any
+ non-essential window.
*FileChangedShell*
FileChangedShell When Vim notices that the modification time of
a file has changed since editing started.
@@ -862,6 +868,7 @@ QuitPre When using `:quit`, `:wq` or `:qall`, before
or quits Vim. Can be used to close any
non-essential window if the current window is
the last ordinary window.
+ Also see |ExitPre|.
*RemoteReply*
RemoteReply When a reply from a Vim that functions as
server was received |server2client()|. The
diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua
index d0a3f38c6b..d002aaae43 100644
--- a/src/nvim/auevents.lua
+++ b/src/nvim/auevents.lua
@@ -34,6 +34,7 @@ return {
'CursorMovedI', -- cursor was moved in Insert mode
'DirChanged', -- directory changed
'EncodingChanged', -- after changing the 'encoding' option
+ 'ExitPre', -- before exiting
'FileAppendCmd', -- append to a file using command
'FileAppendPost', -- after appending to a file
'FileAppendPre', -- before appending to a file
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index f5822535ba..6e695a8897 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -1209,7 +1209,7 @@ int autowrite(buf_T *buf, int forceit)
return r;
}
-/// flush all buffers, except the ones that are readonly
+/// Flush all buffers, except the ones that are readonly or are never written.
void autowrite_all(void)
{
if (!(p_aw || p_awa) || !p_write) {
@@ -1217,7 +1217,7 @@ void autowrite_all(void)
}
FOR_ALL_BUFFERS(buf) {
- if (bufIsChanged(buf) && !buf->b_p_ro) {
+ if (bufIsChanged(buf) && !buf->b_p_ro && !bt_dontwrite(buf)) {
bufref_T bufref;
set_bufref(&bufref, buf);
(void)buf_write_all(buf, false);
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index abefca231c..03f1446265 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -5964,9 +5964,35 @@ void not_exiting(void)
exiting = FALSE;
}
-/*
- * ":quit": quit current window, quit Vim if the last window is closed.
- */
+static bool before_quit_autocmds(win_T *wp, bool quit_all, int forceit)
+{
+ apply_autocmds(EVENT_QUITPRE, NULL, NULL, false, wp->w_buffer);
+
+ // Bail out when autocommands closed the window.
+ // Refuse to quit when the buffer in the last window is being closed (can
+ // only happen in autocommands).
+ if (!win_valid(wp)
+ || curbuf_locked()
+ || (wp->w_buffer->b_nwindows == 1 && wp->w_buffer->b_locked > 0)) {
+ return true;
+ }
+
+ if (quit_all
+ || (check_more(false, forceit) == OK && only_one_window())) {
+ apply_autocmds(EVENT_EXITPRE, NULL, NULL, false, curbuf);
+ // Refuse to quit when locked or when the buffer in the last window is
+ // being closed (can only happen in autocommands).
+ if (curbuf_locked()
+ || (curbuf->b_nwindows == 1 && curbuf->b_locked > 0)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// ":quit": quit current window, quit Vim if the last window is closed.
+// ":{nr}quit": quit window {nr}
static void ex_quit(exarg_T *eap)
{
if (cmdwin_type != 0) {
@@ -5996,11 +6022,9 @@ static void ex_quit(exarg_T *eap)
if (curbuf_locked()) {
return;
}
- apply_autocmds(EVENT_QUITPRE, NULL, NULL, false, wp->w_buffer);
- // Refuse to quit when locked or when the buffer in the last window is
- // being closed (can only happen in autocommands).
- if (!win_valid(wp)
- || (wp->w_buffer->b_nwindows == 1 && wp->w_buffer->b_locked > 0)) {
+
+ // Trigger QuitPre and maybe ExitPre
+ if (before_quit_autocmds(wp, false, eap->forceit)) {
return;
}
@@ -6025,6 +6049,7 @@ static void ex_quit(exarg_T *eap)
if (only_one_window() && (ONE_WINDOW || eap->addr_count == 0)) {
getout(0);
}
+ not_exiting();
// close window; may free buffer
win_close(wp, !buf_hide(wp->w_buffer) || eap->forceit);
}
@@ -6057,10 +6082,8 @@ static void ex_quit_all(exarg_T *eap)
text_locked_msg();
return;
}
- apply_autocmds(EVENT_QUITPRE, NULL, NULL, false, curbuf);
- // Refuse to quit when locked or when the buffer in the last window is
- // being closed (can only happen in autocommands).
- if (curbuf_locked() || (curbuf->b_nwindows == 1 && curbuf->b_locked > 0)) {
+
+ if (before_quit_autocmds(curwin, true, eap->forceit)) {
return;
}
@@ -6346,9 +6369,7 @@ static void ex_stop(exarg_T *eap)
}
}
-/*
- * ":exit", ":xit" and ":wq": Write file and exit Vim.
- */
+// ":exit", ":xit" and ":wq": Write file and quite the current window.
static void ex_exit(exarg_T *eap)
{
if (cmdwin_type != 0) {
@@ -6360,10 +6381,8 @@ static void ex_exit(exarg_T *eap)
text_locked_msg();
return;
}
- apply_autocmds(EVENT_QUITPRE, NULL, NULL, false, curbuf);
- // Refuse to quit when locked or when the buffer in the last window is
- // being closed (can only happen in autocommands).
- if (curbuf_locked() || (curbuf->b_nwindows == 1 && curbuf->b_locked > 0)) {
+
+ if (before_quit_autocmds(curwin, false, eap->forceit)) {
return;
}
@@ -6382,6 +6401,7 @@ static void ex_exit(exarg_T *eap)
// quit last window, exit Vim
getout(0);
}
+ not_exiting();
// Quit current window, may free the buffer.
win_close(curwin, !buf_hide(curwin->w_buffer));
}
diff --git a/src/nvim/testdir/test_exit.vim b/src/nvim/testdir/test_exit.vim
new file mode 100644
index 0000000000..8f02fd29e3
--- /dev/null
+++ b/src/nvim/testdir/test_exit.vim
@@ -0,0 +1,57 @@
+" Tests for exiting Vim.
+
+source shared.vim
+
+func Test_exiting()
+ let after = [
+ \ 'au QuitPre * call writefile(["QuitPre"], "Xtestout")',
+ \ 'au ExitPre * call writefile(["ExitPre"], "Xtestout", "a")',
+ \ 'quit',
+ \ ]
+ if RunVim([], after, '')
+ call assert_equal(['QuitPre', 'ExitPre'], readfile('Xtestout'))
+ endif
+ call delete('Xtestout')
+
+ let after = [
+ \ 'au QuitPre * call writefile(["QuitPre"], "Xtestout")',
+ \ 'au ExitPre * call writefile(["ExitPre"], "Xtestout", "a")',
+ \ 'help',
+ \ 'wincmd w',
+ \ 'quit',
+ \ ]
+ if RunVim([], after, '')
+ call assert_equal(['QuitPre', 'ExitPre'], readfile('Xtestout'))
+ endif
+ call delete('Xtestout')
+
+ let after = [
+ \ 'au QuitPre * call writefile(["QuitPre"], "Xtestout")',
+ \ 'au ExitPre * call writefile(["ExitPre"], "Xtestout", "a")',
+ \ 'split',
+ \ 'new',
+ \ 'qall',
+ \ ]
+ if RunVim([], after, '')
+ call assert_equal(['QuitPre', 'ExitPre'], readfile('Xtestout'))
+ endif
+ call delete('Xtestout')
+
+ let after = [
+ \ 'au QuitPre * call writefile(["QuitPre"], "Xtestout", "a")',
+ \ 'au ExitPre * call writefile(["ExitPre"], "Xtestout", "a")',
+ \ 'augroup nasty',
+ \ ' au ExitPre * split',
+ \ 'augroup END',
+ \ 'quit',
+ \ 'augroup nasty',
+ \ ' au! ExitPre',
+ \ 'augroup END',
+ \ 'quit',
+ \ ]
+ if RunVim([], after, '')
+ call assert_equal(['QuitPre', 'ExitPre', 'QuitPre', 'ExitPre'],
+ \ readfile('Xtestout'))
+ endif
+ call delete('Xtestout')
+endfunc
diff --git a/src/nvim/testdir/test_writefile.vim b/src/nvim/testdir/test_writefile.vim
index 8b031646b5..06f9d03554 100644
--- a/src/nvim/testdir/test_writefile.vim
+++ b/src/nvim/testdir/test_writefile.vim
@@ -31,3 +31,85 @@ func Test_writefile_fails_gently()
call assert_fails('call writefile([], [])', 'E730:')
endfunc
+
+func SetFlag(timer)
+ let g:flag = 1
+endfunc
+
+func Test_write_quit_split()
+ " Prevent exiting by splitting window on file write.
+ augroup testgroup
+ autocmd BufWritePre * split
+ augroup END
+ e! Xfile
+ call setline(1, 'nothing')
+ wq
+
+ if has('timers')
+ " timer will not run if "exiting" is still set
+ let g:flag = 0
+ call timer_start(1, 'SetFlag')
+ sleep 50m
+ call assert_equal(1, g:flag)
+ unlet g:flag
+ endif
+ au! testgroup
+ bwipe Xfile
+ call delete('Xfile')
+endfunc
+
+func Test_nowrite_quit_split()
+ " Prevent exiting by opening a help window.
+ e! Xfile
+ help
+ wincmd w
+ exe winnr() . 'q'
+
+ if has('timers')
+ " timer will not run if "exiting" is still set
+ let g:flag = 0
+ call timer_start(1, 'SetFlag')
+ sleep 50m
+ call assert_equal(1, g:flag)
+ unlet g:flag
+ endif
+ bwipe Xfile
+endfunc
+
+func Test_writefile_autowrite()
+ set autowrite
+ new
+ next Xa Xb Xc
+ call setline(1, 'aaa')
+ next
+ call assert_equal(['aaa'], readfile('Xa'))
+ call setline(1, 'bbb')
+ call assert_fails('edit XX')
+ call assert_false(filereadable('Xb'))
+
+ set autowriteall
+ edit XX
+ call assert_equal(['bbb'], readfile('Xb'))
+
+ bwipe!
+ call delete('Xa')
+ call delete('Xb')
+ set noautowrite
+endfunc
+
+func Test_writefile_autowrite_nowrite()
+ set autowrite
+ new
+ next Xa Xb Xc
+ set buftype=nowrite
+ call setline(1, 'aaa')
+ let buf = bufnr('%')
+ " buffer contents silently lost
+ edit XX
+ call assert_false(filereadable('Xa'))
+ rewind
+ call assert_equal('', getline(1))
+
+ bwipe!
+ set noautowrite
+endfunc