# HG changeset patch # User Bram Moolenaar # Date 1665338404 -7200 # Node ID 58592b6af4e2de23efa6211da819bf52f6f88faa # Parent 7497bddb55b337c5609a047fc4e1dd47ed20748f patch 9.0.0708: :confirm does not work properly for a terminal buffer Commit: https://github.com/vim/vim/commit/15b314ffbb93f934b72cb71aa8f881caea026256 Author: Yee Cheng Chin Date: Sun Oct 9 18:53:32 2022 +0100 patch 9.0.0708: :confirm does not work properly for a terminal buffer Problem: :confirm does not work properly for a terminal buffer. Solution: Handle :confirm for a terminal buffer differently. (Yee Cheng Chin, closes #11312) diff --git a/runtime/menu.vim b/runtime/menu.vim --- a/runtime/menu.vim +++ b/runtime/menu.vim @@ -129,6 +129,12 @@ an 10.330 &File.&Close:clo \ else \ confirm close \ endif +tln 10.330 &File.&Close:close + \ :if winheight(2) < 0 && tabpagewinnr(2) == 0 + \ confirm enew + \ else + \ confirm close + \ endif an 10.335 &File.-SEP1- an 10.340 &File.&Save:w :if expand("%") == ""browse confirm welseconfirm wendif an 10.350 &File.Save\ &As\.\.\.:sav :browse confirm saveas diff --git a/src/buffer.c b/src/buffer.c --- a/src/buffer.c +++ b/src/buffer.c @@ -49,6 +49,7 @@ static int append_arg_number(win_T *wp, 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,21 +1368,30 @@ do_buffer_ext( #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; +# 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 { - semsg(_(e_no_write_since_last_change_for_buffer_nr_add_bang_to_override), - buf->b_fnum); + no_write_message_buf(buf); return FAIL; } } @@ -1552,15 +1562,34 @@ do_buffer_ext( #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; +# 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; + } + } } - if (bufIsChanged(curbuf)) + else #endif { no_write_message(); @@ -1940,6 +1969,18 @@ do_autochdir(void) } #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,8 +5792,8 @@ bt_nofile(buf_T *buf) #endif /* - * Return TRUE if "buf" is a "nowrite", "nofile", "terminal" or "prompt" - * buffer. + * Return TRUE if "buf" is a "nowrite", "nofile", "terminal", "prompt", or + * "popup" buffer. */ int bt_dontwrite(buf_T *buf) diff --git a/src/ex_cmds2.c b/src/ex_cmds2.c --- a/src/ex_cmds2.c +++ b/src/ex_cmds2.c @@ -86,6 +86,13 @@ check_changed(buf_T *buf, int flags) #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,6 +205,7 @@ dialog_changed( || (cmdmod.cmod_flags & CMOD_BROWSE) #endif ) + && !bt_dontwrite(buf2) && !buf2->b_p_ro) { bufref_T bufref; diff --git a/src/ex_docmd.c b/src/ex_docmd.c --- a/src/ex_docmd.c +++ b/src/ex_docmd.c @@ -6061,13 +6061,27 @@ ex_win_close( #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; +# 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 diff --git a/src/proto/terminal.pro b/src/proto/terminal.pro --- a/src/proto/terminal.pro +++ b/src/proto/terminal.pro @@ -10,6 +10,7 @@ void write_to_term(buf_T *buffer, char_u 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); diff --git a/src/terminal.c b/src/terminal.c --- a/src/terminal.c +++ b/src/terminal.c @@ -1651,6 +1651,25 @@ term_none_open(term_T *term) && 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,14 +1685,9 @@ term_try_stop_job(buf_T *buf) 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) + if (term_confirm_stop(buf) == OK) how = "kill"; - else if (ret == VIM_CANCEL) + else return FAIL; } #endif diff --git a/src/testdir/test_terminal.vim b/src/testdir/test_terminal.vim --- a/src/testdir/test_terminal.vim +++ b/src/testdir/test_terminal.vim @@ -98,7 +98,7 @@ endfunc func Test_terminal_wipe_buffer() let buf = Run_shell_in_terminal({}) - call assert_fails(buf . 'bwipe', 'E89:') + call assert_fails(buf . 'bwipe', 'E948:') exe buf . 'bwipe!' call WaitForAssert({-> assert_equal('dead', job_status(g:job))}) call assert_equal("", bufname(buf)) @@ -106,6 +106,126 @@ func Test_terminal_wipe_buffer() 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,7 +827,7 @@ endfunc func Test_terminal_list_args() let buf = term_start([&shell, &shellcmdflag, 'echo "123"']) - call assert_fails(buf . 'bwipe', 'E89:') + call assert_fails(buf . 'bwipe', 'E948:') exe buf . 'bwipe!' call assert_equal("", bufname(buf)) endfunction @@ -1235,7 +1355,7 @@ func Test_terminal_qall_prompt() " make Vim exit, it will prompt to kill the shell call term_sendkeys(buf, "\:confirm qall\") - call WaitForAssert({-> assert_match('ancel:', term_getline(buf, 20))}) + 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))}) diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -700,6 +700,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 708, +/**/ 707, /**/ 706,