patch 7.4.1274
Problem:    Cannot run a job.
Solution:   Add job_start(), job_status() and job_stop(). Currently only works
            for Unix.
diff --git a/src/eval.c b/src/eval.c
index a50fdbf..833c41c 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -451,6 +451,9 @@
 static long dict_len(dict_T *d);
 static char_u *dict2string(typval_T *tv, int copyID);
 static int get_dict_tv(char_u **arg, typval_T *rettv, int evaluate);
+#ifdef FEAT_JOB
+static void job_free(job_T *job);
+#endif
 static char_u *echo_string(typval_T *tv, char_u **tofree, char_u *numbuf, int copyID);
 static char_u *tv2string(typval_T *tv, char_u **tofree, char_u *numbuf, int copyID);
 static char_u *string_quote(char_u *str, int function);
@@ -619,6 +622,11 @@
 static void f_isdirectory(typval_T *argvars, typval_T *rettv);
 static void f_islocked(typval_T *argvars, typval_T *rettv);
 static void f_items(typval_T *argvars, typval_T *rettv);
+#ifdef FEAT_JOB
+static void f_job_start(typval_T *argvars, typval_T *rettv);
+static void f_job_stop(typval_T *argvars, typval_T *rettv);
+static void f_job_status(typval_T *argvars, typval_T *rettv);
+#endif
 static void f_join(typval_T *argvars, typval_T *rettv);
 static void f_jsondecode(typval_T *argvars, typval_T *rettv);
 static void f_jsonencode(typval_T *argvars, typval_T *rettv);
@@ -3062,10 +3070,11 @@
     {
 	switch (tv1->v_type)
 	{
+	    case VAR_UNKNOWN:
 	    case VAR_DICT:
 	    case VAR_FUNC:
 	    case VAR_SPECIAL:
-	    case VAR_UNKNOWN:
+	    case VAR_JOB:
 		break;
 
 	    case VAR_LIST:
@@ -3844,6 +3853,7 @@
 	case VAR_FUNC:
 	case VAR_FLOAT:
 	case VAR_SPECIAL:
+	case VAR_JOB:
 	    break;
 
 	case VAR_LIST:
@@ -5339,6 +5349,7 @@
 	    return FAIL;
 #endif
 	case VAR_SPECIAL:
+	case VAR_JOB:
 	    if (verbose)
 		EMSG(_("E909: Cannot index a special variable"));
 	    return FAIL;
@@ -5446,10 +5457,11 @@
 
 	switch (rettv->v_type)
 	{
-	    case VAR_SPECIAL:
+	    case VAR_UNKNOWN:
 	    case VAR_FUNC:
 	    case VAR_FLOAT:
-	    case VAR_UNKNOWN:
+	    case VAR_SPECIAL:
+	    case VAR_JOB:
 		break; /* not evaluating, skipping over subscript */
 
 	    case VAR_NUMBER:
@@ -6167,9 +6179,6 @@
 
     switch (tv1->v_type)
     {
-	case VAR_UNKNOWN:
-	    break;
-
 	case VAR_LIST:
 	    ++recursive_cnt;
 	    r = list_equal(tv1->vval.v_list, tv2->vval.v_list, ic, TRUE);
@@ -6190,11 +6199,6 @@
 	case VAR_NUMBER:
 	    return tv1->vval.v_number == tv2->vval.v_number;
 
-#ifdef FEAT_FLOAT
-	case VAR_FLOAT:
-	    return tv1->vval.v_float == tv2->vval.v_float;
-#endif
-
 	case VAR_STRING:
 	    s1 = get_tv_string_buf(tv1, buf1);
 	    s2 = get_tv_string_buf(tv2, buf2);
@@ -6202,6 +6206,17 @@
 
 	case VAR_SPECIAL:
 	    return tv1->vval.v_number == tv2->vval.v_number;
+
+	case VAR_FLOAT:
+#ifdef FEAT_FLOAT
+	    return tv1->vval.v_float == tv2->vval.v_float;
+#endif
+	case VAR_JOB:
+#ifdef FEAT_JOB
+	    return tv1->vval.v_job == tv2->vval.v_job;
+#endif
+	case VAR_UNKNOWN:
+	    break;
     }
 
     /* VAR_UNKNOWN can be the result of a invalid expression, let's say it
@@ -6924,7 +6939,7 @@
 }
 
 /*
- * Free lists and dictionaries that are no longer referenced.
+ * Free lists, dictionaries and jobs that are no longer referenced.
  */
     static int
 free_unref_items(int copyID)
@@ -6969,6 +6984,7 @@
 	}
 	ll = ll_next;
     }
+
     return did_free;
 }
 
@@ -7694,6 +7710,40 @@
     return OK;
 }
 
+#ifdef FEAT_JOB
+    static void
+job_free(job_T *job)
+{
+    /* TODO: free any handles */
+
+    vim_free(job);
+}
+
+    static void
+job_unref(job_T *job)
+{
+    if (job != NULL && --job->jv_refcount <= 0)
+	job_free(job);
+}
+
+/*
+ * Allocate a job.  Sets the refcount to one.
+ */
+    static job_T *
+job_alloc(void)
+{
+    job_T *job;
+
+    job = (job_T *)alloc_clear(sizeof(job_T));
+    if (job != NULL)
+    {
+	job->jv_refcount = 1;
+    }
+    return job;
+}
+
+#endif
+
     static char *
 get_var_special_name(int nr)
 {
@@ -7789,12 +7839,13 @@
 	case VAR_STRING:
 	case VAR_NUMBER:
 	case VAR_UNKNOWN:
+	case VAR_JOB:
 	    *tofree = NULL;
 	    r = get_tv_string_buf(tv, numbuf);
 	    break;
 
-#ifdef FEAT_FLOAT
 	case VAR_FLOAT:
+#ifdef FEAT_FLOAT
 	    *tofree = NULL;
 	    vim_snprintf((char *)numbuf, NUMBUFLEN, "%g", tv->vval.v_float);
 	    r = numbuf;
@@ -7844,6 +7895,7 @@
 	case VAR_LIST:
 	case VAR_DICT:
 	case VAR_SPECIAL:
+	case VAR_JOB:
 	case VAR_UNKNOWN:
 	    break;
     }
@@ -8148,6 +8200,11 @@
     {"isdirectory",	1, 1, f_isdirectory},
     {"islocked",	1, 1, f_islocked},
     {"items",		1, 1, f_items},
+#ifdef FEAT_CHANNEL
+    {"job_start",	1, 2, f_job_start},
+    {"job_status",	1, 1, f_job_status},
+    {"job_stop",	1, 1, f_job_stop},
+#endif
     {"join",		1, 2, f_join},
     {"jsondecode",	1, 1, f_jsondecode},
     {"jsonencode",	1, 1, f_jsonencode},
@@ -10535,8 +10592,8 @@
 	case VAR_NUMBER:
 	    n = argvars[0].vval.v_number == 0;
 	    break;
-#ifdef FEAT_FLOAT
 	case VAR_FLOAT:
+#ifdef FEAT_FLOAT
 	    n = argvars[0].vval.v_float == 0.0;
 	    break;
 #endif
@@ -10552,6 +10609,11 @@
 	    n = argvars[0].vval.v_number != VVAL_TRUE;
 	    break;
 
+	case VAR_JOB:
+#ifdef FEAT_JOB
+	    n = argvars[0].vval.v_job->jv_status != JOB_STARTED;
+	    break;
+#endif
 	case VAR_UNKNOWN:
 	    EMSG2(_(e_intern2), "f_empty(UNKNOWN)");
 	    n = TRUE;
@@ -13060,6 +13122,9 @@
 #ifdef FEAT_INS_EXPAND
 	"insert_expand",
 #endif
+#ifdef FEAT_JOB
+	"job",
+#endif
 #ifdef FEAT_JUMPLIST
 	"jumplist",
 #endif
@@ -14188,6 +14253,161 @@
     dict_list(argvars, rettv, 2);
 }
 
+#ifdef FEAT_JOB
+/*
+ * "job_start()" function
+ */
+    static void
+f_job_start(typval_T *argvars UNUSED, 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
+
+    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;
+#ifndef USE_ARGV
+    ga_init2(&ga, 200);
+#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
+	    if (li != l->lv_first)
+	    {
+		s = vim_strsave_shellescape(s, FALSE, TRUE);
+		if (s == NULL)
+		    goto theend;
+	    }
+	    ga_concat(&ga, s);
+	    vim_free(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
+    mch_start_job(argv, job);
+#else
+    mch_start_job(cmd, job);
+#endif
+
+theend:
+#ifdef USE_ARGV
+    if (argv != NULL)
+	vim_free(argv);
+#else
+    vim_free(ga.ga_data);
+#endif
+}
+
+/*
+ * "job_status()" function
+ */
+    static void
+f_job_status(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
+{
+    char *result;
+
+    if (argvars[0].v_type != VAR_JOB)
+	EMSG(_(e_invarg));
+    else
+    {
+	job_T *job = argvars[0].vval.v_job;
+
+	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);
+	rettv->v_type = VAR_STRING;
+	rettv->vval.v_string = vim_strsave((char_u *)result);
+    }
+}
+
+/*
+ * "job_stop()" function
+ */
+    static void
+f_job_stop(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
+{
+    if (argvars[0].v_type != VAR_JOB)
+	EMSG(_(e_invarg));
+    else
+    {
+	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;
+	    }
+	}
+	if (mch_stop_job(argvars[0].vval.v_job, arg) == FAIL)
+	    rettv->vval.v_number = 0;
+	else
+	    rettv->vval.v_number = 1;
+    }
+}
+#endif
+
 /*
  * "join()" function
  */
@@ -14295,6 +14515,7 @@
 	case VAR_SPECIAL:
 	case VAR_FLOAT:
 	case VAR_FUNC:
+	case VAR_JOB:
 	    EMSG(_("E701: Invalid type for len()"));
 	    break;
     }
@@ -19658,6 +19879,9 @@
 	     else
 		 n = 7;
 	     break;
+#ifdef FEAT_JOB
+	case VAR_JOB:    n = 8; break;
+#endif
 	case VAR_UNKNOWN:
 	     EMSG2(_(e_intern2), "f_type(UNKNOWN)");
 	     n = -1;
@@ -21024,10 +21248,13 @@
 	    case VAR_DICT:
 		dict_unref(varp->vval.v_dict);
 		break;
-	    case VAR_NUMBER:
-#ifdef FEAT_FLOAT
-	    case VAR_FLOAT:
+	    case VAR_JOB:
+#ifdef FEAT_JOB
+		job_unref(varp->vval.v_job);
+		break;
 #endif
+	    case VAR_NUMBER:
+	    case VAR_FLOAT:
 	    case VAR_UNKNOWN:
 	    case VAR_SPECIAL:
 		break;
@@ -21065,11 +21292,17 @@
 	    case VAR_SPECIAL:
 		varp->vval.v_number = 0;
 		break;
-#ifdef FEAT_FLOAT
 	    case VAR_FLOAT:
+#ifdef FEAT_FLOAT
 		varp->vval.v_float = 0.0;
 		break;
 #endif
+	    case VAR_JOB:
+#ifdef FEAT_JOB
+		job_unref(varp->vval.v_job);
+		varp->vval.v_job = NULL;
+#endif
+		break;
 	    case VAR_UNKNOWN:
 		break;
 	}
@@ -21112,8 +21345,8 @@
     {
 	case VAR_NUMBER:
 	    return (long)(varp->vval.v_number);
-#ifdef FEAT_FLOAT
 	case VAR_FLOAT:
+#ifdef FEAT_FLOAT
 	    EMSG(_("E805: Using a Float as a Number"));
 	    break;
 #endif
@@ -21134,6 +21367,11 @@
 	case VAR_SPECIAL:
 	    return varp->vval.v_number == VVAL_TRUE ? 1 : 0;
 	    break;
+	case VAR_JOB:
+#ifdef FEAT_JOB
+	    EMSG(_("E910: Using a Job as a Number"));
+	    break;
+#endif
 	case VAR_UNKNOWN:
 	    EMSG2(_(e_intern2), "get_tv_number(UNKNOWN)");
 	    break;
@@ -21153,10 +21391,8 @@
     {
 	case VAR_NUMBER:
 	    return (float_T)(varp->vval.v_number);
-#ifdef FEAT_FLOAT
 	case VAR_FLOAT:
 	    return varp->vval.v_float;
-#endif
 	case VAR_FUNC:
 	    EMSG(_("E891: Using a Funcref as a Float"));
 	    break;
@@ -21172,6 +21408,11 @@
 	case VAR_SPECIAL:
 	    EMSG(_("E907: Using a special value as a Float"));
 	    break;
+	case VAR_JOB:
+# ifdef FEAT_JOB
+	    EMSG(_("E911: Using a Job as a Float"));
+	    break;
+# endif
 	case VAR_UNKNOWN:
 	    EMSG2(_(e_intern2), "get_tv_float(UNKNOWN)");
 	    break;
@@ -21272,8 +21513,8 @@
 	case VAR_DICT:
 	    EMSG(_("E731: using Dictionary as a String"));
 	    break;
-#ifdef FEAT_FLOAT
 	case VAR_FLOAT:
+#ifdef FEAT_FLOAT
 	    EMSG(_(e_float_as_string));
 	    break;
 #endif
@@ -21284,6 +21525,24 @@
 	case VAR_SPECIAL:
 	    STRCPY(buf, get_var_special_name(varp->vval.v_number));
 	    return buf;
+	case VAR_JOB:
+#ifdef FEAT_JOB
+	    {
+		job_T *job = varp->vval.v_job;
+		char  *status = job->jv_status == JOB_FAILED ? "fail"
+				: job->jv_status == JOB_ENDED ? "dead"
+				: "run";
+# ifdef UNIX
+		vim_snprintf((char *)buf, NUMBUFLEN,
+			    "process %ld %s", (long)job->jv_pid, status);
+# else
+		/* TODO */
+		vim_snprintf((char *)buf, NUMBUFLEN, "process ? %s", status);
+# endif
+		return buf;
+	    }
+#endif
+	    break;
 	case VAR_UNKNOWN:
 	    EMSG(_("E908: using an invalid value as a String"));
 	    break;
@@ -21903,11 +22162,17 @@
 	case VAR_SPECIAL:
 	    to->vval.v_number = from->vval.v_number;
 	    break;
-#ifdef FEAT_FLOAT
 	case VAR_FLOAT:
+#ifdef FEAT_FLOAT
 	    to->vval.v_float = from->vval.v_float;
 	    break;
 #endif
+	case VAR_JOB:
+#ifdef FEAT_FLOAT
+	    to->vval.v_job = from->vval.v_job;
+	    ++to->vval.v_job->jv_refcount;
+	    break;
+#endif
 	case VAR_STRING:
 	case VAR_FUNC:
 	    if (from->vval.v_string == NULL)
@@ -21970,12 +22235,11 @@
     switch (from->v_type)
     {
 	case VAR_NUMBER:
-#ifdef FEAT_FLOAT
 	case VAR_FLOAT:
-#endif
 	case VAR_STRING:
 	case VAR_FUNC:
 	case VAR_SPECIAL:
+	case VAR_JOB:
 	    copy_tv(from, to);
 	    break;
 	case VAR_LIST:
@@ -24649,6 +24913,7 @@
 
 		    case VAR_UNKNOWN:
 		    case VAR_FUNC:
+		    case VAR_JOB:
 				     continue;
 		}
 		fprintf(fp, "!%s\t%s\t", this_var->di_key, s);
diff --git a/src/feature.h b/src/feature.h
index ca81d89..19425ed 100644
--- a/src/feature.h
+++ b/src/feature.h
@@ -1255,13 +1255,20 @@
 #endif
 
 /*
- * The Channel feature requires +eval.
+ * The +channel feature requires +eval.
  */
 #if !defined(FEAT_EVAL) && defined(FEAT_CHANNEL)
 # undef FEAT_CHANNEL
 #endif
 
 /*
+ * The +job feature requires Unix and +eval.
+ */
+#if defined(UNIX) && defined(FEAT_EVAL)
+# define FEAT_JOB
+#endif
+
+/*
  * +signs		Allow signs to be displayed to the left of text lines.
  *			Adds the ":sign" command.
  */
diff --git a/src/os_unix.c b/src/os_unix.c
index 2dbf74f..a0e5ed0 100644
--- a/src/os_unix.c
+++ b/src/os_unix.c
@@ -3919,6 +3919,66 @@
     return wait_pid;
 }
 
+#if defined(FEAT_JOB) || !defined(USE_SYSTEM) || defined(PROTO)
+    int
+mch_parse_cmd(char_u *cmd, int use_shcf, char ***argv, int *argc)
+{
+    int		i;
+    char_u	*p;
+    int		inquote;
+
+    /*
+     * Do this loop twice:
+     * 1: find number of arguments
+     * 2: separate them and build argv[]
+     */
+    for (i = 0; i < 2; ++i)
+    {
+	p = cmd;
+	inquote = FALSE;
+	*argc = 0;
+	for (;;)
+	{
+	    if (i == 1)
+		(*argv)[*argc] = (char *)p;
+	    ++*argc;
+	    while (*p != NUL && (inquote || (*p != ' ' && *p != TAB)))
+	    {
+		if (*p == '"')
+		    inquote = !inquote;
+		++p;
+	    }
+	    if (*p == NUL)
+		break;
+	    if (i == 1)
+		*p++ = NUL;
+	    p = skipwhite(p);
+	}
+	if (*argv == NULL)
+	{
+	    if (use_shcf)
+	    {
+		/* Account for possible multiple args in p_shcf. */
+		p = p_shcf;
+		for (;;)
+		{
+		    p = skiptowhite(p);
+		    if (*p == NUL)
+			break;
+		    ++*argc;
+		    p = skipwhite(p);
+		}
+	    }
+
+	    *argv = (char **)alloc((unsigned)((*argc + 4) * sizeof(char *)));
+	    if (*argv == NULL)	    /* out of memory */
+		return FAIL;
+	}
+    }
+    return OK;
+}
+#endif
+
     int
 mch_call_shell(
     char_u	*cmd,
@@ -4046,7 +4106,7 @@
 # define EXEC_FAILED 122    /* Exit code when shell didn't execute.  Don't use
 			       127, some shells use that already */
 
-    char_u	*newcmd = NULL;
+    char_u	*newcmd;
     pid_t	pid;
     pid_t	wpid = 0;
     pid_t	wait_pid = 0;
@@ -4061,7 +4121,6 @@
     char_u	*p_shcf_copy = NULL;
     int		i;
     char_u	*p;
-    int		inquote;
     int		pty_master_fd = -1;	    /* for pty's */
 # ifdef FEAT_GUI
     int		pty_slave_fd = -1;
@@ -4086,53 +4145,9 @@
     if (options & SHELL_COOKED)
 	settmode(TMODE_COOK);		/* set to normal mode */
 
-    /*
-     * Do this loop twice:
-     * 1: find number of arguments
-     * 2: separate them and build argv[]
-     */
-    for (i = 0; i < 2; ++i)
-    {
-	p = newcmd;
-	inquote = FALSE;
-	argc = 0;
-	for (;;)
-	{
-	    if (i == 1)
-		argv[argc] = (char *)p;
-	    ++argc;
-	    while (*p && (inquote || (*p != ' ' && *p != TAB)))
-	    {
-		if (*p == '"')
-		    inquote = !inquote;
-		++p;
-	    }
-	    if (*p == NUL)
-		break;
-	    if (i == 1)
-		*p++ = NUL;
-	    p = skipwhite(p);
-	}
-	if (argv == NULL)
-	{
-	    /*
-	     * Account for possible multiple args in p_shcf.
-	     */
-	    p = p_shcf;
-	    for (;;)
-	    {
-		p = skiptowhite(p);
-		if (*p == NUL)
-		    break;
-		++argc;
-		p = skipwhite(p);
-	    }
+    if (mch_parse_cmd(newcmd, TRUE, &argv, &argc) == FAIL)
+	goto error;
 
-	    argv = (char **)alloc((unsigned)((argc + 4) * sizeof(char *)));
-	    if (argv == NULL)	    /* out of memory */
-		goto error;
-	}
-    }
     if (cmd != NULL)
     {
 	char_u	*s;
@@ -5006,6 +5021,97 @@
 #endif /* USE_SYSTEM */
 }
 
+#if defined(FEAT_JOB) || defined(PROTO)
+    void
+mch_start_job(char **argv, job_T *job)
+{
+    pid_t pid = fork();
+
+    if (pid  == -1)	/* maybe we should use vfork() */
+    {
+	job->jv_status = JOB_FAILED;
+    }
+    else if (pid == 0)
+    {
+	/* child */
+	reset_signals();		/* handle signals normally */
+
+# ifdef HAVE_SETSID
+	/* Create our own process group, so that the child and all its
+	 * children can be kill()ed.  Don't do this when using pipes,
+	 * because stdin is not a tty, we would lose /dev/tty. */
+	(void)setsid();
+# endif
+
+	/* See above for type of argv. */
+	execvp(argv[0], argv);
+
+	perror("executing job failed");
+	_exit(EXEC_FAILED);	    /* exec failed, return failure code */
+    }
+    else
+    {
+	/* parent */
+	job->jv_pid = pid;
+	job->jv_status = JOB_STARTED;
+    }
+}
+
+    char *
+mch_job_status(job_T *job)
+{
+# ifdef HAVE_UNION_WAIT
+    union wait	status;
+# else
+    int		status = -1;
+# endif
+    pid_t	wait_pid = 0;
+
+# ifdef __NeXT__
+    wait_pid = wait4(job->jv_pid, &status, WNOHANG, (struct rusage *)0);
+# else
+    wait_pid = waitpid(job->jv_pid, &status, WNOHANG);
+# endif
+    if (wait_pid == -1)
+    {
+	/* process must have exited */
+	job->jv_status = JOB_ENDED;
+	return "dead";
+    }
+    if (wait_pid == 0)
+	return "run";
+    if (WIFEXITED(status))
+    {
+	/* LINTED avoid "bitwise operation on signed value" */
+	job->jv_exitval = WEXITSTATUS(status);
+	job->jv_status = JOB_ENDED;
+	return "dead";
+    }
+    return "run";
+}
+
+    int
+mch_stop_job(job_T *job, char_u *how)
+{
+    int sig = -1;
+
+    if (STRCMP(how, "hup") == 0)
+	sig = SIGHUP;
+    else if (*how == NUL || STRCMP(how, "term") == 0)
+	sig = SIGTERM;
+    else if (STRCMP(how, "quit") == 0)
+	sig = SIGQUIT;
+    else if (STRCMP(how, "kill") == 0)
+	sig = SIGKILL;
+    else if (isdigit(*how))
+	sig = atoi((char *)how);
+    else
+	return FAIL;
+    kill(job->jv_pid, sig);
+    return OK;
+}
+#endif
+
 /*
  * Check for CTRL-C typed by reading all available characters.
  * In cooked mode we should get SIGINT, no need to check.
diff --git a/src/proto/os_unix.pro b/src/proto/os_unix.pro
index 8442dea..bc250c8 100644
--- a/src/proto/os_unix.pro
+++ b/src/proto/os_unix.pro
@@ -55,7 +55,11 @@
 int mch_get_shellsize(void);
 void mch_set_shellsize(void);
 void mch_new_shellsize(void);
+int mch_parse_cmd(char_u *cmd, int use_shcf, char ***argv, int *argc);
 int mch_call_shell(char_u *cmd, int options);
+void mch_start_job(char **argv, job_T *job);
+char *mch_job_status(job_T *job);
+int mch_stop_job(job_T *job, char_u *how);
 void mch_breakcheck(void);
 int mch_expandpath(garray_T *gap, char_u *path, int flags);
 int mch_expand_wildcards(int num_pat, char_u **pat, int *num_file, char_u ***file, int flags);
diff --git a/src/structs.h b/src/structs.h
index d10547a..5a2d6fd 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -1110,17 +1110,19 @@
 
 typedef struct listvar_S list_T;
 typedef struct dictvar_S dict_T;
+typedef struct jobvar_S job_T;
 
 typedef enum
 {
     VAR_UNKNOWN = 0,
-    VAR_NUMBER,	/* "v_number" is used */
-    VAR_STRING,	/* "v_string" is used */
-    VAR_FUNC,	/* "v_string" is function name */
-    VAR_LIST,	/* "v_list" is used */
-    VAR_DICT,	/* "v_dict" is used */
-    VAR_FLOAT,	/* "v_float" is used */
-    VAR_SPECIAL	/* "v_number" is used */
+    VAR_NUMBER,	 /* "v_number" is used */
+    VAR_STRING,	 /* "v_string" is used */
+    VAR_FUNC,	 /* "v_string" is function name */
+    VAR_LIST,	 /* "v_list" is used */
+    VAR_DICT,	 /* "v_dict" is used */
+    VAR_FLOAT,	 /* "v_float" is used */
+    VAR_SPECIAL, /* "v_number" is used */
+    VAR_JOB	 /* "v_job" is used */
 } vartype_T;
 
 /*
@@ -1139,6 +1141,9 @@
 	char_u		*v_string;	/* string value (can be NULL!) */
 	list_T		*v_list;	/* list value (can be NULL!) */
 	dict_T		*v_dict;	/* dict value (can be NULL!) */
+#ifdef FEAT_JOB
+	job_T		*v_job;		/* job value (can be NULL!) */
+#endif
     }		vval;
 } typval_T;
 
@@ -1204,7 +1209,6 @@
     char_u	di_flags;	/* flags (only used for variable) */
     char_u	di_key[1];	/* key (actually longer!) */
 };
-
 typedef struct dictitem_S dictitem_T;
 
 #define DI_FLAGS_RO	1  /* "di_flags" value: read-only variable */
@@ -1228,6 +1232,30 @@
     dict_T	*dv_used_prev;	/* previous dict in used dicts list */
 };
 
+typedef enum
+{
+    JOB_FAILED,
+    JOB_STARTED,
+    JOB_ENDED
+} jobstatus_T;
+
+/*
+ * Structure to hold info about a Job.
+ */
+struct jobvar_S
+{
+#ifdef UNIX
+    pid_t	jv_pid;
+    int		jv_exitval;
+#endif
+#ifdef WIN32
+    PROCESS_INFORMATION	jf_pi;
+#endif
+    jobstatus_T	jv_status;
+
+    int		jv_refcount;	/* reference count */
+};
+
 /* structure used for explicit stack while garbage collecting hash tables */
 typedef struct ht_stack_S
 {
diff --git a/src/testdir/test_channel.vim b/src/testdir/test_channel.vim
index 46bd364..94ad81c 100644
--- a/src/testdir/test_channel.vim
+++ b/src/testdir/test_channel.vim
@@ -8,8 +8,9 @@
 " This test requires the Python command to run the test server.
 " This most likely only works on Unix and Windows.
 if has('unix')
-  " We also need the pkill command to make sure the server can be stopped.
-  if !executable('python') || !executable('pkill')
+  " We also need the job feature or the pkill command to make sure the server
+  " can be stopped.
+  if !(executable('python') && (has('job') || executable('pkill')))
     finish
   endif
 elseif has('win32')
@@ -27,7 +28,9 @@
   " The Python program writes the port number in Xportnr.
   call delete("Xportnr")
 
-  if has('win32')
+  if has('job')
+    let s:job = job_start("python test_channel.py")
+  elseif has('win32')
     silent !start cmd /c start "test_channel" py test_channel.py
   else
     silent !python test_channel.py&
@@ -62,7 +65,9 @@
 endfunc
 
 func s:kill_server()
-  if has('win32')
+  if has('job')
+    call job_stop(s:job)
+  elseif has('win32')
     call system('taskkill /IM py.exe /T /F /FI "WINDOWTITLE eq test_channel"')
   else
     call system("pkill -f test_channel.py")
diff --git a/src/version.c b/src/version.c
index 9d02580..ddd733d 100644
--- a/src/version.c
+++ b/src/version.c
@@ -289,6 +289,11 @@
 #else
 	"-insert_expand",
 #endif
+#ifdef FEAT_JOB
+	"+job",
+#else
+	"-job",
+#endif
 #ifdef FEAT_JUMPLIST
 	"+jumplist",
 #else
@@ -743,6 +748,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1274,
+/**/
     1273,
 /**/
     1272,