changeset 8498:42277980a76d v7.4.1539

commit https://github.com/vim/vim/commit/8e2c942ce49f2555d7dc2088cf3aa856820c5e32 Author: Bram Moolenaar <Bram@vim.org> Date: Sat Mar 12 13:43:33 2016 +0100 patch 7.4.1539 Problem: Too much code in eval.c. Solution: Move job and channel code to channel.c.
author Christian Brabandt <cb@256bit.org>
date Sat, 12 Mar 2016 13:45:04 +0100
parents da01d5da2cfa
children 10606ea16a0b
files src/channel.c src/eval.c src/proto/channel.pro src/proto/eval.pro src/version.c
diffstat 5 files changed, 1093 insertions(+), 1058 deletions(-) [+]
line wrap: on
line diff
--- a/src/channel.c
+++ b/src/channel.c
@@ -97,11 +97,24 @@ static proftime_T log_start;
 #endif
 
     void
-ch_logfile(FILE *file)
+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,7 +373,7 @@ channel_still_useful(channel_T *channel)
  * killed.
  * Return TRUE if the channel was freed.
  */
-    int
+    static int
 channel_may_free(channel_T *channel)
 {
     if (!channel_still_useful(channel))
@@ -372,6 +385,19 @@ channel_may_free(channel_T *channel)
 }
 
 /*
+ * 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,6 +846,65 @@ channel_open(
     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,6 +2392,62 @@ channel_read_json_block(
     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,6 +2553,146 @@ channel_send(channel_T *channel, int par
     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,4 +2976,782 @@ channel_get_timeout(channel_T *channel, 
     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 */
--- a/src/eval.c
+++ b/src/eval.c
@@ -836,14 +836,11 @@ static int get_var_tv(char_u *name, int 
 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);
@@ -7735,129 +7732,6 @@ failret:
 }
 
 #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 *
@@ -9650,7 +9524,7 @@ f_bufloaded(typval_T *argvars, typval_T 
     rettv->vval.v_number = (buf != NULL && buf->b_ml.ml_mfp != NULL);
 }
 
-    static buf_T *
+    buf_T *
 buflist_find_by_name(char_u *name, int curtab_only)
 {
     int		save_magic;
@@ -9941,397 +9815,8 @@ f_ceil(typval_T *argvars, typval_T *rett
 }
 #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
@@ -10414,21 +9899,11 @@ f_ch_logfile(typval_T *argvars, typval_T
     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_logfile(fname, opt);
 }
 
 /*
@@ -10437,117 +9912,8 @@ f_ch_logfile(typval_T *argvars, typval_T
     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;
-	    }
-	}
-    }
+    rettv->vval.v_channel = channel_open_func(argvars);
 }
 
 /*
@@ -10569,116 +9935,6 @@ f_ch_readraw(typval_T *argvars, typval_T
 }
 
 /*
- * 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);
-	}
-    }
-}
-
-/*
  * "ch_evalexpr()" function
  */
     static void
@@ -10697,36 +9953,6 @@ f_ch_sendexpr(typval_T *argvars, typval_
 }
 
 /*
- * 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
@@ -15138,250 +14364,8 @@ f_job_setoptions(typval_T *argvars, typv
     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" */
-	}
-    }
+    rettv->vval.v_job = job_start(argvars);
 }
 
 /*
@@ -15405,38 +14389,12 @@ f_job_status(typval_T *argvars, typval_T
  * "job_stop()" function
  */
     static void
-f_job_stop(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
+f_job_stop(typval_T *argvars, typval_T *rettv)
 {
     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. */
-    }
+	rettv->vval.v_number = job_stop(job, argvars);
 }
 #endif
 
@@ -22475,7 +21433,7 @@ init_tv(typval_T *varp)
  * caller of incompatible types: it sets *denote to TRUE if "denote"
  * is not NULL or returns -1 otherwise.
  */
-    static long
+    long
 get_tv_number(typval_T *varp)
 {
     int		error = FALSE;
@@ -22626,7 +21584,7 @@ get_tv_lnum_buf(typval_T *argvars, buf_T
  * get_tv_string_chk() and get_tv_string_buf_chk() are similar, but return
  * NULL on error.
  */
-    static char_u *
+    char_u *
 get_tv_string(typval_T *varp)
 {
     static char_u   mybuf[NUMBUFLEN];
@@ -22634,7 +21592,7 @@ get_tv_string(typval_T *varp)
     return get_tv_string_buf(varp, mybuf);
 }
 
-    static char_u *
+    char_u *
 get_tv_string_buf(typval_T *varp, char_u *buf)
 {
     char_u	*res =  get_tv_string_buf_chk(varp, buf);
--- a/src/proto/channel.pro
+++ b/src/proto/channel.pro
@@ -1,13 +1,14 @@
 /* channel.c */
-void ch_logfile(FILE *file);
+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_may_free(channel_T *channel);
+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,9 +28,13 @@ int channel_get_id(void);
 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,4 +45,14 @@ int channel_part_send(channel_T *channel
 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 : */
--- a/src/proto/eval.pro
+++ b/src/proto/eval.pro
@@ -79,15 +79,13 @@ int dict_add_list(dict_T *d, char *key, 
 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);
+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 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);
@@ -107,7 +105,10 @@ char_u *set_cmdarg(exarg_T *eap, char_u 
 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);
--- a/src/version.c
+++ b/src/version.c
@@ -744,6 +744,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1539,
+/**/
     1538,
 /**/
     1537,