changeset 8174:f2286ff0c102 v7.4.1380

commit https://github.com/vim/vim/commit/ee1cffc07a42441924c5353af7fd7ab6e97e5aae Author: Bram Moolenaar <Bram@vim.org> Date: Sun Feb 21 19:14:41 2016 +0100 patch 7.4.1380 Problem: The job exit callback is not implemented. Solution: Add the "exit-cb" option.
author Christian Brabandt <cb@256bit.org>
date Sun, 21 Feb 2016 19:15:04 +0100
parents 8f9a62af9212
children 76ce2d323c0f
files src/channel.c src/eval.c src/macros.h src/misc2.c src/proto/eval.pro src/structs.h src/testdir/test_channel.vim src/version.c
diffstat 8 files changed, 147 insertions(+), 15 deletions(-) [+]
line wrap: on
line diff
--- a/src/channel.c
+++ b/src/channel.c
@@ -833,6 +833,8 @@ invoke_callback(channel_T *channel, char
 
     call_func(callback, (int)STRLEN(callback),
 			     &rettv, 2, argv, 0L, 0L, &dummy, TRUE, NULL);
+    clear_tv(&rettv);
+
     /* If an echo command was used the cursor needs to be put back where
      * it belongs. */
     setcursor();
--- a/src/eval.c
+++ b/src/eval.c
@@ -7774,6 +7774,7 @@ job_free(job_T *job)
 	job->jv_prev->jv_next = job->jv_next;
 
     vim_free(job->jv_stoponexit);
+    vim_free(job->jv_exit_cb);
     vim_free(job);
 }
 
@@ -7781,7 +7782,13 @@ job_free(job_T *job)
 job_unref(job_T *job)
 {
     if (job != NULL && --job->jv_refcount <= 0)
-	job_free(job);
+    {
+	/* Do not free the job when it has not ended yet and there is a
+	 * "stoponexit" flag or an exit callback. */
+	if (job->jv_status != JOB_STARTED
+		|| (job->jv_stoponexit == NULL && job->jv_exit_cb == NULL))
+	    job_free(job);
+    }
 }
 
 /*
@@ -7819,6 +7826,14 @@ job_set_options(job_T *job, jobopt_T *op
 	else
 	    job->jv_stoponexit = vim_strsave(opt->jo_stoponexit);
     }
+    if (opt->jo_set & JO_EXIT_CB)
+    {
+	vim_free(job->jv_exit_cb);
+	if (opt->jo_exit_cb == NULL || *opt->jo_exit_cb == NUL)
+	    job->jv_exit_cb = NULL;
+	else
+	    job->jv_exit_cb = vim_strsave(opt->jo_exit_cb);
+    }
 }
 
 /*
@@ -7830,7 +7845,7 @@ job_stop_on_exit()
     job_T	*job;
 
     for (job = first_job; job != NULL; job = job->jv_next)
-	if (job->jv_stoponexit != NULL && *job->jv_stoponexit != NUL)
+	if (job->jv_status == JOB_STARTED && job->jv_stoponexit != NULL)
 	    mch_stop_job(job, job->jv_stoponexit);
 }
 #endif
@@ -10030,7 +10045,7 @@ get_job_options(typval_T *tv, jobopt_T *
 		opt->jo_out_cb = get_callback(item);
 		if (opt->jo_out_cb == NULL)
 		{
-		    EMSG2(_(e_invarg2), "out-db");
+		    EMSG2(_(e_invarg2), "out-cb");
 		    return FAIL;
 		}
 	    }
@@ -10108,6 +10123,18 @@ get_job_options(typval_T *tv, jobopt_T *
 		    return FAIL;
 		}
 	    }
+	    else if (STRCMP(hi->hi_key, "exit-cb") == 0)
+	    {
+		if (!(supported & JO_EXIT_CB))
+		    break;
+		opt->jo_set |= JO_EXIT_CB;
+		opt->jo_exit_cb = get_tv_string_buf_chk(item, opt->jo_ecb_buf);
+		if (opt->jo_ecb_buf == NULL)
+		{
+		    EMSG2(_(e_invarg2), "exit-cb");
+		    return FAIL;
+		}
+	    }
 	    else
 		break;
 	    --todo;
@@ -14771,7 +14798,7 @@ f_items(typval_T *argvars, typval_T *ret
     dict_list(argvars, rettv, 2);
 }
 
-#ifdef FEAT_JOB
+#if defined(FEAT_JOB) || defined(PROTO)
 /*
  * Get the job from the argument.
  * Returns NULL if the job is invalid.
@@ -14824,7 +14851,7 @@ f_job_setoptions(typval_T *argvars, typv
     if (job == NULL)
 	return;
     clear_job_options(&opt);
-    if (get_job_options(&argvars[1], &opt, JO_STOPONEXIT) == FAIL)
+    if (get_job_options(&argvars[1], &opt, JO_STOPONEXIT + JO_EXIT_CB) == FAIL)
 	return;
     job_set_options(job, &opt);
 }
@@ -14858,7 +14885,8 @@ f_job_start(typval_T *argvars UNUSED, ty
     clear_job_options(&opt);
     opt.jo_mode = MODE_NL;
     if (get_job_options(&argvars[1], &opt,
-	    JO_MODE_ALL + JO_CB_ALL + JO_TIMEOUT_ALL + JO_STOPONEXIT) == FAIL)
+	    JO_MODE_ALL + JO_CB_ALL + JO_TIMEOUT_ALL
+					+ JO_STOPONEXIT + JO_EXIT_CB) == FAIL)
 	return;
     job_set_options(job, &opt);
 
@@ -14959,6 +14987,77 @@ theend:
 }
 
 /*
+ * Get the status of "job" and invoke the exit callback when needed.
+ * The returned string is not allocated.
+ */
+    static char *
+job_status(job_T *job)
+{
+    char	*result;
+
+    if (job->jv_status == JOB_ENDED)
+	/* No need to check, dead is dead. */
+	result = "dead";
+    else if (job->jv_status == JOB_FAILED)
+	result = "fail";
+    else
+    {
+	result = mch_job_status(job);
+# ifdef FEAT_CHANNEL
+	if (job->jv_status == JOB_ENDED)
+	    ch_log(job->jv_channel, "Job ended");
+# endif
+	if (job->jv_status == JOB_ENDED && job->jv_exit_cb != NULL)
+	{
+	    typval_T	argv[3];
+	    typval_T	rettv;
+	    int		dummy;
+
+	    /* invoke the exit callback */
+	    argv[0].v_type = VAR_JOB;
+	    argv[0].vval.v_job = job;
+	    argv[1].v_type = VAR_NUMBER;
+	    argv[1].vval.v_number = job->jv_exitval;
+	    call_func(job->jv_exit_cb, (int)STRLEN(job->jv_exit_cb),
+				 &rettv, 2, argv, 0L, 0L, &dummy, TRUE, NULL);
+	    clear_tv(&rettv);
+	}
+	if (job->jv_status == JOB_ENDED && job->jv_refcount == 0)
+	{
+	    /* The job already was unreferenced, now that it ended it can be
+	     * freed. Careful: caller must not use "job" after this! */
+	    job_free(job);
+	}
+    }
+    return result;
+}
+
+/*
+ * Called once in a while: check if any jobs with an "exit-cb" have ended.
+ */
+    void
+job_check_ended()
+{
+    static time_t   last_check = 0;
+    time_t	    now;
+    job_T	    *job;
+    job_T	    *next;
+
+    /* Only do this once in 10 seconds. */
+    now = time(NULL);
+    if (last_check + 10 < now)
+    {
+	last_check = now;
+	for (job = first_job; job != NULL; job = next)
+	{
+	    next = job->jv_next;
+	    if (job->jv_status == JOB_STARTED && job->jv_exit_cb != NULL)
+		job_status(job); /* may free "job" */
+	}
+    }
+}
+
+/*
  * "job_status()" function
  */
     static void
@@ -14969,13 +15068,7 @@ f_job_status(typval_T *argvars, typval_T
 
     if (job != NULL)
     {
-	if (job->jv_status == JOB_ENDED)
-	    /* No need to check, dead is dead. */
-	    result = "dead";
-	else if (job->jv_status == JOB_FAILED)
-	    result = "fail";
-	else
-	    result = mch_job_status(job);
+	result = job_status(job);
 	rettv->v_type = VAR_STRING;
 	rettv->vval.v_string = vim_strsave((char_u *)result);
     }
@@ -22857,7 +22950,8 @@ copy_tv(typval_T *from, typval_T *to)
 	case VAR_JOB:
 #ifdef FEAT_JOB
 	    to->vval.v_job = from->vval.v_job;
-	    ++to->vval.v_job->jv_refcount;
+	    if (to->vval.v_job != NULL)
+		++to->vval.v_job->jv_refcount;
 	    break;
 #endif
 	case VAR_CHANNEL:
--- a/src/macros.h
+++ b/src/macros.h
@@ -317,6 +317,6 @@
 # define PLINES_NOFILL(x) plines(x)
 #endif
 
-#if defined(FEAT_CHANNEL) || defined(FEAT_CLIENTSERVER)
+#if defined(FEAT_CHANNEL) || defined(FEAT_JOB) || defined(FEAT_CLIENTSERVER)
 # define MESSAGE_QUEUE
 #endif
--- a/src/misc2.c
+++ b/src/misc2.c
@@ -6256,5 +6256,9 @@ parse_queued_messages(void)
     /* Process the queued clientserver messages. */
     server_parse_messages();
 # endif
+# ifdef FEAT_JOB
+    /* Check if any jobs have ended. */
+    job_check_ended();
+# endif
 }
 #endif
--- a/src/proto/eval.pro
+++ b/src/proto/eval.pro
@@ -87,6 +87,7 @@ char_u *get_expr_name(expand_T *xp, int 
 int call_func(char_u *funcname, int len, typval_T *rettv, int argcount, typval_T *argvars, linenr_T firstline, linenr_T lastline, int *doesrange, int evaluate, dict_T *selfdict);
 int func_call(char_u *name, typval_T *args, dict_T *selfdict, typval_T *rettv);
 void dict_extend(dict_T *d1, dict_T *d2, char_u *action);
+void job_check_ended(void);
 void mzscheme_call_vim(char_u *name, typval_T *args, typval_T *rettv);
 float_T vim_round(float_T f);
 long do_searchpair(char_u *spat, char_u *mpat, char_u *epat, int dir, char_u *skip, int flags, pos_T *match_pos, linenr_T lnum_stop, long time_limit);
--- a/src/structs.h
+++ b/src/structs.h
@@ -1265,6 +1265,7 @@ struct jobvar_S
 #endif
     jobstatus_T	jv_status;
     char_u	*jv_stoponexit; /* allocated */
+    char_u	*jv_exit_cb;	/* allocated */
 
     int		jv_refcount;	/* reference count */
     channel_T	*jv_channel;	/* channel for I/O, reference counted */
@@ -1390,6 +1391,7 @@ struct channel_S {
 #define JO_PART		0x0800	/* "part" */
 #define JO_ID		0x1000	/* "id" */
 #define JO_STOPONEXIT	0x2000	/* "stoponexit" */
+#define JO_EXIT_CB	0x4000	/* "exit-cb" */
 #define JO_ALL		0xffffff
 
 #define JO_MODE_ALL	(JO_MODE + JO_IN_MODE + JO_OUT_MODE + JO_ERR_MODE)
@@ -1418,6 +1420,8 @@ typedef struct
     int		jo_id;
     char_u	jo_soe_buf[NUMBUFLEN];
     char_u	*jo_stoponexit;
+    char_u	jo_ecb_buf[NUMBUFLEN];
+    char_u	*jo_exit_cb;
 } jobopt_T;
 
 
--- a/src/testdir/test_channel.vim
+++ b/src/testdir/test_channel.vim
@@ -468,3 +468,28 @@ func Test_call()
   call ch_log('Test_call()')
   call s:run_server('s:test_call')
 endfunc
+
+"""""""""
+
+let s:job_ret = 'not yet'
+function MyExitCb(job, status)
+  let s:job_ret = 'done'
+endfunc
+
+function s:test_exit_callback(port)
+  call job_setoptions(s:job, {'exit-cb': 'MyExitCb'})
+  let s:exit_job = s:job
+endfunc
+
+func Test_exit_callback()
+  if has('job')
+    call s:run_server('s:test_exit_callback')
+
+    " the job may take a little while to exit
+    sleep 50m
+
+    " calling job_status() triggers the callback
+    call job_status(s:exit_job)
+    call assert_equal('done', s:job_ret)
+  endif
+endfunc
--- a/src/version.c
+++ b/src/version.c
@@ -748,6 +748,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1380,
+/**/
     1379,
 /**/
     1378,