Patch 9.0.0708
Problem: :confirm does not work properly for a terminal buffer.
Solution: Handle :confirm for a terminal buffer differently. (Yee Cheng
Chin, closes #11312)
Files: runtime/menu.vim, src/buffer.c, src/ex_cmds2.c, src/ex_docmd.c,
src/proto/
terminal.pro, src/terminal.c,
src/testdir/test_terminal.vim
*** ../vim-9.0.0707/runtime/menu.vim 2022-03-02 21:25:35.000000000 +0000
--- runtime/menu.vim 2022-10-09 18:42:05.491666766 +0100
***************
*** 129,134 ****
--- 129,140 ----
\ else <Bar>
\ confirm close <Bar>
\ endif<CR>
+ tln <silent> 10.330 &File.&Close<Tab>:close
+ \ <C-W>:if winheight(2) < 0 && tabpagewinnr(2) == 0 <Bar>
+ \ confirm enew <Bar>
+ \ else <Bar>
+ \ confirm close <Bar>
+ \ endif<CR>
an 10.335 &File.-SEP1- <Nop>
an <silent> 10.340 &File.&Save<Tab>:w :if expand("%") == ""<Bar>browse confirm w<Bar>else<Bar>confirm w<Bar>endif<CR>
an 10.350 &File.Save\ &As\.\.\.<Tab>:sav :browse confirm saveas<CR>
*** ../vim-9.0.0707/src/buffer.c 2022-10-01 19:43:48.602494034 +0100
--- src/buffer.c 2022-10-09 18:42:05.491666766 +0100
***************
*** 49,54 ****
--- 49,55 ----
static void free_buffer(buf_T *);
static void free_buffer_stuff(buf_T *buf, int free_options);
static int bt_nofileread(buf_T *buf);
+ static void no_write_message_buf(buf_T *buf);
#ifdef UNIX
# define dev_T dev_t
***************
*** 1367,1387 ****
#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
if ((p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) && p_write)
{
! dialog_changed(buf, FALSE);
! if (!bufref_valid(&bufref))
! // Autocommand deleted buffer, oops! It's not changed
! // now.
! return FAIL;
! // If it's still changed fail silently, the dialog already
! // mentioned why it fails.
! if (bufIsChanged(buf))
! return FAIL;
}
else
#endif
{
! semsg(_(e_no_write_since_last_change_for_buffer_nr_add_bang_to_override),
! buf->b_fnum);
return FAIL;
}
}
--- 1368,1397 ----
#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
if ((p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) && p_write)
{
! # ifdef FEAT_TERMINAL
! if (term_job_running(buf->b_term))
! {
! if (term_confirm_stop(buf) == FAIL)
! return FAIL;
! }
! else
! # endif
! {
! dialog_changed(buf, FALSE);
! if (!bufref_valid(&bufref))
! // Autocommand deleted buffer, oops! It's not changed
! // now.
! return FAIL;
! // If it's still changed fail silently, the dialog already
! // mentioned why it fails.
! if (bufIsChanged(buf))
! return FAIL;
! }
}
else
#endif
{
! no_write_message_buf(buf);
return FAIL;
}
}
***************
*** 1552,1566 ****
#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
if ((p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) && p_write)
{
! bufref_T bufref;
! set_bufref(&bufref, buf);
! dialog_changed(curbuf, FALSE);
! if (!bufref_valid(&bufref))
! // Autocommand deleted buffer, oops!
! return FAIL;
}
! if (bufIsChanged(curbuf))
#endif
{
no_write_message();
--- 1562,1595 ----
#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
if ((p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) && p_write)
{
! # ifdef FEAT_TERMINAL
! if (term_job_running(curbuf->b_term))
! {
! if (term_confirm_stop(curbuf) == FAIL)
! return FAIL;
! // Manually kill the terminal here because this command will
! // hide it otherwise.
! free_terminal(curbuf);
! }
! else
! # endif
! {
! bufref_T bufref;
! set_bufref(&bufref, buf);
! dialog_changed(curbuf, FALSE);
! if (!bufref_valid(&bufref))
! // Autocommand deleted buffer, oops!
! return FAIL;
!
! if (bufIsChanged(curbuf))
! {
! no_write_message();
! return FAIL;
! }
! }
}
! else
#endif
{
no_write_message();
***************
*** 1940,1945 ****
--- 1969,1986 ----
}
#endif
+ static void
+ no_write_message_buf(buf_T *buf UNUSED)
+ {
+ #ifdef FEAT_TERMINAL
+ if (term_job_running(buf->b_term))
+ emsg(_(e_job_still_running_add_bang_to_end_the_job));
+ else
+ #endif
+ semsg(_(e_no_write_since_last_change_for_buffer_nr_add_bang_to_override),
+ buf->b_fnum);
+ }
+
void
no_write_message(void)
{
***************
*** 5751,5758 ****
#endif
/*
! * Return TRUE if "buf" is a "nowrite", "nofile", "terminal" or "prompt"
! * buffer.
*/
int
bt_dontwrite(buf_T *buf)
--- 5792,5799 ----
#endif
/*
! * Return TRUE if "buf" is a "nowrite", "nofile", "terminal", "prompt", or
! * "popup" buffer.
*/
int
bt_dontwrite(buf_T *buf)
*** ../vim-9.0.0707/src/ex_cmds2.c 2022-08-29 15:06:46.716715543 +0100
--- src/ex_cmds2.c 2022-10-09 18:42:05.491666766 +0100
***************
*** 86,91 ****
--- 86,98 ----
#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
if ((p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) && p_write)
{
+ # ifdef FEAT_TERMINAL
+ if (term_job_running(buf->b_term))
+ {
+ return term_confirm_stop(buf) == FAIL;
+ }
+ # endif
+
buf_T *buf2;
int count = 0;
***************
*** 198,203 ****
--- 205,211 ----
|| (cmdmod.cmod_flags & CMOD_BROWSE)
#endif
)
+ && !bt_dontwrite(buf2)
&& !buf2->b_p_ro)
{
bufref_T bufref;
*** ../vim-9.0.0707/src/ex_docmd.c 2022-10-07 18:51:20.139679475 +0100
--- src/ex_docmd.c 2022-10-09 18:42:05.491666766 +0100
***************
*** 6061,6073 ****
#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
if ((p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) && p_write)
{
! bufref_T bufref;
! set_bufref(&bufref, buf);
! dialog_changed(buf, FALSE);
! if (bufref_valid(&bufref) && bufIsChanged(buf))
! return;
! need_hide = FALSE;
}
else
#endif
--- 6061,6087 ----
#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
if ((p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) && p_write)
{
! # ifdef FEAT_TERMINAL
! if (term_job_running(buf->b_term))
! {
! if (term_confirm_stop(buf) == FAIL)
! return;
! // Manually kill the terminal here because this command will
! // hide it otherwise.
! free_terminal(buf);
! need_hide = FALSE;
! }
! else
! # endif
! {
! bufref_T bufref;
! set_bufref(&bufref, buf);
! dialog_changed(buf, FALSE);
! if (bufref_valid(&bufref) && bufIsChanged(buf))
! return;
! need_hide = FALSE;
! }
}
else
#endif
*** ../vim-9.0.0707/src/proto/
terminal.pro 2022-06-27 23:15:25.000000000 +0100
--- src/proto/
terminal.pro 2022-10-09 18:42:05.491666766 +0100
***************
*** 10,15 ****
--- 10,16 ----
int term_job_running(term_T *term);
int term_job_running_not_none(term_T *term);
int term_none_open(term_T *term);
+ int term_confirm_stop(buf_T *buf);
int term_try_stop_job(buf_T *buf);
int term_check_timers(int next_due_arg, proftime_T *now);
int term_in_normal_mode(void);
*** ../vim-9.0.0707/src/terminal.c 2022-10-04 16:23:39.018042176 +0100
--- src/terminal.c 2022-10-09 18:42:05.495666761 +0100
***************
*** 1651,1656 ****
--- 1651,1675 ----
&& term->tl_job->jv_channel->ch_keep_open;
}
+ //
+ // Used to confirm whether we would like to kill a terminal.
+ // Return OK when the user confirms to kill it.
+ // Return FAIL if the user selects otherwise.
+ //
+ int
+ term_confirm_stop(buf_T *buf)
+ {
+ char_u buff[DIALOG_MSG_SIZE];
+ int ret;
+
+ dialog_msg(buff, _("Kill job in \"%s\"?"), buf_get_fname(buf));
+ ret = vim_dialog_yesno(VIM_QUESTION, NULL, buff, 1);
+ if (ret == VIM_YES)
+ return OK;
+ else
+ return FAIL;
+ }
+
/*
* Used when exiting: kill the job in "buf" if so desired.
* Return OK when the job finished.
***************
*** 1666,1679 ****
if ((how == NULL || *how == NUL)
&& (p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)))
{
! char_u buff[DIALOG_MSG_SIZE];
! int ret;
!
! dialog_msg(buff, _("Kill job in \"%s\"?"), buf_get_fname(buf));
! ret = vim_dialog_yesnocancel(VIM_QUESTION, NULL, buff, 1);
! if (ret == VIM_YES)
how = "kill";
! else if (ret == VIM_CANCEL)
return FAIL;
}
#endif
--- 1685,1693 ----
if ((how == NULL || *how == NUL)
&& (p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)))
{
! if (term_confirm_stop(buf) == OK)
how = "kill";
! else
return FAIL;
}
#endif
*** ../vim-9.0.0707/src/testdir/test_terminal.vim 2022-09-22 12:57:02.288750572 +0100
--- src/testdir/test_terminal.vim 2022-10-09 18:42:05.495666761 +0100
***************
*** 98,104 ****
func Test_terminal_wipe_buffer()
let buf = Run_shell_in_terminal({})
! call assert_fails(buf . 'bwipe', 'E89:')
exe buf . 'bwipe!'
call WaitForAssert({-> assert_equal('dead', job_status(g:job))})
call assert_equal("", bufname(buf))
--- 98,104 ----
func Test_terminal_wipe_buffer()
let buf = Run_shell_in_terminal({})
! call assert_fails(buf . 'bwipe', 'E948:')
exe buf . 'bwipe!'
call WaitForAssert({-> assert_equal('dead', job_status(g:job))})
call assert_equal("", bufname(buf))
***************
*** 106,111 ****
--- 106,231 ----
unlet g:job
endfunc
+ " Test that using ':confirm bwipe' on terminal works
+ func Test_terminal_confirm_wipe_buffer()
+ CheckUnix
+ CheckNotGui
+ CheckFeature dialog_con
+ let buf = Run_shell_in_terminal({})
+ call assert_fails(buf . 'bwipe', 'E948:')
+ call feedkeys('n', 'L')
+ call assert_fails('confirm ' .. buf .. 'bwipe', 'E517:')
+ call assert_equal(buf, bufnr())
+ call assert_equal(1, &modified)
+ call feedkeys('y', 'L')
+ exe 'confirm ' .. buf .. 'bwipe'
+ call assert_notequal(buf, bufnr())
+ call WaitForAssert({-> assert_equal('dead', job_status(g:job))})
+ call assert_equal("", bufname(buf))
+
+ unlet g:job
+ endfunc
+
+ " Test that using :b! will hide the terminal
+ func Test_terminal_goto_buffer()
+ let buf_mod = bufnr()
+ let buf_term = Run_shell_in_terminal({})
+ call assert_equal(buf_term, bufnr())
+ call assert_fails(buf_mod . 'b', 'E948:')
+ exe buf_mod . 'b!'
+ call assert_equal(buf_mod, bufnr())
+ call assert_equal('run', job_status(g:job))
+ call assert_notequal('', bufname(buf_term))
+ exec buf_mod .. 'bwipe!'
+ exec buf_term .. 'bwipe!'
+
+ unlet g:job
+ endfunc
+
+ " Test that using ':confirm :b' will kill terminal
+ func Test_terminal_confirm_goto_buffer()
+ CheckUnix
+ CheckNotGui
+ CheckFeature dialog_con
+ let buf_mod = bufnr()
+ let buf_term = Run_shell_in_terminal({})
+ call feedkeys('n', 'L')
+ exe 'confirm ' .. buf_mod .. 'b'
+ call assert_equal(buf_term, bufnr())
+ call feedkeys('y', 'L')
+ exec 'confirm ' .. buf_mod .. 'b'
+ call assert_equal(buf_mod, bufnr())
+ call WaitForAssert({-> assert_equal('dead', job_status(g:job))})
+ call assert_equal("", bufname(buf_term))
+ exec buf_mod .. 'bwipe!'
+
+ unlet g:job
+ endfunc
+
+ " Test that using :close! will hide the terminal
+ func Test_terminal_close_win()
+ let buf = Run_shell_in_terminal({})
+ call assert_equal(buf, bufnr())
+ call assert_fails('close', 'E948:')
+ close!
+ call assert_notequal(buf, bufnr())
+ call assert_equal('run', job_status(g:job))
+ call assert_notequal('', bufname(buf))
+ exec buf .. 'bwipe!'
+
+ unlet g:job
+ endfunc
+
+ " Test that using ':confirm close' will kill terminal
+ func Test_terminal_confirm_close_win()
+ CheckUnix
+ CheckNotGui
+ CheckFeature dialog_con
+ let buf = Run_shell_in_terminal({})
+ call feedkeys('n', 'L')
+ confirm close
+ call assert_equal(buf, bufnr())
+ call feedkeys('y', 'L')
+ confirm close
+ call assert_notequal(buf, bufnr())
+ call WaitForAssert({-> assert_equal('dead', job_status(g:job))})
+ call assert_equal("", bufname(buf))
+
+ unlet g:job
+ endfunc
+
+ " Test that using :quit! will kill the terminal
+ func Test_terminal_quit()
+ let buf = Run_shell_in_terminal({})
+ call assert_equal(buf, bufnr())
+ call assert_fails('quit', 'E948:')
+ quit!
+ call assert_notequal(buf, bufnr())
+ call WaitForAssert({-> assert_equal('dead', job_status(g:job))})
+ exec buf .. 'bwipe!'
+
+ unlet g:job
+ endfunc
+
+ " Test that using ':confirm quit' will kill terminal
+ func Test_terminal_confirm_quit()
+ CheckUnix
+ CheckNotGui
+ CheckFeature dialog_con
+ let buf = Run_shell_in_terminal({})
+ call feedkeys('n', 'L')
+ confirm quit
+ call assert_equal(buf, bufnr())
+ call feedkeys('y', 'L')
+ confirm quit
+ call assert_notequal(buf, bufnr())
+ call WaitForAssert({-> assert_equal('dead', job_status(g:job))})
+
+ unlet g:job
+ endfunc
+
+ " Test :q or :next
+
func Test_terminal_split_quit()
let buf = Run_shell_in_terminal({})
split
***************
*** 707,713 ****
func Test_terminal_list_args()
let buf = term_start([&shell, &shellcmdflag, 'echo "123"'])
! call assert_fails(buf . 'bwipe', 'E89:')
exe buf . 'bwipe!'
call assert_equal("", bufname(buf))
endfunction
--- 827,833 ----
func Test_terminal_list_args()
let buf = term_start([&shell, &shellcmdflag, 'echo "123"'])
! call assert_fails(buf . 'bwipe', 'E948:')
exe buf . 'bwipe!'
call assert_equal("", bufname(buf))
endfunction
***************
*** 1235,1241 ****
" make Vim exit, it will prompt to kill the shell
call term_sendkeys(buf, "\<C-W>:confirm qall\<CR>")
! call WaitForAssert({-> assert_match('ancel:', term_getline(buf, 20))})
call term_sendkeys(buf, "y")
call WaitForAssert({-> assert_equal('finished', term_getstatus(buf))})
--- 1355,1361 ----
" make Vim exit, it will prompt to kill the shell
call term_sendkeys(buf, "\<C-W>:confirm qall\<CR>")
! call WaitForAssert({-> assert_match('\[Y\]es, (N)o:', term_getline(buf, 20))})
call term_sendkeys(buf, "y")
call WaitForAssert({-> assert_equal('finished', term_getstatus(buf))})
*** ../vim-9.0.0707/src/version.c 2022-10-09 17:19:04.445451418 +0100
--- src/version.c 2022-10-09 18:49:46.120290363 +0100
***************
*** 701,702 ****
--- 701,704 ----
{ /* Add new patch number below this line */
+ /**/
+ 708,
/**/
--
I still remember when I gave up Smoking, Drinking and Sex. It was the
most *horrifying* hour of my life!
/// Bram Moolenaar -- Br...@Moolenaar.net --
http://www.Moolenaar.net \\\
/// \\\
\\\ sponsor Vim, vote for features --
http://www.Vim.org/sponsor/ ///
\\\ help me help AIDS victims --
http://ICCF-Holland.org ///