Patch 7.4.1539
Problem:    Too much code in eval.c.
Solution:   Move job and channel code to channel.c.
Files:      src/eval.c, src/channel.c, src/proto/channel.pro,
            src/proto/eval.pro


*** ../vim-7.4.1538/src/eval.c  2016-03-11 22:52:00.734438114 +0100
--- src/eval.c  2016-03-12 13:33:22.054745522 +0100
***************
*** 836,849 ****
  static int handle_subscript(char_u **arg, typval_T *rettv, int evaluate, int 
verbose);
  static typval_T *alloc_string_tv(char_u *string);
  static void init_tv(typval_T *varp);
- static long get_tv_number(typval_T *varp);
  #ifdef FEAT_FLOAT
  static float_T get_tv_float(typval_T *varp);
  #endif
  static linenr_T get_tv_lnum(typval_T *argvars);
  static linenr_T get_tv_lnum_buf(typval_T *argvars, buf_T *buf);
- static char_u *get_tv_string(typval_T *varp);
- static char_u *get_tv_string_buf(typval_T *varp, char_u *buf);
  static dictitem_T *find_var(char_u *name, hashtab_T **htp, int no_autoload);
  static dictitem_T *find_var_in_ht(hashtab_T *ht, int htname, char_u *varname, 
int no_autoload);
  static hashtab_T *find_var_ht(char_u *name, char_u **varname);
--- 836,846 ----
***************
*** 7735,7863 ****
  }
  
  #if defined(FEAT_JOB_CHANNEL) || defined(PROTO)
- /*
-  * Decrement the reference count on "channel" and maybe free it when it goes
-  * down to zero.  Don't free it if there is a pending action.
-  * Returns TRUE when the channel is no longer referenced.
-  */
-     int
- channel_unref(channel_T *channel)
- {
-     if (channel != NULL && --channel->ch_refcount <= 0)
-       return channel_may_free(channel);
-     return FALSE;
- }
- 
- static job_T *first_job = NULL;
- 
-     static void
- job_free(job_T *job)
- {
-     ch_log(job->jv_channel, "Freeing job");
-     if (job->jv_channel != NULL)
-     {
-       /* The link from the channel to the job doesn't count as a reference,
-        * thus don't decrement the refcount of the job.  The reference from
-        * the job to the channel does count the refrence, decrement it and
-        * NULL the reference.  We don't set ch_job_killed, unreferencing the
-        * job doesn't mean it stops running. */
-       job->jv_channel->ch_job = NULL;
-       channel_unref(job->jv_channel);
-     }
-     mch_clear_job(job);
- 
-     if (job->jv_next != NULL)
-       job->jv_next->jv_prev = job->jv_prev;
-     if (job->jv_prev == NULL)
-       first_job = job->jv_next;
-     else
-       job->jv_prev->jv_next = job->jv_next;
- 
-     vim_free(job->jv_stoponexit);
-     vim_free(job->jv_exit_cb);
-     vim_free(job);
- }
- 
-     static void
- job_unref(job_T *job)
- {
-     if (job != NULL && --job->jv_refcount <= 0)
-     {
-       /* 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);
-       }
-       else if (job->jv_channel != NULL)
-       {
-           /* Do remove the link to the channel, otherwise it hangs
-            * around until Vim exits. See job_free() for refcount. */
-           job->jv_channel->ch_job = NULL;
-           channel_unref(job->jv_channel);
-           job->jv_channel = NULL;
-       }
-     }
- }
- 
- /*
-  * Allocate a job.  Sets the refcount to one and sets options default.
-  */
-     static job_T *
- job_alloc(void)
- {
-     job_T *job;
- 
-     job = (job_T *)alloc_clear(sizeof(job_T));
-     if (job != NULL)
-     {
-       job->jv_refcount = 1;
-       job->jv_stoponexit = vim_strsave((char_u *)"term");
- 
-       if (first_job != NULL)
-       {
-           first_job->jv_prev = job;
-           job->jv_next = first_job;
-       }
-       first_job = job;
-     }
-     return job;
- }
- 
-     static void
- job_set_options(job_T *job, jobopt_T *opt)
- {
-     if (opt->jo_set & JO_STOPONEXIT)
-     {
-       vim_free(job->jv_stoponexit);
-       if (opt->jo_stoponexit == NULL || *opt->jo_stoponexit == NUL)
-           job->jv_stoponexit = NULL;
-       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);
-     }
- }
- 
- /*
-  * Called when Vim is exiting: kill all jobs that have the "stoponexit" flag.
-  */
-     void
- job_stop_on_exit()
- {
-     job_T     *job;
- 
-     for (job = first_job; job != NULL; job = job->jv_next)
-       if (job->jv_status == JOB_STARTED && job->jv_stoponexit != NULL)
-           mch_stop_job(job, job->jv_stoponexit);
- }
  #endif
  
      static char *
--- 7732,7737 ----
***************
*** 9650,9656 ****
      rettv->vval.v_number = (buf != NULL && buf->b_ml.ml_mfp != NULL);
  }
  
!     static buf_T *
  buflist_find_by_name(char_u *name, int curtab_only)
  {
      int               save_magic;
--- 9524,9530 ----
      rettv->vval.v_number = (buf != NULL && buf->b_ml.ml_mfp != NULL);
  }
  
!     buf_T *
  buflist_find_by_name(char_u *name, int curtab_only)
  {
      int               save_magic;
***************
*** 9941,10337 ****
  }
  #endif
  
- #if defined(FEAT_JOB_CHANNEL)
- /*
-  * Get a callback from "arg".  It can be a Funcref or a function name.
-  * When "arg" is zero return an empty string.
-  * Return NULL for an invalid argument.
-  */
-     static char_u *
- get_callback(typval_T *arg)
- {
-     if (arg->v_type == VAR_FUNC || arg->v_type == VAR_STRING)
-       return arg->vval.v_string;
-     if (arg->v_type == VAR_NUMBER && arg->vval.v_number == 0)
-       return (char_u *)"";
-     EMSG(_("E999: Invalid callback argument"));
-     return NULL;
- }
- 
-     static int
- handle_mode(typval_T *item, jobopt_T *opt, ch_mode_T *modep, int jo)
- {
-     char_u    *val = get_tv_string(item);
- 
-     opt->jo_set |= jo;
-     if (STRCMP(val, "nl") == 0)
-       *modep = MODE_NL;
-     else if (STRCMP(val, "raw") == 0)
-       *modep = MODE_RAW;
-     else if (STRCMP(val, "js") == 0)
-       *modep = MODE_JS;
-     else if (STRCMP(val, "json") == 0)
-       *modep = MODE_JSON;
-     else
-     {
-       EMSG2(_(e_invarg2), val);
-       return FAIL;
-     }
-     return OK;
- }
- 
-     static int
- handle_io(typval_T *item, int part, jobopt_T *opt)
- {
-     char_u    *val = get_tv_string(item);
- 
-     opt->jo_set |= JO_OUT_IO << (part - PART_OUT);
-     if (STRCMP(val, "null") == 0)
-       opt->jo_io[part] = JIO_NULL;
-     else if (STRCMP(val, "pipe") == 0)
-       opt->jo_io[part] = JIO_PIPE;
-     else if (STRCMP(val, "file") == 0)
-       opt->jo_io[part] = JIO_FILE;
-     else if (STRCMP(val, "buffer") == 0)
-       opt->jo_io[part] = JIO_BUFFER;
-     else if (STRCMP(val, "out") == 0 && part == PART_ERR)
-       opt->jo_io[part] = JIO_OUT;
-     else
-     {
-       EMSG2(_(e_invarg2), val);
-       return FAIL;
-     }
-     return OK;
- }
- 
-     static void
- clear_job_options(jobopt_T *opt)
- {
-     vim_memset(opt, 0, sizeof(jobopt_T));
- }
- 
- /*
-  * Get the PART_ number from the first character of an option name.
-  */
-     static int
- part_from_char(int c)
- {
-     return c == 'i' ? PART_IN : c == 'o' ? PART_OUT: PART_ERR;
- }
- 
- /*
-  * Get the option entries from the dict in "tv", parse them and put the result
-  * in "opt".
-  * Only accept options in "supported".
-  * If an option value is invalid return FAIL.
-  */
-     static int
- get_job_options(typval_T *tv, jobopt_T *opt, int supported)
- {
-     typval_T  *item;
-     char_u    *val;
-     dict_T    *dict;
-     int               todo;
-     hashitem_T        *hi;
-     int               part;
- 
-     opt->jo_set = 0;
-     if (tv->v_type == VAR_UNKNOWN)
-       return OK;
-     if (tv->v_type != VAR_DICT)
-     {
-       EMSG(_(e_invarg));
-       return FAIL;
-     }
-     dict = tv->vval.v_dict;
-     if (dict == NULL)
-       return OK;
- 
-     todo = (int)dict->dv_hashtab.ht_used;
-     for (hi = dict->dv_hashtab.ht_array; todo > 0; ++hi)
-       if (!HASHITEM_EMPTY(hi))
-       {
-           item = &HI2DI(hi)->di_tv;
- 
-           if (STRCMP(hi->hi_key, "mode") == 0)
-           {
-               if (!(supported & JO_MODE))
-                   break;
-               if (handle_mode(item, opt, &opt->jo_mode, JO_MODE) == FAIL)
-                   return FAIL;
-           }
-           else if (STRCMP(hi->hi_key, "in-mode") == 0)
-           {
-               if (!(supported & JO_IN_MODE))
-                   break;
-               if (handle_mode(item, opt, &opt->jo_in_mode, JO_IN_MODE)
-                                                                     == FAIL)
-                   return FAIL;
-           }
-           else if (STRCMP(hi->hi_key, "out-mode") == 0)
-           {
-               if (!(supported & JO_OUT_MODE))
-                   break;
-               if (handle_mode(item, opt, &opt->jo_out_mode, JO_OUT_MODE)
-                                                                     == FAIL)
-                   return FAIL;
-           }
-           else if (STRCMP(hi->hi_key, "err-mode") == 0)
-           {
-               if (!(supported & JO_ERR_MODE))
-                   break;
-               if (handle_mode(item, opt, &opt->jo_err_mode, JO_ERR_MODE)
-                                                                     == FAIL)
-                   return FAIL;
-           }
-           else if (STRCMP(hi->hi_key, "in-io") == 0
-                   || STRCMP(hi->hi_key, "out-io") == 0
-                   || STRCMP(hi->hi_key, "err-io") == 0)
-           {
-               if (!(supported & JO_OUT_IO))
-                   break;
-               if (handle_io(item, part_from_char(*hi->hi_key), opt) == FAIL)
-                   return FAIL;
-           }
-           else if (STRCMP(hi->hi_key, "in-name") == 0
-                   || STRCMP(hi->hi_key, "out-name") == 0
-                   || STRCMP(hi->hi_key, "err-name") == 0)
-           {
-               part = part_from_char(*hi->hi_key);
- 
-               if (!(supported & JO_OUT_IO))
-                   break;
-               opt->jo_set |= JO_OUT_NAME << (part - PART_OUT);
-               opt->jo_io_name[part] =
-                      get_tv_string_buf_chk(item, opt->jo_io_name_buf[part]);
-           }
-           else if (STRCMP(hi->hi_key, "in-buf") == 0
-                   || STRCMP(hi->hi_key, "out-buf") == 0
-                   || STRCMP(hi->hi_key, "err-buf") == 0)
-           {
-               part = part_from_char(*hi->hi_key);
- 
-               if (!(supported & JO_OUT_IO))
-                   break;
-               opt->jo_set |= JO_OUT_BUF << (part - PART_OUT);
-               opt->jo_io_buf[part] = get_tv_number(item);
-               if (opt->jo_io_buf[part] <= 0)
-               {
-                   EMSG2(_(e_invarg2), get_tv_string(item));
-                   return FAIL;
-               }
-               if (buflist_findnr(opt->jo_io_buf[part]) == NULL)
-               {
-                   EMSGN(_(e_nobufnr), (long)opt->jo_io_buf[part]);
-                   return FAIL;
-               }
-           }
-           else if (STRCMP(hi->hi_key, "in-top") == 0
-                   || STRCMP(hi->hi_key, "in-bot") == 0)
-           {
-               linenr_T *lp;
- 
-               if (!(supported & JO_OUT_IO))
-                   break;
-               if (hi->hi_key[3] == 't')
-               {
-                   lp = &opt->jo_in_top;
-                   opt->jo_set |= JO_IN_TOP;
-               }
-               else
-               {
-                   lp = &opt->jo_in_bot;
-                   opt->jo_set |= JO_IN_BOT;
-               }
-               *lp = get_tv_number(item);
-               if (*lp < 0)
-               {
-                   EMSG2(_(e_invarg2), get_tv_string(item));
-                   return FAIL;
-               }
-           }
-           else if (STRCMP(hi->hi_key, "channel") == 0)
-           {
-               if (!(supported & JO_OUT_IO))
-                   break;
-               opt->jo_set |= JO_CHANNEL;
-               if (item->v_type != VAR_CHANNEL)
-               {
-                   EMSG2(_(e_invarg2), "channel");
-                   return FAIL;
-               }
-               opt->jo_channel = item->vval.v_channel;
-           }
-           else if (STRCMP(hi->hi_key, "callback") == 0)
-           {
-               if (!(supported & JO_CALLBACK))
-                   break;
-               opt->jo_set |= JO_CALLBACK;
-               opt->jo_callback = get_callback(item);
-               if (opt->jo_callback == NULL)
-               {
-                   EMSG2(_(e_invarg2), "callback");
-                   return FAIL;
-               }
-           }
-           else if (STRCMP(hi->hi_key, "out-cb") == 0)
-           {
-               if (!(supported & JO_OUT_CALLBACK))
-                   break;
-               opt->jo_set |= JO_OUT_CALLBACK;
-               opt->jo_out_cb = get_callback(item);
-               if (opt->jo_out_cb == NULL)
-               {
-                   EMSG2(_(e_invarg2), "out-cb");
-                   return FAIL;
-               }
-           }
-           else if (STRCMP(hi->hi_key, "err-cb") == 0)
-           {
-               if (!(supported & JO_ERR_CALLBACK))
-                   break;
-               opt->jo_set |= JO_ERR_CALLBACK;
-               opt->jo_err_cb = get_callback(item);
-               if (opt->jo_err_cb == NULL)
-               {
-                   EMSG2(_(e_invarg2), "err-cb");
-                   return FAIL;
-               }
-           }
-           else if (STRCMP(hi->hi_key, "close-cb") == 0)
-           {
-               if (!(supported & JO_CLOSE_CALLBACK))
-                   break;
-               opt->jo_set |= JO_CLOSE_CALLBACK;
-               opt->jo_close_cb = get_callback(item);
-               if (opt->jo_close_cb == NULL)
-               {
-                   EMSG2(_(e_invarg2), "close-cb");
-                   return FAIL;
-               }
-           }
-           else if (STRCMP(hi->hi_key, "waittime") == 0)
-           {
-               if (!(supported & JO_WAITTIME))
-                   break;
-               opt->jo_set |= JO_WAITTIME;
-               opt->jo_waittime = get_tv_number(item);
-           }
-           else if (STRCMP(hi->hi_key, "timeout") == 0)
-           {
-               if (!(supported & JO_TIMEOUT))
-                   break;
-               opt->jo_set |= JO_TIMEOUT;
-               opt->jo_timeout = get_tv_number(item);
-           }
-           else if (STRCMP(hi->hi_key, "out-timeout") == 0)
-           {
-               if (!(supported & JO_OUT_TIMEOUT))
-                   break;
-               opt->jo_set |= JO_OUT_TIMEOUT;
-               opt->jo_out_timeout = get_tv_number(item);
-           }
-           else if (STRCMP(hi->hi_key, "err-timeout") == 0)
-           {
-               if (!(supported & JO_ERR_TIMEOUT))
-                   break;
-               opt->jo_set |= JO_ERR_TIMEOUT;
-               opt->jo_err_timeout = get_tv_number(item);
-           }
-           else if (STRCMP(hi->hi_key, "part") == 0)
-           {
-               if (!(supported & JO_PART))
-                   break;
-               opt->jo_set |= JO_PART;
-               val = get_tv_string(item);
-               if (STRCMP(val, "err") == 0)
-                   opt->jo_part = PART_ERR;
-               else
-               {
-                   EMSG2(_(e_invarg2), val);
-                   return FAIL;
-               }
-           }
-           else if (STRCMP(hi->hi_key, "id") == 0)
-           {
-               if (!(supported & JO_ID))
-                   break;
-               opt->jo_set |= JO_ID;
-               opt->jo_id = get_tv_number(item);
-           }
-           else if (STRCMP(hi->hi_key, "stoponexit") == 0)
-           {
-               if (!(supported & JO_STOPONEXIT))
-                   break;
-               opt->jo_set |= JO_STOPONEXIT;
-               opt->jo_stoponexit = get_tv_string_buf_chk(item,
-                                                            opt->jo_soe_buf);
-               if (opt->jo_stoponexit == NULL)
-               {
-                   EMSG2(_(e_invarg2), "stoponexit");
-                   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_exit_cb == NULL)
-               {
-                   EMSG2(_(e_invarg2), "exit-cb");
-                   return FAIL;
-               }
-           }
-           else
-               break;
-           --todo;
-       }
-     if (todo > 0)
-     {
-       EMSG2(_(e_invarg2), hi->hi_key);
-       return FAIL;
-     }
- 
-     return OK;
- }
- #endif
- 
  #ifdef FEAT_JOB_CHANNEL
  /*
-  * Get the channel from the argument.
-  * Returns NULL if the handle is invalid.
-  */
-     static channel_T *
- get_channel_arg(typval_T *tv, int check_open)
- {
-     channel_T *channel = NULL;
- 
-     if (tv->v_type == VAR_JOB)
-     {
-       if (tv->vval.v_job != NULL)
-           channel = tv->vval.v_job->jv_channel;
-     }
-     else if (tv->v_type == VAR_CHANNEL)
-     {
-       channel = tv->vval.v_channel;
-     }
-     else
-     {
-       EMSG2(_(e_invarg2), get_tv_string(tv));
-       return NULL;
-     }
- 
-     if (check_open && (channel == NULL || !channel_is_open(channel)))
-     {
-       EMSG(_("E906: not an open channel"));
-       return NULL;
-     }
-     return channel;
- }
- 
- /*
   * "ch_close()" function
   */
      static void
--- 9815,9822 ----
***************
*** 10366,10681 ****
            part = PART_OUT;
        else if (STRCMP(what, "in") == 0)
            part = PART_IN;
!       else
!           part = PART_SOCK;
!       if (channel->ch_part[part].ch_buffer != NULL)
!           rettv->vval.v_number = channel->ch_part[part].ch_buffer->b_fnum;
!     }
! }
! 
! /*
!  * "ch_getjob()" function
!  */
!     static void
! f_ch_getjob(typval_T *argvars, typval_T *rettv)
! {
!     channel_T *channel = get_channel_arg(&argvars[0], TRUE);
! 
!     if (channel != NULL)
!     {
!       rettv->v_type = VAR_JOB;
!       rettv->vval.v_job = channel->ch_job;
!       if (channel->ch_job != NULL)
!           ++channel->ch_job->jv_refcount;
!     }
! }
! 
! /*
!  * "ch_log()" function
!  */
!     static void
! f_ch_log(typval_T *argvars, typval_T *rettv UNUSED)
! {
!     char_u    *msg = get_tv_string(&argvars[0]);
!     channel_T *channel = NULL;
! 
!     if (argvars[1].v_type != VAR_UNKNOWN)
!       channel = get_channel_arg(&argvars[1], TRUE);
! 
!     ch_log(channel, (char *)msg);
! }
! 
! /*
!  * "ch_logfile()" function
!  */
!     static void
! f_ch_logfile(typval_T *argvars, typval_T *rettv UNUSED)
! {
!     char_u *fname;
!     char_u *opt = (char_u *)"";
!     char_u buf[NUMBUFLEN];
!     FILE   *file = NULL;
! 
!     fname = get_tv_string(&argvars[0]);
!     if (argvars[1].v_type == VAR_STRING)
!       opt = get_tv_string_buf(&argvars[1], buf);
!     if (*fname != NUL)
!     {
!       file = fopen((char *)fname, *opt == 'w' ? "w" : "a");
!       if (file == NULL)
!       {
!           EMSG2(_(e_notopen), fname);
!           return;
!       }
!     }
!     ch_logfile(file);
! }
! 
! /*
!  * "ch_open()" function
!  */
!     static void
! f_ch_open(typval_T *argvars, typval_T *rettv)
! {
!     char_u    *address;
!     char_u    *p;
!     char      *rest;
!     int               port;
!     jobopt_T    opt;
!     channel_T *channel;
! 
!     /* default: fail */
!     rettv->v_type = VAR_CHANNEL;
!     rettv->vval.v_channel = NULL;
! 
!     address = get_tv_string(&argvars[0]);
!     if (argvars[1].v_type != VAR_UNKNOWN
!        && (argvars[1].v_type != VAR_DICT || argvars[1].vval.v_dict == NULL))
!     {
!       EMSG(_(e_invarg));
!       return;
!     }
! 
!     /* parse address */
!     p = vim_strchr(address, ':');
!     if (p == NULL)
!     {
!       EMSG2(_(e_invarg2), address);
!       return;
!     }
!     *p++ = NUL;
!     port = strtol((char *)p, &rest, 10);
!     if (*address == NUL || port <= 0 || *rest != NUL)
!     {
!       p[-1] = ':';
!       EMSG2(_(e_invarg2), address);
!       return;
!     }
! 
!     /* parse options */
!     clear_job_options(&opt);
!     opt.jo_mode = MODE_JSON;
!     opt.jo_timeout = 2000;
!     if (get_job_options(&argvars[1], &opt,
!             JO_MODE_ALL + JO_CB_ALL + JO_WAITTIME + JO_TIMEOUT_ALL) == FAIL)
!       return;
!     if (opt.jo_timeout < 0)
!     {
!       EMSG(_(e_invarg));
!       return;
      }
  
-     channel = channel_open((char *)address, port, opt.jo_waittime, NULL);
      if (channel != NULL)
      {
!       rettv->vval.v_channel = channel;
!       opt.jo_set = JO_ALL;
!       channel_set_options(channel, &opt);
      }
  }
  
  /*
!  * Common for ch_read() and ch_readraw().
   */
      static void
! common_channel_read(typval_T *argvars, typval_T *rettv, int raw)
  {
!     channel_T *channel;
!     int               part;
!     jobopt_T  opt;
!     int               mode;
!     int               timeout;
!     int               id = -1;
!     typval_T  *listtv = NULL;
! 
!     /* return an empty string by default */
!     rettv->v_type = VAR_STRING;
!     rettv->vval.v_string = NULL;
  
!     clear_job_options(&opt);
!     if (get_job_options(&argvars[1], &opt, JO_TIMEOUT + JO_PART + JO_ID)
!                                                                     == FAIL)
!       return;
  
!     channel = get_channel_arg(&argvars[0], TRUE);
!     if (channel != NULL)
!     {
!       if (opt.jo_set & JO_PART)
!           part = opt.jo_part;
!       else
!           part = channel_part_read(channel);
!       mode = channel_get_mode(channel, part);
!       timeout = channel_get_timeout(channel, part);
!       if (opt.jo_set & JO_TIMEOUT)
!           timeout = opt.jo_timeout;
! 
!       if (raw || mode == MODE_RAW || mode == MODE_NL)
!           rettv->vval.v_string = channel_read_block(channel, part, timeout);
!       else
!       {
!           if (opt.jo_set & JO_ID)
!               id = opt.jo_id;
!           channel_read_json_block(channel, part, timeout, id, &listtv);
!           if (listtv != NULL)
!           {
!               *rettv = *listtv;
!               vim_free(listtv);
!           }
!           else
!           {
!               rettv->v_type = VAR_SPECIAL;
!               rettv->vval.v_number = VVAL_NONE;
!           }
!       }
!     }
  }
  
  /*
!  * "ch_read()" function
   */
      static void
! f_ch_read(typval_T *argvars, typval_T *rettv)
  {
!     common_channel_read(argvars, rettv, FALSE);
  }
  
  /*
!  * "ch_readraw()" function
   */
      static void
! f_ch_readraw(typval_T *argvars, typval_T *rettv)
  {
!     common_channel_read(argvars, rettv, TRUE);
  }
  
  /*
!  * common for "sendexpr()" and "sendraw()"
!  * Returns the channel if the caller should read the response.
!  * Sets "part_read" to the the read fd.
!  * Otherwise returns NULL.
!  */
!     static channel_T *
! send_common(
!       typval_T    *argvars,
!       char_u      *text,
!       int         id,
!       int         eval,
!       jobopt_T    *opt,
!       char        *fun,
!       int         *part_read)
  {
!     channel_T *channel;
!     int               part_send;
! 
!     channel = get_channel_arg(&argvars[0], TRUE);
!     if (channel == NULL)
!       return NULL;
!     part_send = channel_part_send(channel);
!     *part_read = channel_part_read(channel);
! 
!     clear_job_options(opt);
!     if (get_job_options(&argvars[2], opt, JO_CALLBACK + JO_TIMEOUT) == FAIL)
!       return NULL;
! 
!     /* Set the callback. An empty callback means no callback and not reading
!      * the response. With "ch_evalexpr()" and "ch_evalraw()" a callback is not
!      * allowed. */
!     if (opt->jo_callback != NULL && *opt->jo_callback != NUL)
!     {
!       if (eval)
!       {
!           EMSG2(_("E917: Cannot use a callback with %s()"), fun);
!           return NULL;
!       }
!       channel_set_req_callback(channel, part_send, opt->jo_callback, id);
!     }
! 
!     if (channel_send(channel, part_send, text, fun) == OK
!                                                 && opt->jo_callback == NULL)
!       return channel;
!     return NULL;
  }
  
  /*
!  * common for "ch_evalexpr()" and "ch_sendexpr()"
   */
      static void
! ch_expr_common(typval_T *argvars, typval_T *rettv, int eval)
  {
!     char_u    *text;
!     typval_T  *listtv;
!     channel_T *channel;
!     int               id;
!     ch_mode_T ch_mode;
!     int               part_send;
!     int               part_read;
!     jobopt_T    opt;
!     int               timeout;
! 
!     /* return an empty string by default */
!     rettv->v_type = VAR_STRING;
!     rettv->vval.v_string = NULL;
! 
!     channel = get_channel_arg(&argvars[0], TRUE);
!     if (channel == NULL)
!       return;
!     part_send = channel_part_send(channel);
! 
!     ch_mode = channel_get_mode(channel, part_send);
!     if (ch_mode == MODE_RAW || ch_mode == MODE_NL)
!     {
!       EMSG(_("E912: cannot use ch_evalexpr()/ch_sendexpr() with a raw or nl 
channel"));
!       return;
!     }
! 
!     id = channel_get_id();
!     text = json_encode_nr_expr(id, &argvars[1],
!                                           ch_mode == MODE_JS ? JSON_JS : 0);
!     if (text == NULL)
!       return;
! 
!     channel = send_common(argvars, text, id, eval, &opt,
!                           eval ? "ch_evalexpr" : "ch_sendexpr", &part_read);
!     vim_free(text);
!     if (channel != NULL && eval)
!     {
!       if (opt.jo_set & JO_TIMEOUT)
!           timeout = opt.jo_timeout;
!       else
!           timeout = channel_get_timeout(channel, part_read);
!       timeout = channel_get_timeout(channel, part_read);
!       if (channel_read_json_block(channel, part_read, timeout, id, &listtv)
!                                                                       == OK)
!       {
!           list_T *list = listtv->vval.v_list;
! 
!           /* Move the item from the list and then change the type to
!            * avoid the value being freed. */
!           *rettv = list->lv_last->li_tv;
!           list->lv_last->li_tv.v_type = VAR_NUMBER;
!           free_tv(listtv);
!       }
!     }
  }
  
  /*
--- 9851,9937 ----
            part = PART_OUT;
        else if (STRCMP(what, "in") == 0)
            part = PART_IN;
!       else
!           part = PART_SOCK;
!       if (channel->ch_part[part].ch_buffer != NULL)
!           rettv->vval.v_number = channel->ch_part[part].ch_buffer->b_fnum;
      }
+ }
+ 
+ /*
+  * "ch_getjob()" function
+  */
+     static void
+ f_ch_getjob(typval_T *argvars, typval_T *rettv)
+ {
+     channel_T *channel = get_channel_arg(&argvars[0], TRUE);
  
      if (channel != NULL)
      {
!       rettv->v_type = VAR_JOB;
!       rettv->vval.v_job = channel->ch_job;
!       if (channel->ch_job != NULL)
!           ++channel->ch_job->jv_refcount;
      }
  }
  
  /*
!  * "ch_log()" function
   */
      static void
! f_ch_log(typval_T *argvars, typval_T *rettv UNUSED)
  {
!     char_u    *msg = get_tv_string(&argvars[0]);
!     channel_T *channel = NULL;
  
!     if (argvars[1].v_type != VAR_UNKNOWN)
!       channel = get_channel_arg(&argvars[1], TRUE);
  
!     ch_log(channel, (char *)msg);
  }
  
  /*
!  * "ch_logfile()" function
   */
      static void
! f_ch_logfile(typval_T *argvars, typval_T *rettv UNUSED)
  {
!     char_u *fname;
!     char_u *opt = (char_u *)"";
!     char_u buf[NUMBUFLEN];
! 
!     fname = get_tv_string(&argvars[0]);
!     if (argvars[1].v_type == VAR_STRING)
!       opt = get_tv_string_buf(&argvars[1], buf);
!     ch_logfile(fname, opt);
  }
  
  /*
!  * "ch_open()" function
   */
      static void
! f_ch_open(typval_T *argvars, typval_T *rettv)
  {
!     rettv->v_type = VAR_CHANNEL;
!     rettv->vval.v_channel = channel_open_func(argvars);
  }
  
  /*
!  * "ch_read()" function
!  */
!     static void
! f_ch_read(typval_T *argvars, typval_T *rettv)
  {
!     common_channel_read(argvars, rettv, FALSE);
  }
  
  /*
!  * "ch_readraw()" function
   */
      static void
! f_ch_readraw(typval_T *argvars, typval_T *rettv)
  {
!     common_channel_read(argvars, rettv, TRUE);
  }
  
  /*
***************
*** 10697,10732 ****
  }
  
  /*
-  * common for "ch_evalraw()" and "ch_sendraw()"
-  */
-     static void
- ch_raw_common(typval_T *argvars, typval_T *rettv, int eval)
- {
-     char_u    buf[NUMBUFLEN];
-     char_u    *text;
-     channel_T *channel;
-     int               part_read;
-     jobopt_T    opt;
-     int               timeout;
- 
-     /* return an empty string by default */
-     rettv->v_type = VAR_STRING;
-     rettv->vval.v_string = NULL;
- 
-     text = get_tv_string_buf(&argvars[1], buf);
-     channel = send_common(argvars, text, 0, eval, &opt,
-                             eval ? "ch_evalraw" : "ch_sendraw", &part_read);
-     if (channel != NULL && eval)
-     {
-       if (opt.jo_set & JO_TIMEOUT)
-           timeout = opt.jo_timeout;
-       else
-           timeout = channel_get_timeout(channel, part_read);
-       rettv->vval.v_string = channel_read_block(channel, part_read, timeout);
-     }
- }
- 
- /*
   * "ch_evalraw()" function
   */
      static void
--- 9953,9958 ----
***************
*** 15138,15387 ****
      static void
  f_job_start(typval_T *argvars, typval_T *rettv)
  {
-     job_T     *job;
-     char_u    *cmd = NULL;
- #if defined(UNIX)
- # define USE_ARGV
-     char      **argv = NULL;
-     int               argc = 0;
- #else
-     garray_T  ga;
- #endif
-     jobopt_T  opt;
-     int               part;
- 
      rettv->v_type = VAR_JOB;
!     job = job_alloc();
!     rettv->vval.v_job = job;
!     if (job == NULL)
!       return;
! 
!     rettv->vval.v_job->jv_status = JOB_FAILED;
! 
!     /* Default mode is NL. */
!     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 + JO_EXIT_CB + JO_OUT_IO) == FAIL)
!       return;
! 
!     /* Check that when io is "file" that there is a file name. */
!     for (part = PART_OUT; part <= PART_IN; ++part)
!       if ((opt.jo_set & (JO_OUT_IO << (part - PART_OUT)))
!               && opt.jo_io[part] == JIO_FILE
!               && (!(opt.jo_set & (JO_OUT_NAME << (part - PART_OUT)))
!                   || *opt.jo_io_name[part] == NUL))
!       {
!           EMSG(_("E920: -io file requires -name to be set"));
!           return;
!       }
! 
!     if ((opt.jo_set & JO_IN_IO) && opt.jo_io[PART_IN] == JIO_BUFFER)
!     {
!       buf_T *buf = NULL;
! 
!       /* check that we can find the buffer before starting the job */
!       if (opt.jo_set & JO_IN_BUF)
!       {
!           buf = buflist_findnr(opt.jo_io_buf[PART_IN]);
!           if (buf == NULL)
!               EMSGN(_(e_nobufnr), (long)opt.jo_io_buf[PART_IN]);
!       }
!       else if (!(opt.jo_set & JO_IN_NAME))
!       {
!           EMSG(_("E915: in-io buffer requires in-buf or in-name to be set"));
!       }
!       else
!           buf = buflist_find_by_name(opt.jo_io_name[PART_IN], FALSE);
!       if (buf == NULL)
!           return;
!       if (buf->b_ml.ml_mfp == NULL)
!       {
!           char_u      numbuf[NUMBUFLEN];
!           char_u      *s;
! 
!           if (opt.jo_set & JO_IN_BUF)
!           {
!               sprintf((char *)numbuf, "%d", opt.jo_io_buf[PART_IN]);
!               s = numbuf;
!           }
!           else
!               s = opt.jo_io_name[PART_IN];
!           EMSG2(_("E918: buffer must be loaded: %s"), s);
!           return;
!       }
!       job->jv_in_buf = buf;
!     }
! 
!     job_set_options(job, &opt);
! 
! #ifndef USE_ARGV
!     ga_init2(&ga, (int)sizeof(char*), 20);
! #endif
! 
!     if (argvars[0].v_type == VAR_STRING)
!     {
!       /* Command is a string. */
!       cmd = argvars[0].vval.v_string;
! #ifdef USE_ARGV
!       if (mch_parse_cmd(cmd, FALSE, &argv, &argc) == FAIL)
!           return;
!       argv[argc] = NULL;
! #endif
!     }
!     else if (argvars[0].v_type != VAR_LIST
!           || argvars[0].vval.v_list == NULL
!           || argvars[0].vval.v_list->lv_len < 1)
!     {
!       EMSG(_(e_invarg));
!       return;
!     }
!     else
!     {
!       list_T      *l = argvars[0].vval.v_list;
!       listitem_T  *li;
!       char_u      *s;
! 
! #ifdef USE_ARGV
!       /* Pass argv[] to mch_call_shell(). */
!       argv = (char **)alloc(sizeof(char *) * (l->lv_len + 1));
!       if (argv == NULL)
!           return;
! #endif
!       for (li = l->lv_first; li != NULL; li = li->li_next)
!       {
!           s = get_tv_string_chk(&li->li_tv);
!           if (s == NULL)
!               goto theend;
! #ifdef USE_ARGV
!           argv[argc++] = (char *)s;
! #else
!           /* Only escape when needed, double quotes are not always allowed. */
!           if (li != l->lv_first && vim_strpbrk(s, (char_u *)" \t\"") != NULL)
!           {
!               s = vim_strsave_shellescape(s, FALSE, TRUE);
!               if (s == NULL)
!                   goto theend;
!               ga_concat(&ga, s);
!               vim_free(s);
!           }
!           else
!               ga_concat(&ga, s);
!           if (li->li_next != NULL)
!               ga_append(&ga, ' ');
! #endif
!       }
! #ifdef USE_ARGV
!       argv[argc] = NULL;
! #else
!       cmd = ga.ga_data;
! #endif
!     }
! 
! #ifdef USE_ARGV
!     if (ch_log_active())
!     {
!       garray_T    ga;
!       int         i;
! 
!       ga_init2(&ga, (int)sizeof(char), 200);
!       for (i = 0; i < argc; ++i)
!       {
!           if (i > 0)
!               ga_concat(&ga, (char_u *)"  ");
!           ga_concat(&ga, (char_u *)argv[i]);
!       }
!       ch_logs(NULL, "Starting job: %s", (char *)ga.ga_data);
!       ga_clear(&ga);
!     }
!     mch_start_job(argv, job, &opt);
! #else
!     ch_logs(NULL, "Starting job: %s", (char *)cmd);
!     mch_start_job((char *)cmd, job, &opt);
! #endif
! 
!     /* If the channel is reading from a buffer, write lines now. */
!     if (job->jv_channel != NULL)
!       channel_write_in(job->jv_channel);
! 
! theend:
! #ifdef USE_ARGV
!     vim_free(argv);
! #else
!     vim_free(ga.ga_data);
! #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);
!       if (job->jv_status == JOB_ENDED)
!           ch_log(job->jv_channel, "Job ended");
!       if (job->jv_status == JOB_ENDED && job->jv_exit_cb != NULL)
!       {
!           typval_T    argv[3];
!           typval_T    rettv;
!           int         dummy;
! 
!           /* invoke the exit callback; make sure the refcount is > 0 */
!           ++job->jv_refcount;
!           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);
!           --job->jv_refcount;
!       }
!       if (job->jv_status == JOB_ENDED && job->jv_refcount == 0)
!       {
!           /* The job was already 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(void)
! {
!     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" */
!       }
!     }
  }
  
  /*
--- 14364,14371 ----
      static void
  f_job_start(typval_T *argvars, typval_T *rettv)
  {
      rettv->v_type = VAR_JOB;
!     rettv->vval.v_job = job_start(argvars);
  }
  
  /*
***************
*** 15405,15442 ****
   * "job_stop()" function
   */
      static void
! f_job_stop(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
  {
      job_T     *job = get_job_arg(&argvars[0]);
  
      if (job != NULL)
!     {
!       char_u *arg;
! 
!       if (argvars[1].v_type == VAR_UNKNOWN)
!           arg = (char_u *)"";
!       else
!       {
!           arg = get_tv_string_chk(&argvars[1]);
!           if (arg == NULL)
!           {
!               EMSG(_(e_invarg));
!               return;
!           }
!       }
!       ch_logs(job->jv_channel, "Stopping job with '%s'", (char *)arg);
!       if (mch_stop_job(job, arg) == FAIL)
!           rettv->vval.v_number = 0;
!       else
!       {
!           rettv->vval.v_number = 1;
!           /* Assume that "hup" does not kill the job. */
!           if (job->jv_channel != NULL && STRCMP(arg, "hup") != 0)
!               job->jv_channel->ch_job_killed = TRUE;
!       }
!       /* We don't try freeing the job, obviously the caller still has a
!        * reference to it. */
!     }
  }
  #endif
  
--- 14389,14400 ----
   * "job_stop()" function
   */
      static void
! f_job_stop(typval_T *argvars, typval_T *rettv)
  {
      job_T     *job = get_job_arg(&argvars[0]);
  
      if (job != NULL)
!       rettv->vval.v_number = job_stop(job, argvars);
  }
  #endif
  
***************
*** 22475,22481 ****
   * caller of incompatible types: it sets *denote to TRUE if "denote"
   * is not NULL or returns -1 otherwise.
   */
!     static long
  get_tv_number(typval_T *varp)
  {
      int               error = FALSE;
--- 21433,21439 ----
   * caller of incompatible types: it sets *denote to TRUE if "denote"
   * is not NULL or returns -1 otherwise.
   */
!     long
  get_tv_number(typval_T *varp)
  {
      int               error = FALSE;
***************
*** 22626,22632 ****
   * get_tv_string_chk() and get_tv_string_buf_chk() are similar, but return
   * NULL on error.
   */
!     static char_u *
  get_tv_string(typval_T *varp)
  {
      static char_u   mybuf[NUMBUFLEN];
--- 21584,21590 ----
   * get_tv_string_chk() and get_tv_string_buf_chk() are similar, but return
   * NULL on error.
   */
!     char_u *
  get_tv_string(typval_T *varp)
  {
      static char_u   mybuf[NUMBUFLEN];
***************
*** 22634,22640 ****
      return get_tv_string_buf(varp, mybuf);
  }
  
!     static char_u *
  get_tv_string_buf(typval_T *varp, char_u *buf)
  {
      char_u    *res =  get_tv_string_buf_chk(varp, buf);
--- 21592,21598 ----
      return get_tv_string_buf(varp, mybuf);
  }
  
!     char_u *
  get_tv_string_buf(typval_T *varp, char_u *buf)
  {
      char_u    *res =  get_tv_string_buf_chk(varp, buf);
*** ../vim-7.4.1538/src/channel.c       2016-03-11 22:52:00.726438199 +0100
--- src/channel.c       2016-03-12 13:33:47.438483356 +0100
***************
*** 97,107 ****
  #endif
  
      void
! ch_logfile(FILE *file)
  {
      if (log_fd != NULL)
        fclose(log_fd);
      log_fd = file;
      if (log_fd != NULL)
      {
        fprintf(log_fd, "==== start log session ====\n");
--- 97,120 ----
  #endif
  
      void
! ch_logfile(char_u *fname, char_u *opt)
  {
+     FILE   *file = NULL;
+ 
      if (log_fd != NULL)
        fclose(log_fd);
+ 
+     if (*fname != NUL)
+     {
+       file = fopen((char *)fname, *opt == 'w' ? "w" : "a");
+       if (file == NULL)
+       {
+           EMSG2(_(e_notopen), fname);
+           return;
+       }
+     }
      log_fd = file;
+ 
      if (log_fd != NULL)
      {
        fprintf(log_fd, "==== start log session ====\n");
***************
*** 360,366 ****
   * killed.
   * Return TRUE if the channel was freed.
   */
!     int
  channel_may_free(channel_T *channel)
  {
      if (!channel_still_useful(channel))
--- 373,379 ----
   * killed.
   * Return TRUE if the channel was freed.
   */
!     static int
  channel_may_free(channel_T *channel)
  {
      if (!channel_still_useful(channel))
***************
*** 372,377 ****
--- 385,403 ----
  }
  
  /*
+  * Decrement the reference count on "channel" and maybe free it when it goes
+  * down to zero.  Don't free it if there is a pending action.
+  * Returns TRUE when the channel is no longer referenced.
+  */
+     int
+ channel_unref(channel_T *channel)
+ {
+     if (channel != NULL && --channel->ch_refcount <= 0)
+       return channel_may_free(channel);
+     return FALSE;
+ }
+ 
+ /*
   * Close a channel and free all its resources.
   */
      void
***************
*** 820,825 ****
--- 846,910 ----
      return channel;
  }
  
+ /*
+  * Implements ch_open().
+  */
+     channel_T *
+ channel_open_func(typval_T *argvars)
+ {
+     char_u    *address;
+     char_u    *p;
+     char      *rest;
+     int               port;
+     jobopt_T    opt;
+     channel_T *channel;
+ 
+     address = get_tv_string(&argvars[0]);
+     if (argvars[1].v_type != VAR_UNKNOWN
+        && (argvars[1].v_type != VAR_DICT || argvars[1].vval.v_dict == NULL))
+     {
+       EMSG(_(e_invarg));
+       return NULL;
+     }
+ 
+     /* parse address */
+     p = vim_strchr(address, ':');
+     if (p == NULL)
+     {
+       EMSG2(_(e_invarg2), address);
+       return NULL;
+     }
+     *p++ = NUL;
+     port = strtol((char *)p, &rest, 10);
+     if (*address == NUL || port <= 0 || *rest != NUL)
+     {
+       p[-1] = ':';
+       EMSG2(_(e_invarg2), address);
+       return NULL;
+     }
+ 
+     /* parse options */
+     clear_job_options(&opt);
+     opt.jo_mode = MODE_JSON;
+     opt.jo_timeout = 2000;
+     if (get_job_options(&argvars[1], &opt,
+             JO_MODE_ALL + JO_CB_ALL + JO_WAITTIME + JO_TIMEOUT_ALL) == FAIL)
+       return NULL;
+     if (opt.jo_timeout < 0)
+     {
+       EMSG(_(e_invarg));
+       return NULL;
+     }
+ 
+     channel = channel_open((char *)address, port, opt.jo_waittime, NULL);
+     if (channel != NULL)
+     {
+       opt.jo_set = JO_ALL;
+       channel_set_options(channel, &opt);
+     }
+     return channel;
+ }
+ 
      static void
  may_close_part(sock_T *fd)
  {
***************
*** 2307,2312 ****
--- 2392,2453 ----
      return FAIL;
  }
  
+ /*
+  * Common for ch_read() and ch_readraw().
+  */
+     void
+ common_channel_read(typval_T *argvars, typval_T *rettv, int raw)
+ {
+     channel_T *channel;
+     int               part;
+     jobopt_T  opt;
+     int               mode;
+     int               timeout;
+     int               id = -1;
+     typval_T  *listtv = NULL;
+ 
+     /* return an empty string by default */
+     rettv->v_type = VAR_STRING;
+     rettv->vval.v_string = NULL;
+ 
+     clear_job_options(&opt);
+     if (get_job_options(&argvars[1], &opt, JO_TIMEOUT + JO_PART + JO_ID)
+                                                                     == FAIL)
+       return;
+ 
+     channel = get_channel_arg(&argvars[0], TRUE);
+     if (channel != NULL)
+     {
+       if (opt.jo_set & JO_PART)
+           part = opt.jo_part;
+       else
+           part = channel_part_read(channel);
+       mode = channel_get_mode(channel, part);
+       timeout = channel_get_timeout(channel, part);
+       if (opt.jo_set & JO_TIMEOUT)
+           timeout = opt.jo_timeout;
+ 
+       if (raw || mode == MODE_RAW || mode == MODE_NL)
+           rettv->vval.v_string = channel_read_block(channel, part, timeout);
+       else
+       {
+           if (opt.jo_set & JO_ID)
+               id = opt.jo_id;
+           channel_read_json_block(channel, part, timeout, id, &listtv);
+           if (listtv != NULL)
+           {
+               *rettv = *listtv;
+               vim_free(listtv);
+           }
+           else
+           {
+               rettv->v_type = VAR_SPECIAL;
+               rettv->vval.v_number = VVAL_NONE;
+           }
+       }
+     }
+ }
+ 
  # if defined(WIN32) || defined(FEAT_GUI_X11) || defined(FEAT_GUI_GTK) \
        || defined(PROTO)
  /*
***************
*** 2412,2417 ****
--- 2553,2698 ----
      return OK;
  }
  
+ /*
+  * Common for "ch_sendexpr()" and "ch_sendraw()".
+  * Returns the channel if the caller should read the response.
+  * Sets "part_read" to the the read fd.
+  * Otherwise returns NULL.
+  */
+     channel_T *
+ send_common(
+       typval_T    *argvars,
+       char_u      *text,
+       int         id,
+       int         eval,
+       jobopt_T    *opt,
+       char        *fun,
+       int         *part_read)
+ {
+     channel_T *channel;
+     int               part_send;
+ 
+     channel = get_channel_arg(&argvars[0], TRUE);
+     if (channel == NULL)
+       return NULL;
+     part_send = channel_part_send(channel);
+     *part_read = channel_part_read(channel);
+ 
+     clear_job_options(opt);
+     if (get_job_options(&argvars[2], opt, JO_CALLBACK + JO_TIMEOUT) == FAIL)
+       return NULL;
+ 
+     /* Set the callback. An empty callback means no callback and not reading
+      * the response. With "ch_evalexpr()" and "ch_evalraw()" a callback is not
+      * allowed. */
+     if (opt->jo_callback != NULL && *opt->jo_callback != NUL)
+     {
+       if (eval)
+       {
+           EMSG2(_("E917: Cannot use a callback with %s()"), fun);
+           return NULL;
+       }
+       channel_set_req_callback(channel, part_send, opt->jo_callback, id);
+     }
+ 
+     if (channel_send(channel, part_send, text, fun) == OK
+                                                 && opt->jo_callback == NULL)
+       return channel;
+     return NULL;
+ }
+ 
+ /*
+  * common for "ch_evalexpr()" and "ch_sendexpr()"
+  */
+     void
+ ch_expr_common(typval_T *argvars, typval_T *rettv, int eval)
+ {
+     char_u    *text;
+     typval_T  *listtv;
+     channel_T *channel;
+     int               id;
+     ch_mode_T ch_mode;
+     int               part_send;
+     int               part_read;
+     jobopt_T    opt;
+     int               timeout;
+ 
+     /* return an empty string by default */
+     rettv->v_type = VAR_STRING;
+     rettv->vval.v_string = NULL;
+ 
+     channel = get_channel_arg(&argvars[0], TRUE);
+     if (channel == NULL)
+       return;
+     part_send = channel_part_send(channel);
+ 
+     ch_mode = channel_get_mode(channel, part_send);
+     if (ch_mode == MODE_RAW || ch_mode == MODE_NL)
+     {
+       EMSG(_("E912: cannot use ch_evalexpr()/ch_sendexpr() with a raw or nl 
channel"));
+       return;
+     }
+ 
+     id = channel_get_id();
+     text = json_encode_nr_expr(id, &argvars[1],
+                                           ch_mode == MODE_JS ? JSON_JS : 0);
+     if (text == NULL)
+       return;
+ 
+     channel = send_common(argvars, text, id, eval, &opt,
+                           eval ? "ch_evalexpr" : "ch_sendexpr", &part_read);
+     vim_free(text);
+     if (channel != NULL && eval)
+     {
+       if (opt.jo_set & JO_TIMEOUT)
+           timeout = opt.jo_timeout;
+       else
+           timeout = channel_get_timeout(channel, part_read);
+       timeout = channel_get_timeout(channel, part_read);
+       if (channel_read_json_block(channel, part_read, timeout, id, &listtv)
+                                                                       == OK)
+       {
+           list_T *list = listtv->vval.v_list;
+ 
+           /* Move the item from the list and then change the type to
+            * avoid the value being freed. */
+           *rettv = list->lv_last->li_tv;
+           list->lv_last->li_tv.v_type = VAR_NUMBER;
+           free_tv(listtv);
+       }
+     }
+ }
+ 
+ /*
+  * common for "ch_evalraw()" and "ch_sendraw()"
+  */
+     void
+ ch_raw_common(typval_T *argvars, typval_T *rettv, int eval)
+ {
+     char_u    buf[NUMBUFLEN];
+     char_u    *text;
+     channel_T *channel;
+     int               part_read;
+     jobopt_T    opt;
+     int               timeout;
+ 
+     /* return an empty string by default */
+     rettv->v_type = VAR_STRING;
+     rettv->vval.v_string = NULL;
+ 
+     text = get_tv_string_buf(&argvars[1], buf);
+     channel = send_common(argvars, text, 0, eval, &opt,
+                             eval ? "ch_evalraw" : "ch_sendraw", &part_read);
+     if (channel != NULL && eval)
+     {
+       if (opt.jo_set & JO_TIMEOUT)
+           timeout = opt.jo_timeout;
+       else
+           timeout = channel_get_timeout(channel, part_read);
+       rettv->vval.v_string = channel_read_block(channel, part_read, timeout);
+     }
+ }
+ 
  # if (defined(UNIX) && !defined(HAVE_SELECT)) || defined(PROTO)
  /*
   * Add open channels to the poll struct.
***************
*** 2695,2698 ****
--- 2976,3757 ----
      return channel->ch_part[part].ch_timeout;
  }
  
+ /*
+  * Get a callback from "arg".  It can be a Funcref or a function name.
+  * When "arg" is zero return an empty string.
+  * Return NULL for an invalid argument.
+  */
+     static char_u *
+ get_callback(typval_T *arg)
+ {
+     if (arg->v_type == VAR_FUNC || arg->v_type == VAR_STRING)
+       return arg->vval.v_string;
+     if (arg->v_type == VAR_NUMBER && arg->vval.v_number == 0)
+       return (char_u *)"";
+     EMSG(_("E999: Invalid callback argument"));
+     return NULL;
+ }
+ 
+     static int
+ handle_mode(typval_T *item, jobopt_T *opt, ch_mode_T *modep, int jo)
+ {
+     char_u    *val = get_tv_string(item);
+ 
+     opt->jo_set |= jo;
+     if (STRCMP(val, "nl") == 0)
+       *modep = MODE_NL;
+     else if (STRCMP(val, "raw") == 0)
+       *modep = MODE_RAW;
+     else if (STRCMP(val, "js") == 0)
+       *modep = MODE_JS;
+     else if (STRCMP(val, "json") == 0)
+       *modep = MODE_JSON;
+     else
+     {
+       EMSG2(_(e_invarg2), val);
+       return FAIL;
+     }
+     return OK;
+ }
+ 
+     static int
+ handle_io(typval_T *item, int part, jobopt_T *opt)
+ {
+     char_u    *val = get_tv_string(item);
+ 
+     opt->jo_set |= JO_OUT_IO << (part - PART_OUT);
+     if (STRCMP(val, "null") == 0)
+       opt->jo_io[part] = JIO_NULL;
+     else if (STRCMP(val, "pipe") == 0)
+       opt->jo_io[part] = JIO_PIPE;
+     else if (STRCMP(val, "file") == 0)
+       opt->jo_io[part] = JIO_FILE;
+     else if (STRCMP(val, "buffer") == 0)
+       opt->jo_io[part] = JIO_BUFFER;
+     else if (STRCMP(val, "out") == 0 && part == PART_ERR)
+       opt->jo_io[part] = JIO_OUT;
+     else
+     {
+       EMSG2(_(e_invarg2), val);
+       return FAIL;
+     }
+     return OK;
+ }
+ 
+     void
+ clear_job_options(jobopt_T *opt)
+ {
+     vim_memset(opt, 0, sizeof(jobopt_T));
+ }
+ 
+ /*
+  * Get the PART_ number from the first character of an option name.
+  */
+     static int
+ part_from_char(int c)
+ {
+     return c == 'i' ? PART_IN : c == 'o' ? PART_OUT: PART_ERR;
+ }
+ 
+ /*
+  * Get the option entries from the dict in "tv", parse them and put the result
+  * in "opt".
+  * Only accept options in "supported".
+  * If an option value is invalid return FAIL.
+  */
+     int
+ get_job_options(typval_T *tv, jobopt_T *opt, int supported)
+ {
+     typval_T  *item;
+     char_u    *val;
+     dict_T    *dict;
+     int               todo;
+     hashitem_T        *hi;
+     int               part;
+ 
+     opt->jo_set = 0;
+     if (tv->v_type == VAR_UNKNOWN)
+       return OK;
+     if (tv->v_type != VAR_DICT)
+     {
+       EMSG(_(e_invarg));
+       return FAIL;
+     }
+     dict = tv->vval.v_dict;
+     if (dict == NULL)
+       return OK;
+ 
+     todo = (int)dict->dv_hashtab.ht_used;
+     for (hi = dict->dv_hashtab.ht_array; todo > 0; ++hi)
+       if (!HASHITEM_EMPTY(hi))
+       {
+           item = &dict_lookup(hi)->di_tv;
+ 
+           if (STRCMP(hi->hi_key, "mode") == 0)
+           {
+               if (!(supported & JO_MODE))
+                   break;
+               if (handle_mode(item, opt, &opt->jo_mode, JO_MODE) == FAIL)
+                   return FAIL;
+           }
+           else if (STRCMP(hi->hi_key, "in-mode") == 0)
+           {
+               if (!(supported & JO_IN_MODE))
+                   break;
+               if (handle_mode(item, opt, &opt->jo_in_mode, JO_IN_MODE)
+                                                                     == FAIL)
+                   return FAIL;
+           }
+           else if (STRCMP(hi->hi_key, "out-mode") == 0)
+           {
+               if (!(supported & JO_OUT_MODE))
+                   break;
+               if (handle_mode(item, opt, &opt->jo_out_mode, JO_OUT_MODE)
+                                                                     == FAIL)
+                   return FAIL;
+           }
+           else if (STRCMP(hi->hi_key, "err-mode") == 0)
+           {
+               if (!(supported & JO_ERR_MODE))
+                   break;
+               if (handle_mode(item, opt, &opt->jo_err_mode, JO_ERR_MODE)
+                                                                     == FAIL)
+                   return FAIL;
+           }
+           else if (STRCMP(hi->hi_key, "in-io") == 0
+                   || STRCMP(hi->hi_key, "out-io") == 0
+                   || STRCMP(hi->hi_key, "err-io") == 0)
+           {
+               if (!(supported & JO_OUT_IO))
+                   break;
+               if (handle_io(item, part_from_char(*hi->hi_key), opt) == FAIL)
+                   return FAIL;
+           }
+           else if (STRCMP(hi->hi_key, "in-name") == 0
+                   || STRCMP(hi->hi_key, "out-name") == 0
+                   || STRCMP(hi->hi_key, "err-name") == 0)
+           {
+               part = part_from_char(*hi->hi_key);
+ 
+               if (!(supported & JO_OUT_IO))
+                   break;
+               opt->jo_set |= JO_OUT_NAME << (part - PART_OUT);
+               opt->jo_io_name[part] =
+                      get_tv_string_buf_chk(item, opt->jo_io_name_buf[part]);
+           }
+           else if (STRCMP(hi->hi_key, "in-buf") == 0
+                   || STRCMP(hi->hi_key, "out-buf") == 0
+                   || STRCMP(hi->hi_key, "err-buf") == 0)
+           {
+               part = part_from_char(*hi->hi_key);
+ 
+               if (!(supported & JO_OUT_IO))
+                   break;
+               opt->jo_set |= JO_OUT_BUF << (part - PART_OUT);
+               opt->jo_io_buf[part] = get_tv_number(item);
+               if (opt->jo_io_buf[part] <= 0)
+               {
+                   EMSG2(_(e_invarg2), get_tv_string(item));
+                   return FAIL;
+               }
+               if (buflist_findnr(opt->jo_io_buf[part]) == NULL)
+               {
+                   EMSGN(_(e_nobufnr), (long)opt->jo_io_buf[part]);
+                   return FAIL;
+               }
+           }
+           else if (STRCMP(hi->hi_key, "in-top") == 0
+                   || STRCMP(hi->hi_key, "in-bot") == 0)
+           {
+               linenr_T *lp;
+ 
+               if (!(supported & JO_OUT_IO))
+                   break;
+               if (hi->hi_key[3] == 't')
+               {
+                   lp = &opt->jo_in_top;
+                   opt->jo_set |= JO_IN_TOP;
+               }
+               else
+               {
+                   lp = &opt->jo_in_bot;
+                   opt->jo_set |= JO_IN_BOT;
+               }
+               *lp = get_tv_number(item);
+               if (*lp < 0)
+               {
+                   EMSG2(_(e_invarg2), get_tv_string(item));
+                   return FAIL;
+               }
+           }
+           else if (STRCMP(hi->hi_key, "channel") == 0)
+           {
+               if (!(supported & JO_OUT_IO))
+                   break;
+               opt->jo_set |= JO_CHANNEL;
+               if (item->v_type != VAR_CHANNEL)
+               {
+                   EMSG2(_(e_invarg2), "channel");
+                   return FAIL;
+               }
+               opt->jo_channel = item->vval.v_channel;
+           }
+           else if (STRCMP(hi->hi_key, "callback") == 0)
+           {
+               if (!(supported & JO_CALLBACK))
+                   break;
+               opt->jo_set |= JO_CALLBACK;
+               opt->jo_callback = get_callback(item);
+               if (opt->jo_callback == NULL)
+               {
+                   EMSG2(_(e_invarg2), "callback");
+                   return FAIL;
+               }
+           }
+           else if (STRCMP(hi->hi_key, "out-cb") == 0)
+           {
+               if (!(supported & JO_OUT_CALLBACK))
+                   break;
+               opt->jo_set |= JO_OUT_CALLBACK;
+               opt->jo_out_cb = get_callback(item);
+               if (opt->jo_out_cb == NULL)
+               {
+                   EMSG2(_(e_invarg2), "out-cb");
+                   return FAIL;
+               }
+           }
+           else if (STRCMP(hi->hi_key, "err-cb") == 0)
+           {
+               if (!(supported & JO_ERR_CALLBACK))
+                   break;
+               opt->jo_set |= JO_ERR_CALLBACK;
+               opt->jo_err_cb = get_callback(item);
+               if (opt->jo_err_cb == NULL)
+               {
+                   EMSG2(_(e_invarg2), "err-cb");
+                   return FAIL;
+               }
+           }
+           else if (STRCMP(hi->hi_key, "close-cb") == 0)
+           {
+               if (!(supported & JO_CLOSE_CALLBACK))
+                   break;
+               opt->jo_set |= JO_CLOSE_CALLBACK;
+               opt->jo_close_cb = get_callback(item);
+               if (opt->jo_close_cb == NULL)
+               {
+                   EMSG2(_(e_invarg2), "close-cb");
+                   return FAIL;
+               }
+           }
+           else if (STRCMP(hi->hi_key, "waittime") == 0)
+           {
+               if (!(supported & JO_WAITTIME))
+                   break;
+               opt->jo_set |= JO_WAITTIME;
+               opt->jo_waittime = get_tv_number(item);
+           }
+           else if (STRCMP(hi->hi_key, "timeout") == 0)
+           {
+               if (!(supported & JO_TIMEOUT))
+                   break;
+               opt->jo_set |= JO_TIMEOUT;
+               opt->jo_timeout = get_tv_number(item);
+           }
+           else if (STRCMP(hi->hi_key, "out-timeout") == 0)
+           {
+               if (!(supported & JO_OUT_TIMEOUT))
+                   break;
+               opt->jo_set |= JO_OUT_TIMEOUT;
+               opt->jo_out_timeout = get_tv_number(item);
+           }
+           else if (STRCMP(hi->hi_key, "err-timeout") == 0)
+           {
+               if (!(supported & JO_ERR_TIMEOUT))
+                   break;
+               opt->jo_set |= JO_ERR_TIMEOUT;
+               opt->jo_err_timeout = get_tv_number(item);
+           }
+           else if (STRCMP(hi->hi_key, "part") == 0)
+           {
+               if (!(supported & JO_PART))
+                   break;
+               opt->jo_set |= JO_PART;
+               val = get_tv_string(item);
+               if (STRCMP(val, "err") == 0)
+                   opt->jo_part = PART_ERR;
+               else
+               {
+                   EMSG2(_(e_invarg2), val);
+                   return FAIL;
+               }
+           }
+           else if (STRCMP(hi->hi_key, "id") == 0)
+           {
+               if (!(supported & JO_ID))
+                   break;
+               opt->jo_set |= JO_ID;
+               opt->jo_id = get_tv_number(item);
+           }
+           else if (STRCMP(hi->hi_key, "stoponexit") == 0)
+           {
+               if (!(supported & JO_STOPONEXIT))
+                   break;
+               opt->jo_set |= JO_STOPONEXIT;
+               opt->jo_stoponexit = get_tv_string_buf_chk(item,
+                                                            opt->jo_soe_buf);
+               if (opt->jo_stoponexit == NULL)
+               {
+                   EMSG2(_(e_invarg2), "stoponexit");
+                   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_exit_cb == NULL)
+               {
+                   EMSG2(_(e_invarg2), "exit-cb");
+                   return FAIL;
+               }
+           }
+           else
+               break;
+           --todo;
+       }
+     if (todo > 0)
+     {
+       EMSG2(_(e_invarg2), hi->hi_key);
+       return FAIL;
+     }
+ 
+     return OK;
+ }
+ 
+ /*
+  * Get the channel from the argument.
+  * Returns NULL if the handle is invalid.
+  */
+     channel_T *
+ get_channel_arg(typval_T *tv, int check_open)
+ {
+     channel_T *channel = NULL;
+ 
+     if (tv->v_type == VAR_JOB)
+     {
+       if (tv->vval.v_job != NULL)
+           channel = tv->vval.v_job->jv_channel;
+     }
+     else if (tv->v_type == VAR_CHANNEL)
+     {
+       channel = tv->vval.v_channel;
+     }
+     else
+     {
+       EMSG2(_(e_invarg2), get_tv_string(tv));
+       return NULL;
+     }
+ 
+     if (check_open && (channel == NULL || !channel_is_open(channel)))
+     {
+       EMSG(_("E906: not an open channel"));
+       return NULL;
+     }
+     return channel;
+ }
+ 
+ static job_T *first_job = NULL;
+ 
+     static void
+ job_free(job_T *job)
+ {
+     ch_log(job->jv_channel, "Freeing job");
+     if (job->jv_channel != NULL)
+     {
+       /* The link from the channel to the job doesn't count as a reference,
+        * thus don't decrement the refcount of the job.  The reference from
+        * the job to the channel does count the refrence, decrement it and
+        * NULL the reference.  We don't set ch_job_killed, unreferencing the
+        * job doesn't mean it stops running. */
+       job->jv_channel->ch_job = NULL;
+       channel_unref(job->jv_channel);
+     }
+     mch_clear_job(job);
+ 
+     if (job->jv_next != NULL)
+       job->jv_next->jv_prev = job->jv_prev;
+     if (job->jv_prev == NULL)
+       first_job = job->jv_next;
+     else
+       job->jv_prev->jv_next = job->jv_next;
+ 
+     vim_free(job->jv_stoponexit);
+     vim_free(job->jv_exit_cb);
+     vim_free(job);
+ }
+ 
+     void
+ job_unref(job_T *job)
+ {
+     if (job != NULL && --job->jv_refcount <= 0)
+     {
+       /* 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);
+       }
+       else if (job->jv_channel != NULL)
+       {
+           /* Do remove the link to the channel, otherwise it hangs
+            * around until Vim exits. See job_free() for refcount. */
+           job->jv_channel->ch_job = NULL;
+           channel_unref(job->jv_channel);
+           job->jv_channel = NULL;
+       }
+     }
+ }
+ 
+ /*
+  * Allocate a job.  Sets the refcount to one and sets options default.
+  */
+     static job_T *
+ job_alloc(void)
+ {
+     job_T *job;
+ 
+     job = (job_T *)alloc_clear(sizeof(job_T));
+     if (job != NULL)
+     {
+       job->jv_refcount = 1;
+       job->jv_stoponexit = vim_strsave((char_u *)"term");
+ 
+       if (first_job != NULL)
+       {
+           first_job->jv_prev = job;
+           job->jv_next = first_job;
+       }
+       first_job = job;
+     }
+     return job;
+ }
+ 
+     void
+ job_set_options(job_T *job, jobopt_T *opt)
+ {
+     if (opt->jo_set & JO_STOPONEXIT)
+     {
+       vim_free(job->jv_stoponexit);
+       if (opt->jo_stoponexit == NULL || *opt->jo_stoponexit == NUL)
+           job->jv_stoponexit = NULL;
+       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);
+     }
+ }
+ 
+ /*
+  * Called when Vim is exiting: kill all jobs that have the "stoponexit" flag.
+  */
+     void
+ job_stop_on_exit()
+ {
+     job_T     *job;
+ 
+     for (job = first_job; job != NULL; job = job->jv_next)
+       if (job->jv_status == JOB_STARTED && job->jv_stoponexit != NULL)
+           mch_stop_job(job, job->jv_stoponexit);
+ }
+ 
+ /*
+  * Called once in a while: check if any jobs with an "exit-cb" have ended.
+  */
+     void
+ job_check_ended(void)
+ {
+     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_start()" function
+  */
+     job_T *
+ job_start(typval_T *argvars)
+ {
+     job_T     *job;
+     char_u    *cmd = NULL;
+ #if defined(UNIX)
+ # define USE_ARGV
+     char      **argv = NULL;
+     int               argc = 0;
+ #else
+     garray_T  ga;
+ #endif
+     jobopt_T  opt;
+     int               part;
+ 
+     job = job_alloc();
+     if (job == NULL)
+       return NULL;
+ 
+     job->jv_status = JOB_FAILED;
+ 
+     /* Default mode is NL. */
+     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 + JO_EXIT_CB + JO_OUT_IO) == FAIL)
+       return job;
+ 
+     /* Check that when io is "file" that there is a file name. */
+     for (part = PART_OUT; part <= PART_IN; ++part)
+       if ((opt.jo_set & (JO_OUT_IO << (part - PART_OUT)))
+               && opt.jo_io[part] == JIO_FILE
+               && (!(opt.jo_set & (JO_OUT_NAME << (part - PART_OUT)))
+                   || *opt.jo_io_name[part] == NUL))
+       {
+           EMSG(_("E920: -io file requires -name to be set"));
+           return job;
+       }
+ 
+     if ((opt.jo_set & JO_IN_IO) && opt.jo_io[PART_IN] == JIO_BUFFER)
+     {
+       buf_T *buf = NULL;
+ 
+       /* check that we can find the buffer before starting the job */
+       if (opt.jo_set & JO_IN_BUF)
+       {
+           buf = buflist_findnr(opt.jo_io_buf[PART_IN]);
+           if (buf == NULL)
+               EMSGN(_(e_nobufnr), (long)opt.jo_io_buf[PART_IN]);
+       }
+       else if (!(opt.jo_set & JO_IN_NAME))
+       {
+           EMSG(_("E915: in-io buffer requires in-buf or in-name to be set"));
+       }
+       else
+           buf = buflist_find_by_name(opt.jo_io_name[PART_IN], FALSE);
+       if (buf == NULL)
+           return job;
+       if (buf->b_ml.ml_mfp == NULL)
+       {
+           char_u      numbuf[NUMBUFLEN];
+           char_u      *s;
+ 
+           if (opt.jo_set & JO_IN_BUF)
+           {
+               sprintf((char *)numbuf, "%d", opt.jo_io_buf[PART_IN]);
+               s = numbuf;
+           }
+           else
+               s = opt.jo_io_name[PART_IN];
+           EMSG2(_("E918: buffer must be loaded: %s"), s);
+           return job;
+       }
+       job->jv_in_buf = buf;
+     }
+ 
+     job_set_options(job, &opt);
+ 
+ #ifndef USE_ARGV
+     ga_init2(&ga, (int)sizeof(char*), 20);
+ #endif
+ 
+     if (argvars[0].v_type == VAR_STRING)
+     {
+       /* Command is a string. */
+       cmd = argvars[0].vval.v_string;
+ #ifdef USE_ARGV
+       if (mch_parse_cmd(cmd, FALSE, &argv, &argc) == FAIL)
+           return job;
+       argv[argc] = NULL;
+ #endif
+     }
+     else if (argvars[0].v_type != VAR_LIST
+           || argvars[0].vval.v_list == NULL
+           || argvars[0].vval.v_list->lv_len < 1)
+     {
+       EMSG(_(e_invarg));
+       return job;
+     }
+     else
+     {
+       list_T      *l = argvars[0].vval.v_list;
+       listitem_T  *li;
+       char_u      *s;
+ 
+ #ifdef USE_ARGV
+       /* Pass argv[] to mch_call_shell(). */
+       argv = (char **)alloc(sizeof(char *) * (l->lv_len + 1));
+       if (argv == NULL)
+           return job;
+ #endif
+       for (li = l->lv_first; li != NULL; li = li->li_next)
+       {
+           s = get_tv_string_chk(&li->li_tv);
+           if (s == NULL)
+               goto theend;
+ #ifdef USE_ARGV
+           argv[argc++] = (char *)s;
+ #else
+           /* Only escape when needed, double quotes are not always allowed. */
+           if (li != l->lv_first && vim_strpbrk(s, (char_u *)" \t\"") != NULL)
+           {
+               s = vim_strsave_shellescape(s, FALSE, TRUE);
+               if (s == NULL)
+                   goto theend;
+               ga_concat(&ga, s);
+               vim_free(s);
+           }
+           else
+               ga_concat(&ga, s);
+           if (li->li_next != NULL)
+               ga_append(&ga, ' ');
+ #endif
+       }
+ #ifdef USE_ARGV
+       argv[argc] = NULL;
+ #else
+       cmd = ga.ga_data;
+ #endif
+     }
+ 
+ #ifdef USE_ARGV
+     if (ch_log_active())
+     {
+       garray_T    ga;
+       int         i;
+ 
+       ga_init2(&ga, (int)sizeof(char), 200);
+       for (i = 0; i < argc; ++i)
+       {
+           if (i > 0)
+               ga_concat(&ga, (char_u *)"  ");
+           ga_concat(&ga, (char_u *)argv[i]);
+       }
+       ch_logs(NULL, "Starting job: %s", (char *)ga.ga_data);
+       ga_clear(&ga);
+     }
+     mch_start_job(argv, job, &opt);
+ #else
+     ch_logs(NULL, "Starting job: %s", (char *)cmd);
+     mch_start_job((char *)cmd, job, &opt);
+ #endif
+ 
+     /* If the channel is reading from a buffer, write lines now. */
+     if (job->jv_channel != NULL)
+       channel_write_in(job->jv_channel);
+ 
+ theend:
+ #ifdef USE_ARGV
+     vim_free(argv);
+ #else
+     vim_free(ga.ga_data);
+ #endif
+     return job;
+ }
+ 
+ /*
+  * Get the status of "job" and invoke the exit callback when needed.
+  * The returned string is not allocated.
+  */
+     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);
+       if (job->jv_status == JOB_ENDED)
+           ch_log(job->jv_channel, "Job ended");
+       if (job->jv_status == JOB_ENDED && job->jv_exit_cb != NULL)
+       {
+           typval_T    argv[3];
+           typval_T    rettv;
+           int         dummy;
+ 
+           /* invoke the exit callback; make sure the refcount is > 0 */
+           ++job->jv_refcount;
+           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);
+           --job->jv_refcount;
+       }
+       if (job->jv_status == JOB_ENDED && job->jv_refcount == 0)
+       {
+           /* The job was already unreferenced, now that it ended it can be
+            * freed. Careful: caller must not use "job" after this! */
+           job_free(job);
+       }
+     }
+     return result;
+ }
+ 
+     int
+ job_stop(job_T *job, typval_T *argvars)
+ {
+     char_u *arg;
+ 
+     if (argvars[1].v_type == VAR_UNKNOWN)
+       arg = (char_u *)"";
+     else
+     {
+       arg = get_tv_string_chk(&argvars[1]);
+       if (arg == NULL)
+       {
+           EMSG(_(e_invarg));
+           return 0;
+       }
+     }
+     ch_logs(job->jv_channel, "Stopping job with '%s'", (char *)arg);
+     if (mch_stop_job(job, arg) == FAIL)
+       return 0;
+ 
+     /* Assume that "hup" does not kill the job. */
+     if (job->jv_channel != NULL && STRCMP(arg, "hup") != 0)
+       job->jv_channel->ch_job_killed = TRUE;
+ 
+     /* We don't try freeing the job, obviously the caller still has a
+      * reference to it. */
+     return 1;
+ }
+ 
  #endif /* FEAT_JOB_CHANNEL */
*** ../vim-7.4.1538/src/proto/channel.pro       2016-03-11 22:19:39.934894037 
+0100
--- src/proto/channel.pro       2016-03-12 13:33:25.446710489 +0100
***************
*** 1,13 ****
  /* channel.c */
! void ch_logfile(FILE *file);
  int ch_log_active(void);
  void ch_log(channel_T *ch, char *msg);
  void ch_logs(channel_T *ch, char *msg, char *name);
  channel_T *add_channel(void);
! int channel_may_free(channel_T *channel);
  void channel_free(channel_T *channel);
  void channel_gui_register_all(void);
  channel_T *channel_open(char *hostname, int port_in, int waittime, void 
(*nb_close_cb)(void));
  void channel_set_pipes(channel_T *channel, sock_T in, sock_T out, sock_T err);
  void channel_set_job(channel_T *channel, job_T *job, jobopt_T *options);
  void channel_set_options(channel_T *channel, jobopt_T *opt);
--- 1,14 ----
  /* channel.c */
! void ch_logfile(char_u *fname, char_u *opt);
  int ch_log_active(void);
  void ch_log(channel_T *ch, char *msg);
  void ch_logs(channel_T *ch, char *msg, char *name);
  channel_T *add_channel(void);
! int channel_unref(channel_T *channel);
  void channel_free(channel_T *channel);
  void channel_gui_register_all(void);
  channel_T *channel_open(char *hostname, int port_in, int waittime, void 
(*nb_close_cb)(void));
+ channel_T *channel_open_func(typval_T *argvars);
  void channel_set_pipes(channel_T *channel, sock_T in, sock_T out, sock_T err);
  void channel_set_job(channel_T *channel, job_T *job, jobopt_T *options);
  void channel_set_options(channel_T *channel, jobopt_T *opt);
***************
*** 27,35 ****
--- 28,40 ----
  void channel_read(channel_T *channel, int part, char *func);
  char_u *channel_read_block(channel_T *channel, int part, int timeout);
  int channel_read_json_block(channel_T *channel, int part, int timeout, int 
id, typval_T **rettv);
+ void common_channel_read(typval_T *argvars, typval_T *rettv, int raw);
  channel_T *channel_fd2channel(sock_T fd, int *partp);
  void channel_handle_events(void);
  int channel_send(channel_T *channel, int part, char_u *buf, char *fun);
+ channel_T *send_common(typval_T *argvars, char_u *text, int id, int eval, 
jobopt_T *opt, char *fun, int *part_read);
+ void ch_expr_common(typval_T *argvars, typval_T *rettv, int eval);
+ void ch_raw_common(typval_T *argvars, typval_T *rettv, int eval);
  int channel_poll_setup(int nfd_in, void *fds_in);
  int channel_poll_check(int ret_in, void *fds_in);
  int channel_select_setup(int maxfd_in, void *rfds_in);
***************
*** 40,43 ****
--- 45,58 ----
  int channel_part_read(channel_T *channel);
  ch_mode_T channel_get_mode(channel_T *channel, int part);
  int channel_get_timeout(channel_T *channel, int part);
+ void clear_job_options(jobopt_T *opt);
+ int get_job_options(typval_T *tv, jobopt_T *opt, int supported);
+ channel_T *get_channel_arg(typval_T *tv, int check_open);
+ void job_unref(job_T *job);
+ void job_set_options(job_T *job, jobopt_T *opt);
+ void job_stop_on_exit(void);
+ void job_check_ended(void);
+ job_T *job_start(typval_T *argvars);
+ char *job_status(job_T *job);
+ int job_stop(job_T *job, typval_T *argvars);
  /* vim: set ft=c : */
*** ../vim-7.4.1538/src/proto/eval.pro  2016-02-21 19:14:36.679958696 +0100
--- src/proto/eval.pro  2016-03-12 13:32:31.475267912 +0100
***************
*** 79,93 ****
  dictitem_T *dict_find(dict_T *d, char_u *key, int len);
  char_u *get_dict_string(dict_T *d, char_u *key, int save);
  long get_dict_number(dict_T *d, char_u *key);
- int channel_unref(channel_T *channel);
- void job_stop_on_exit(void);
  int string2float(char_u *text, float_T *value);
  char_u *get_function_name(expand_T *xp, int idx);
  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);
--- 79,91 ----
  dictitem_T *dict_find(dict_T *d, char_u *key, int len);
  char_u *get_dict_string(dict_T *d, char_u *key, int save);
  long get_dict_number(dict_T *d, char_u *key);
  int string2float(char_u *text, float_T *value);
  char_u *get_function_name(expand_T *xp, int idx);
  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);
+ buf_T *buflist_find_by_name(char_u *name, int curtab_only);
  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 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);
***************
*** 107,113 ****
--- 105,114 ----
  typval_T *alloc_tv(void);
  void free_tv(typval_T *varp);
  void clear_tv(typval_T *varp);
+ long get_tv_number(typval_T *varp);
  long get_tv_number_chk(typval_T *varp, int *denote);
+ char_u *get_tv_string(typval_T *varp);
+ char_u *get_tv_string_buf(typval_T *varp, char_u *buf);
  char_u *get_tv_string_chk(typval_T *varp);
  char_u *get_tv_string_buf_chk(typval_T *varp, char_u *buf);
  char_u *get_var_value(char_u *name);
*** ../vim-7.4.1538/src/version.c       2016-03-12 12:40:54.219242159 +0100
--- src/version.c       2016-03-12 13:00:08.419328525 +0100
***************
*** 745,746 ****
--- 745,748 ----
  {   /* Add new patch number below this line */
+ /**/
+     1539,
  /**/

-- 
How To Keep A Healthy Level Of Insanity:
18. When leaving the zoo, start running towards the parking lot,
    yelling "run for your lives, they're loose!!"

 /// Bram Moolenaar -- [email protected] -- http://www.Moolenaar.net   \\\
///        sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\
\\\  an exciting new programming language -- http://www.Zimbu.org        ///
 \\\            help me help AIDS victims -- http://ICCF-Holland.org    ///

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/d/optout.

Raspunde prin e-mail lui