summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2018-03-10 20:28:12 +0100
committerBram Moolenaar <Bram@vim.org>2018-03-10 20:28:12 +0100
commit25cdd9c33b21ddbd31321c075873bb225450d2d2 (patch)
tree380538cdf0abb9b7f3777c57ada6930930c9e036 /src
parentb5b7562475ad032a174b893286172de0d2c157cd (diff)
downloadvim-git-25cdd9c33b21ddbd31321c075873bb225450d2d2.tar.gz
patch 8.0.1593: :qall never exits with an active terminal windowv8.0.1593
Problem: :qall never exits with an active terminal window. Solution: Add a way to kill a job in a terminal window.
Diffstat (limited to 'src')
-rw-r--r--src/channel.c23
-rw-r--r--src/evalfunc.c1
-rw-r--r--src/ex_cmds2.c20
-rw-r--r--src/proto/terminal.pro4
-rw-r--r--src/structs.h4
-rw-r--r--src/terminal.c132
-rw-r--r--src/testdir/test_terminal.vim46
-rw-r--r--src/version.c2
8 files changed, 197 insertions, 35 deletions
diff --git a/src/channel.c b/src/channel.c
index 7c574427a..1f41445c6 100644
--- a/src/channel.c
+++ b/src/channel.c
@@ -4746,50 +4746,57 @@ get_job_options(typval_T *tv, jobopt_T *opt, int supported, int supported2)
{
if (!(supported2 & JO2_TERM_ROWS))
break;
- opt->jo_set |= JO2_TERM_ROWS;
+ opt->jo_set2 |= JO2_TERM_ROWS;
opt->jo_term_rows = get_tv_number(item);
}
else if (STRCMP(hi->hi_key, "term_cols") == 0)
{
if (!(supported2 & JO2_TERM_COLS))
break;
- opt->jo_set |= JO2_TERM_COLS;
+ opt->jo_set2 |= JO2_TERM_COLS;
opt->jo_term_cols = get_tv_number(item);
}
else if (STRCMP(hi->hi_key, "vertical") == 0)
{
if (!(supported2 & JO2_VERTICAL))
break;
- opt->jo_set |= JO2_VERTICAL;
+ opt->jo_set2 |= JO2_VERTICAL;
opt->jo_vertical = get_tv_number(item);
}
else if (STRCMP(hi->hi_key, "curwin") == 0)
{
if (!(supported2 & JO2_CURWIN))
break;
- opt->jo_set |= JO2_CURWIN;
+ opt->jo_set2 |= JO2_CURWIN;
opt->jo_curwin = get_tv_number(item);
}
else if (STRCMP(hi->hi_key, "hidden") == 0)
{
if (!(supported2 & JO2_HIDDEN))
break;
- opt->jo_set |= JO2_HIDDEN;
+ opt->jo_set2 |= JO2_HIDDEN;
opt->jo_hidden = get_tv_number(item);
}
else if (STRCMP(hi->hi_key, "norestore") == 0)
{
if (!(supported2 & JO2_NORESTORE))
break;
- opt->jo_set |= JO2_NORESTORE;
+ opt->jo_set2 |= JO2_NORESTORE;
opt->jo_term_norestore = get_tv_number(item);
}
+ else if (STRCMP(hi->hi_key, "term_kill") == 0)
+ {
+ if (!(supported2 & JO2_TERM_KILL))
+ break;
+ opt->jo_set2 |= JO2_TERM_KILL;
+ opt->jo_term_kill = get_tv_string_chk(item);
+ }
#endif
else if (STRCMP(hi->hi_key, "env") == 0)
{
if (!(supported2 & JO2_ENV))
break;
- opt->jo_set |= JO2_ENV;
+ opt->jo_set2 |= JO2_ENV;
opt->jo_env = item->vval.v_dict;
++item->vval.v_dict->dv_refcount;
}
@@ -4803,7 +4810,7 @@ get_job_options(typval_T *tv, jobopt_T *opt, int supported, int supported2)
EMSG2(_(e_invargval), "cwd");
return FAIL;
}
- opt->jo_set |= JO2_CWD;
+ opt->jo_set2 |= JO2_CWD;
}
else if (STRCMP(hi->hi_key, "waittime") == 0)
{
diff --git a/src/evalfunc.c b/src/evalfunc.c
index aa0ec6073..1620efe50 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -867,6 +867,7 @@ static struct fst
{"term_list", 0, 0, f_term_list},
{"term_scrape", 2, 2, f_term_scrape},
{"term_sendkeys", 2, 2, f_term_sendkeys},
+ {"term_setkill", 2, 2, f_term_setkill},
{"term_setrestore", 2, 2, f_term_setrestore},
{"term_start", 1, 2, f_term_start},
{"term_wait", 1, 2, f_term_wait},
diff --git a/src/ex_cmds2.c b/src/ex_cmds2.c
index 424bac8c7..ae4ce337d 100644
--- a/src/ex_cmds2.c
+++ b/src/ex_cmds2.c
@@ -2254,7 +2254,7 @@ add_bufnum(int *bufnrs, int *bufnump, int nr)
/*
* Return TRUE if any buffer was changed and cannot be abandoned.
* That changed buffer becomes the current buffer.
- * When "unload" is true the current buffer is unloaded instead of making it
+ * When "unload" is TRUE the current buffer is unloaded instead of making it
* hidden. This is used for ":q!".
*/
int
@@ -2272,6 +2272,7 @@ check_changed_any(
tabpage_T *tp;
win_T *wp;
+ /* Make a list of all buffers, with the most important ones first. */
FOR_ALL_BUFFERS(buf)
++bufcount;
@@ -2284,17 +2285,19 @@ check_changed_any(
/* curbuf */
bufnrs[bufnum++] = curbuf->b_fnum;
- /* buf in curtab */
+
+ /* buffers in current tab */
FOR_ALL_WINDOWS(wp)
if (wp->w_buffer != curbuf)
add_bufnum(bufnrs, &bufnum, wp->w_buffer->b_fnum);
- /* buf in other tab */
+ /* buffers in other tabs */
FOR_ALL_TABPAGES(tp)
if (tp != curtab)
for (wp = tp->tp_firstwin; wp != NULL; wp = wp->w_next)
add_bufnum(bufnrs, &bufnum, wp->w_buffer->b_fnum);
- /* any other buf */
+
+ /* any other buffer */
FOR_ALL_BUFFERS(buf)
add_bufnum(bufnrs, &bufnum, buf->b_fnum);
@@ -2308,6 +2311,14 @@ check_changed_any(
bufref_T bufref;
set_bufref(&bufref, buf);
+#ifdef FEAT_TERMINAL
+ if (term_job_running(buf->b_term))
+ {
+ if (term_try_stop_job(buf) == FAIL)
+ break;
+ }
+ else
+#endif
/* Try auto-writing the buffer. If this fails but the buffer no
* longer exists it's not changed, that's OK. */
if (check_changed(buf, (p_awa ? CCGD_AW : 0)
@@ -2320,6 +2331,7 @@ check_changed_any(
if (i >= bufnum)
goto theend;
+ /* Get here if "buf" cannot be abandoned. */
ret = TRUE;
exiting = FALSE;
#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
diff --git a/src/proto/terminal.pro b/src/proto/terminal.pro
index aefa40d84..f7d06c050 100644
--- a/src/proto/terminal.pro
+++ b/src/proto/terminal.pro
@@ -2,11 +2,11 @@
void ex_terminal(exarg_T *eap);
int term_write_session(FILE *fd, win_T *wp);
int term_should_restore(buf_T *buf);
-void f_term_setrestore(typval_T *argvars, typval_T *rettv);
void free_terminal(buf_T *buf);
void write_to_term(buf_T *buffer, char_u *msg, channel_T *channel);
int term_job_running(term_T *term);
int term_none_open(term_T *term);
+int term_try_stop_job(buf_T *buf);
int term_in_normal_mode(void);
void term_enter_job_mode(void);
int send_keys_to_term(term_T *term, int c, int typed);
@@ -41,6 +41,8 @@ void f_term_gettty(typval_T *argvars, typval_T *rettv);
void f_term_list(typval_T *argvars, typval_T *rettv);
void f_term_scrape(typval_T *argvars, typval_T *rettv);
void f_term_sendkeys(typval_T *argvars, typval_T *rettv);
+void f_term_setrestore(typval_T *argvars, typval_T *rettv);
+void f_term_setkill(typval_T *argvars, typval_T *rettv);
void f_term_start(typval_T *argvars, typval_T *rettv);
void f_term_wait(typval_T *argvars, typval_T *rettv);
void term_send_eof(channel_T *ch);
diff --git a/src/structs.h b/src/structs.h
index d6959bc4b..db2792bbd 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -1707,7 +1707,8 @@ struct channel_S {
#define JO2_TERM_OPENCMD 0x0800 /* "term_opencmd" */
#define JO2_EOF_CHARS 0x1000 /* "eof_chars" */
#define JO2_NORESTORE 0x2000 /* "norestore" */
-#define JO2_ALL 0x2FFF
+#define JO2_TERM_KILL 0x4000 /* "term_kill" */
+#define JO2_ALL 0x7FFF
#define JO_MODE_ALL (JO_MODE + JO_IN_MODE + JO_OUT_MODE + JO_ERR_MODE)
#define JO_CB_ALL \
@@ -1775,6 +1776,7 @@ typedef struct
char_u *jo_term_opencmd;
int jo_term_finish;
char_u *jo_eof_chars;
+ char_u *jo_term_kill;
#endif
} jobopt_T;
diff --git a/src/terminal.c b/src/terminal.c
index 547897fcf..cc8305517 100644
--- a/src/terminal.c
+++ b/src/terminal.c
@@ -137,6 +137,7 @@ struct terminal_S {
#if defined(FEAT_SESSION)
char_u *tl_command;
#endif
+ char_u *tl_kill;
/* last known vterm size */
int tl_rows;
@@ -535,6 +536,13 @@ term_start(typval_T *argvar, jobopt_T *opt, int without_job, int forceit)
}
#endif
+ if (opt->jo_term_kill != NULL)
+ {
+ char_u *p = skiptowhite(opt->jo_term_kill);
+
+ term->tl_kill = vim_strnsave(opt->jo_term_kill, p - opt->jo_term_kill);
+ }
+
/* System dependent: setup the vterm and maybe start the job in it. */
if (argvar->v_type == VAR_STRING
&& argvar->vval.v_string != NULL
@@ -611,6 +619,13 @@ ex_terminal(exarg_T *eap)
opt.jo_hidden = 1;
else if ((int)(p - cmd) == 9 && STRNICMP(cmd, "norestore", 9) == 0)
opt.jo_term_norestore = 1;
+ else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "kill", 4) == 0
+ && ep != NULL)
+ {
+ opt.jo_set2 |= JO2_TERM_KILL;
+ opt.jo_term_kill = ep + 1;
+ p = skiptowhite(cmd);
+ }
else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "rows", 4) == 0
&& ep != NULL && isdigit(ep[1]))
{
@@ -644,7 +659,7 @@ ex_terminal(exarg_T *eap)
if (*p)
*p = NUL;
EMSG2(_("E181: Invalid attribute: %s"), cmd);
- return;
+ goto theend;
}
cmd = skipwhite(p);
}
@@ -667,6 +682,8 @@ ex_terminal(exarg_T *eap)
argvar[1].v_type = VAR_UNKNOWN;
term_start(argvar, &opt, FALSE, eap->forceit);
vim_free(tofree);
+
+theend:
vim_free(opt.jo_eof_chars);
}
@@ -758,6 +775,7 @@ free_terminal(buf_T *buf)
#ifdef FEAT_SESSION
vim_free(term->tl_command);
#endif
+ vim_free(term->tl_kill);
vim_free(term->tl_status_text);
vim_free(term->tl_opencmd);
vim_free(term->tl_eof_chars);
@@ -1081,6 +1099,56 @@ term_none_open(term_T *term)
}
/*
+ * Used when exiting: kill the job in "buf" if so desired.
+ * Return OK when the job finished.
+ * Return FAIL when the job is still running.
+ */
+ int
+term_try_stop_job(buf_T *buf)
+{
+ int count;
+ char *how = (char *)buf->b_term->tl_kill;
+
+#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
+ if ((how == NULL || *how == NUL) && (p_confirm || cmdmod.confirm))
+ {
+ char_u buff[DIALOG_MSG_SIZE];
+ int ret;
+
+ dialog_msg(buff, _("Kill job in \"%s\"?"), buf->b_fname);
+ ret = vim_dialog_yesnocancel(VIM_QUESTION, NULL, buff, 1);
+ if (ret == VIM_YES)
+ how = "kill";
+ else if (ret == VIM_CANCEL)
+ return FAIL;
+ }
+#endif
+ if (how == NULL || *how == NUL)
+ return FAIL;
+
+ job_stop(buf->b_term->tl_job, NULL, how);
+
+ /* wait for up to a second for the job to die */
+ for (count = 0; count < 100; ++count)
+ {
+ /* buffer, terminal and job may be cleaned up while waiting */
+ if (!buf_valid(buf)
+ || buf->b_term == NULL
+ || buf->b_term->tl_job == NULL)
+ return OK;
+
+ /* call job_status() to update jv_status */
+ job_status(buf->b_term->tl_job);
+ if (buf->b_term->tl_job->jv_status >= JOB_ENDED)
+ return OK;
+ ui_delay(10L, FALSE);
+ mch_check_messages();
+ parse_queued_messages();
+ }
+ return FAIL;
+}
+
+/*
* Add the last line of the scrollback buffer to the buffer in the window.
*/
static void
@@ -2922,10 +2990,11 @@ set_terminal_default_colors(int cterm_fg, int cterm_bg)
/*
* Get the buffer from the first argument in "argvars".
- * Returns NULL when the buffer is not for a terminal window.
+ * Returns NULL when the buffer is not for a terminal window and logs a message
+ * with "where".
*/
static buf_T *
-term_get_buf(typval_T *argvars)
+term_get_buf(typval_T *argvars, char *where)
{
buf_T *buf;
@@ -2934,7 +3003,10 @@ term_get_buf(typval_T *argvars)
buf = get_buf_tv(&argvars[0], FALSE);
--emsg_off;
if (buf == NULL || buf->b_term == NULL)
+ {
+ ch_log(NULL, "%s: invalid buffer argument", where);
return NULL;
+ }
return buf;
}
@@ -2980,7 +3052,7 @@ dump_term_color(FILE *fd, VTermColor *color)
void
f_term_dumpwrite(typval_T *argvars, typval_T *rettv UNUSED)
{
- buf_T *buf = term_get_buf(argvars);
+ buf_T *buf = term_get_buf(argvars, "term_dumpwrite()");
term_T *term;
char_u *fname;
int max_height = 0;
@@ -3719,7 +3791,7 @@ f_term_dumpload(typval_T *argvars, typval_T *rettv)
void
f_term_getaltscreen(typval_T *argvars, typval_T *rettv)
{
- buf_T *buf = term_get_buf(argvars);
+ buf_T *buf = term_get_buf(argvars, "term_getaltscreen()");
if (buf == NULL)
return;
@@ -3766,7 +3838,7 @@ f_term_getattr(typval_T *argvars, typval_T *rettv)
void
f_term_getcursor(typval_T *argvars, typval_T *rettv)
{
- buf_T *buf = term_get_buf(argvars);
+ buf_T *buf = term_get_buf(argvars, "term_getcursor()");
term_T *term;
list_T *l;
dict_T *d;
@@ -3800,7 +3872,7 @@ f_term_getcursor(typval_T *argvars, typval_T *rettv)
void
f_term_getjob(typval_T *argvars, typval_T *rettv)
{
- buf_T *buf = term_get_buf(argvars);
+ buf_T *buf = term_get_buf(argvars, "term_getjob()");
rettv->v_type = VAR_JOB;
rettv->vval.v_job = NULL;
@@ -3828,7 +3900,7 @@ get_row_number(typval_T *tv, term_T *term)
void
f_term_getline(typval_T *argvars, typval_T *rettv)
{
- buf_T *buf = term_get_buf(argvars);
+ buf_T *buf = term_get_buf(argvars, "term_getline()");
term_T *term;
int row;
@@ -3875,7 +3947,7 @@ f_term_getline(typval_T *argvars, typval_T *rettv)
void
f_term_getscrolled(typval_T *argvars, typval_T *rettv)
{
- buf_T *buf = term_get_buf(argvars);
+ buf_T *buf = term_get_buf(argvars, "term_getscrolled()");
if (buf == NULL)
return;
@@ -3888,7 +3960,7 @@ f_term_getscrolled(typval_T *argvars, typval_T *rettv)
void
f_term_getsize(typval_T *argvars, typval_T *rettv)
{
- buf_T *buf = term_get_buf(argvars);
+ buf_T *buf = term_get_buf(argvars, "term_getsize()");
list_T *l;
if (rettv_list_alloc(rettv) == FAIL)
@@ -3907,7 +3979,7 @@ f_term_getsize(typval_T *argvars, typval_T *rettv)
void
f_term_getstatus(typval_T *argvars, typval_T *rettv)
{
- buf_T *buf = term_get_buf(argvars);
+ buf_T *buf = term_get_buf(argvars, "term_getstatus()");
term_T *term;
char_u val[100];
@@ -3931,7 +4003,7 @@ f_term_getstatus(typval_T *argvars, typval_T *rettv)
void
f_term_gettitle(typval_T *argvars, typval_T *rettv)
{
- buf_T *buf = term_get_buf(argvars);
+ buf_T *buf = term_get_buf(argvars, "term_gettitle()");
rettv->v_type = VAR_STRING;
if (buf == NULL)
@@ -3947,7 +4019,7 @@ f_term_gettitle(typval_T *argvars, typval_T *rettv)
void
f_term_gettty(typval_T *argvars, typval_T *rettv)
{
- buf_T *buf = term_get_buf(argvars);
+ buf_T *buf = term_get_buf(argvars, "term_gettty()");
char_u *p;
int num = 0;
@@ -4005,7 +4077,7 @@ f_term_list(typval_T *argvars UNUSED, typval_T *rettv)
void
f_term_scrape(typval_T *argvars, typval_T *rettv)
{
- buf_T *buf = term_get_buf(argvars);
+ buf_T *buf = term_get_buf(argvars, "term_scrape()");
VTermScreen *screen = NULL;
VTermPos pos;
list_T *l;
@@ -4114,7 +4186,7 @@ f_term_scrape(typval_T *argvars, typval_T *rettv)
void
f_term_sendkeys(typval_T *argvars, typval_T *rettv)
{
- buf_T *buf = term_get_buf(argvars);
+ buf_T *buf = term_get_buf(argvars, "term_sendkeys()");
char_u *msg;
term_T *term;
@@ -4143,7 +4215,7 @@ f_term_sendkeys(typval_T *argvars, typval_T *rettv)
f_term_setrestore(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
#if defined(FEAT_SESSION)
- buf_T *buf = term_get_buf(argvars);
+ buf_T *buf = term_get_buf(argvars, "term_setrestore()");
term_T *term;
char_u *cmd;
@@ -4160,6 +4232,27 @@ f_term_setrestore(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
}
/*
+ * "term_setkill(buf, how)" function
+ */
+ void
+f_term_setkill(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
+{
+ buf_T *buf = term_get_buf(argvars, "term_setkill()");
+ term_T *term;
+ char_u *how;
+
+ if (buf == NULL)
+ return;
+ term = buf->b_term;
+ vim_free(term->tl_kill);
+ how = get_tv_string_chk(&argvars[1]);
+ if (how != NULL)
+ term->tl_kill = vim_strsave(how);
+ else
+ term->tl_kill = NULL;
+}
+
+/*
* "term_start(command, options)" function
*/
void
@@ -4177,7 +4270,7 @@ f_term_start(typval_T *argvars, typval_T *rettv)
JO2_TERM_NAME + JO2_TERM_FINISH + JO2_HIDDEN + JO2_TERM_OPENCMD
+ JO2_TERM_COLS + JO2_TERM_ROWS + JO2_VERTICAL + JO2_CURWIN
+ JO2_CWD + JO2_ENV + JO2_EOF_CHARS
- + JO2_NORESTORE) == FAIL)
+ + JO2_NORESTORE + JO2_TERM_KILL) == FAIL)
return;
if (opt.jo_vertical)
@@ -4194,13 +4287,10 @@ f_term_start(typval_T *argvars, typval_T *rettv)
void
f_term_wait(typval_T *argvars, typval_T *rettv UNUSED)
{
- buf_T *buf = term_get_buf(argvars);
+ buf_T *buf = term_get_buf(argvars, "term_wait()");
if (buf == NULL)
- {
- ch_log(NULL, "term_wait(): invalid argument");
return;
- }
if (buf->b_term->tl_job == NULL)
{
ch_log(NULL, "term_wait(): no job to wait for");
diff --git a/src/testdir/test_terminal.vim b/src/testdir/test_terminal.vim
index 0e04abd5c..ef6b176ee 100644
--- a/src/testdir/test_terminal.vim
+++ b/src/testdir/test_terminal.vim
@@ -5,6 +5,7 @@ if !has('terminal')
endif
source shared.vim
+source screendump.vim
let s:python = PythonProg()
@@ -839,3 +840,48 @@ func Test_terminal_response_to_control_sequence()
call delete('Xescape')
unlet g:job
endfunc
+
+" Run Vim in a terminal, then start a terminal in that Vim with a kill
+" argument, check that :qall works.
+func Test_terminal_qall_kill_arg()
+ if !CanRunVimInTerminal()
+ return
+ endif
+ let buf = RunVimInTerminal('', {})
+
+ " Open a terminal window and wait for the prompt to appear
+ call term_sendkeys(buf, ":term ++kill=kill\<CR>")
+ call WaitFor({-> term_getline(buf, 10) =~ '\[running]'})
+ call WaitFor({-> term_getline(buf, 1) !~ '^\s*$'})
+
+ " make Vim exit, it will kill the shell
+ call term_sendkeys(buf, "\<C-W>:qall\<CR>")
+ call WaitFor({-> term_getstatus(buf) == "finished"})
+
+ " close the terminal window where Vim was running
+ quit
+endfunc
+
+" Run Vim in a terminal, then start a terminal in that Vim with a kill
+" argument, check that :qall works.
+func Test_terminal_qall_kill_func()
+ if !CanRunVimInTerminal()
+ return
+ endif
+ let buf = RunVimInTerminal('', {})
+
+ " Open a terminal window and wait for the prompt to appear
+ call term_sendkeys(buf, ":term\<CR>")
+ call WaitFor({-> term_getline(buf, 10) =~ '\[running]'})
+ call WaitFor({-> term_getline(buf, 1) !~ '^\s*$'})
+
+ " set kill using term_setkill()
+ call term_sendkeys(buf, "\<C-W>:call term_setkill(bufnr('%'), 'kill')\<CR>")
+
+ " make Vim exit, it will kill the shell
+ call term_sendkeys(buf, "\<C-W>:qall\<CR>")
+ call WaitFor({-> term_getstatus(buf) == "finished"})
+
+ " close the terminal window where Vim was running
+ quit
+endfunc
diff --git a/src/version.c b/src/version.c
index 89b6eef14..727c21fe1 100644
--- a/src/version.c
+++ b/src/version.c
@@ -767,6 +767,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 1593,
+/**/
1592,
/**/
1591,