]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 7.4.1380 v7.4.1380
authorBram Moolenaar <Bram@vim.org>
Sun, 21 Feb 2016 18:14:41 +0000 (19:14 +0100)
committerBram Moolenaar <Bram@vim.org>
Sun, 21 Feb 2016 18:14:41 +0000 (19:14 +0100)
Problem:    The job exit callback is not implemented.
Solution:   Add the "exit-cb" option.

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

index d02f0197ef62e2305e2040083b22fa09eefaa1a3..aa97088b403f54bdadf71073e9ac56f4025711bf 100644 (file)
@@ -833,6 +833,8 @@ invoke_callback(channel_T *channel, char_u *callback, typval_T *argv)
 
     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();
index 2d81f5680bc9abf47f1c13b2ec7980b2add5e0d1..cc7b9455f2b59cf683d511622309bcabc3032674 100644 (file)
@@ -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 *opt)
        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, int supported)
                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 *opt, int supported)
                    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 *rettv)
     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, typval_T *rettv UNUSED)
     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, typval_T *rettv)
     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);
 
@@ -14958,6 +14986,77 @@ theend:
 #endif
 }
 
+/*
+ * 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
  */
@@ -14969,13 +15068,7 @@ f_job_status(typval_T *argvars, typval_T *rettv)
 
     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:
index ec0cdc3f9f7803259f797aa6ad85149d0972af61..012def6aeadbf276d0b57925c7dc6445b6582227 100644 (file)
 # 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
index 932d67e7813054a1945a959269131e82fc1aa35e..7665d2931cb64a52f919316ecb4b60443af94619 100644 (file)
@@ -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
index 780b40c5327a89670bacb311cd99aabda25d57d9..a511b813ca9b6393a2cfdc8ec56ae5fe4c06b124 100644 (file)
@@ -87,6 +87,7 @@ char_u *get_expr_name(expand_T *xp, int idx);
 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);
index 56b698cc177060116dcf40c5c44dcbfff7ff5121..b67f48a855213fb04db8855d887814f84ee0b233 100644 (file)
@@ -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;
 
 
index 61ca9b73213aeb4ec3c433d29f0abc9b948bed80..2b4b1892595d8362bebd2eb95fece5cb9b1180eb 100644 (file)
@@ -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
index b5f482536824355b30138a93f04b25d4c4b60518..b6153549c97ede3f9fecea9d09add3a669dbd780 100644 (file)
@@ -747,6 +747,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1380,
 /**/
     1379,
 /**/