changeset 7957:b74549818500 v7.4.1274

commit https://github.com/vim/vim/commit/835dc636a5350f610b62f110227d2363b5b2880a Author: Bram Moolenaar <Bram@vim.org> Date: Sun Feb 7 14:27:38 2016 +0100 patch 7.4.1274 Problem: Cannot run a job. Solution: Add job_start(), job_status() and job_stop(). Currently only works for Unix.
author Christian Brabandt <cb@256bit.org>
date Sun, 07 Feb 2016 14:30:04 +0100
parents 31dfe91e5016
children 91b85ecdfd4d
files runtime/doc/channel.txt runtime/doc/eval.txt src/eval.c src/feature.h src/os_unix.c src/proto/os_unix.pro src/structs.h src/testdir/test_channel.vim src/version.c
diffstat 9 files changed, 611 insertions(+), 104 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/channel.txt
+++ b/runtime/doc/channel.txt
@@ -1,4 +1,4 @@
-*channel.txt*      For Vim version 7.4.  Last change: 2016 Feb 05
+*channel.txt*      For Vim version 7.4.  Last change: 2016 Feb 06
 
 
 		  VIM REFERENCE MANUAL	  by Bram Moolenaar
@@ -93,7 +93,7 @@ The default is zero, don't wait, which i
 be running already.  A negative number waits forever.
 
 "timeout" is the time to wait for a request when blocking, using
-ch_sendexpr().  Again in millisecons.  The default si 2000 (2 seconds).
+ch_sendexpr().  Again in milliseconds.  The default is 2000 (2 seconds).
 
 When "mode" is "json" the "msg" argument is the body of the received message,
 converted to Vim types.
@@ -104,13 +104,13 @@ possible to receive a message after send
 
 The handler can be added or changed later: >
     call ch_setcallback(handle, {callback})
-When "callback is empty (zero or an empty string) the handler is removed.
+When "callback" is empty (zero or an empty string) the handler is removed.
 NOT IMPLEMENTED YET
 
 The timeout can be changed later: >
     call ch_settimeout(handle, {msec})
 NOT IMPLEMENTED YET
-
+							  *E906*
 Once done with the channel, disconnect it like this: >
     call ch_close(handle)
 
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -1,4 +1,4 @@
-*eval.txt*	For Vim version 7.4.  Last change: 2016 Feb 05
+*eval.txt*	For Vim version 7.4.  Last change: 2016 Feb 07
 
 
 		  VIM REFERENCE MANUAL	  by Bram Moolenaar
@@ -37,7 +37,7 @@ 1. Variables						*variables*
 
 1.1 Variable types ~
 							*E712*
-There are six types of variables:
+There are eight types of variables:
 
 Number		A 32 or 64 bit signed number.  |expr-number| *Number*
 		Examples:  -123  0x10  0177
@@ -49,9 +49,6 @@ Float		A floating point number. |floatin
 String		A NUL terminated string of 8-bit unsigned characters (bytes).
 		|expr-string| Examples: "ab\txx\"--"  'x-z''a,c'
 
-Funcref		A reference to a function |Funcref|.
-		Example: function("strlen")
-
 List		An ordered sequence of items |List|.
 		Example: [1, 2, ['a', 'b']]
 
@@ -59,6 +56,13 @@ Dictionary	An associative, unordered arr
 		value. |Dictionary|
 		Example: {'blue': "#0000ff", 'red': "#ff0000"}
 
+Funcref		A reference to a function |Funcref|.
+		Example: function("strlen")
+
+Special		v:false, v:true, v:none and v:null
+
+Job		Used for job control, see |job_start()|.
+
 The Number and String types are converted automatically, depending on how they
 are used.
 
@@ -95,15 +99,16 @@ Note that in the command >
 "foo" is converted to 0, which means FALSE.  To test for a non-empty string,
 use empty(): >
 	:if !empty("foo")
-<				*E745* *E728* *E703* *E729* *E730* *E731*
-List, Dictionary and Funcref types are not automatically converted.
+<
+			*E745* *E728* *E703* *E729* *E730* *E731* *E908* *E910*
+List, Dictionary, Funcref and Job types are not automatically converted.
 
 							*E805* *E806* *E808*
 When mixing Number and Float the Number is converted to Float.	Otherwise
 there is no automatic conversion of Float.  You can use str2float() for String
 to Float, printf() for Float to String and float2nr() for Float to Number.
 
-						*E891* *E892* *E893* *E894*
+				*E891* *E892* *E893* *E894* *E907* *E911*
 When expecting a Float a Number can also be used, but nothing else.
 
 						*E706* *sticky-type-checking*
@@ -864,7 +869,7 @@ These three can be repeated and mixed.	E
 expr8							*expr8*
 -----
 expr8[expr1]		item of String or |List|	*expr-[]* *E111*
-
+							*E909*
 If expr8 is a Number or String this results in a String that contains the
 expr1'th single byte from expr8.  expr8 is used as a String, expr1 as a
 Number.  This doesn't recognize multi-byte encodings, see |byteidx()| for
@@ -1947,6 +1952,9 @@ invert( {expr})			Number  bitwise invert
 isdirectory( {directory})	Number	TRUE if {directory} is a directory
 islocked( {expr})		Number	TRUE if {expr} is locked
 items( {dict})			List	key-value pairs in {dict}
+job_start({command} [, {options}]) Job	start a job	
+job_status({job})		String	get the status of a job
+job_stop({job} [, {how}])	Number	stop a job
 join( {list} [, {sep}])		String	join {list} items into one String
 jsondecode( {string})		any	decode JSON
 jsonencode( {expr})		String	encode JSON
@@ -2668,6 +2676,7 @@ confirm({msg} [, {choices} [, {default} 
 
 ch_close({handle})					*ch_close()*
 		Close channel {handle}.  See |channel|.
+		{only available when compiled with the |+channel| feature}
 
 ch_open({address} [, {argdict}])				*ch_open()*
 		Open a channel to {address}.  See |channel|.
@@ -2677,7 +2686,7 @@ ch_open({address} [, {argdict}])				*ch_
 		{address} has the form "hostname:port", e.g.,
 		"localhost:8765".
 
-		If {argdict} is given it must be a |Directory|.  The optional
+		If {argdict} is given it must be a |Dictionary|.  The optional
 		items are:
 			mode        "raw" or "json".
 				    Default "json".
@@ -2686,10 +2695,11 @@ ch_open({address} [, {argdict}])				*ch_
 				    Default: none.
 			waittime    Specify connect timeout as milliseconds.
 				    Negative means forever.
-				    Default: 0.
+				    Default: 0 (don't wait)
 			timeout	    Specify response read timeout value as
 				    milliseconds. 
 				    Default: 2000.
+		{only available when compiled with the |+channel| feature}
 
 ch_sendexpr({handle}, {expr} [, {callback}])		*ch_sendexpr()*
 		Send {expr} over JSON channel {handle}.  See |channel-use|.
@@ -2704,10 +2714,14 @@ ch_sendexpr({handle}, {expr} [, {callbac
 		function.  It is called when the response is received.  See
 		|channel-callback|.
 
+		{only available when compiled with the |+channel| feature}
+
 ch_sendraw({handle}, {string} [, {callback}])		*ch_sendraw()*
 		Send {string} over raw channel {handle}.  See |channel-raw|.
 		Works like |ch_sendexpr()|, but does not decode the response.
 
+		{only available when compiled with the |+channel| feature}
+
 							*copy()*
 copy({expr})	Make a copy of {expr}.	For Numbers and Strings this isn't
 		different from using {expr} directly.
@@ -2888,9 +2902,12 @@ diff_hlID({lnum}, {col})				*diff_hlID()
 
 empty({expr})						*empty()*
 		Return the Number 1 if {expr} is empty, zero otherwise.
-		A |List| or |Dictionary| is empty when it does not have any
-		items.	A Number is empty when its value is zero.
-		|v:false|, |v:none| and |v:null| are empty, |v:true| is not.
+		- A |List| or |Dictionary| is empty when it does not have any
+		  items.
+		- A Number and Float is empty when its value is zero.
+		- |v:false|, |v:none| and |v:null| are empty, |v:true| is not.
+		- A Job is empty when it failed to start.
+
 		For a long |List| this is much faster than comparing the
 		length with zero.
 
@@ -4286,6 +4303,73 @@ items({dict})						*items()*
 		order.
 
 
+job_start({command} [, {options}])				*job_start()*
+		Start a job and return a Job object.  Unlike |system()| and
+		|:!cmd| this does not wait for the job to finish.
+
+		{command} can be a string.  This works best on MS-Windows.  On
+		Unix it is split up in white-separated parts to be passed to
+		execvp().  Arguments in double quotes can contain white space.
+
+		{command} can be a list, where the first item is the executable
+		and further items are the arguments.  All items are converted
+		to String.  This works best on Unix.
+
+		The command is executed directly, not through a shell, the
+		'shell' option is not used.  To use the shell: >
+	let job = job_start(["/bin/sh", "-c", "echo hello"])
+<		Or: >
+	let job = job_start('/bin/sh -c "echo hello"')
+<		However, the status of the job will now be the status of the
+		shell, and stopping the job means stopping the shell and the
+		command may continue to run.
+
+		On Unix $PATH is used to search for the executable only when
+		the command does not contain a slash.
+
+		The job will use the same terminal as Vim.  If it reads from
+		stdin the job and Vim will be fighting over input, that
+		doesn't work.  Redirect stdin and stdout to avoid problems: >
+	let job = job_start(['sh', '-c', "myserver </dev/null >/dev/null"])
+<
+		The returned Job object can be used to get the status with
+		|job_status()| and stop the job with |job_stop()|.
+
+		{options} must be a Dictionary.  It can contain these optional
+		items:
+			killonexit	When non-zero kill the job when Vim
+					exits. (default: 0, don't kill)
+
+		{only available when compiled with the |+channel| feature}
+
+job_status({job})						*job_status()*
+		Returns a String with the status of {job}:
+			"run"	job is running
+			"fail"	job failed to start
+			"dead"	job died or was stopped after running
+
+		{only available when compiled with the |+channel| feature}
+
+job_stop({job} [, {how}])					*job_stop()*
+		Stop the {job}.  This can also be used to signal the job.
+
+		When {how} is omitted or is "term" the job will be terminated
+		normally.  For Unix SIGTERM is sent.
+		Other values:
+			"hup"	Unix: SIGHUP
+			"quit"	Unix: SIGQUIT
+			"kill"	Unix: SIGKILL (strongest way to stop)
+			number	Unix: signal with that number
+
+		The result is a Number: 1 if the operation could be executed,
+		0 if "how" is not supported on the system.
+		Note that even when the operation was executed, whether the
+		job was actually stopped needs to be checked with
+		job_status().
+		The operation will even be done when the job wasn't running.
+
+		{only available when compiled with the |+channel| feature}
+
 join({list} [, {sep}])					*join()*
 		Join the items in {list} together into one String.
 		When {sep} is specified it is put in between the items.  If
@@ -6692,6 +6776,7 @@ type({expr})	The result is a Number, dep
 			Float:	    5
 			Boolean:    6 (v:false and v:true)
 			None	    7 (v:null and v:none)
+			Job	    8
 		To avoid the magic numbers it should be used this way: >
 			:if type(myvar) == type(0)
 			:if type(myvar) == type("")
--- a/src/eval.c
+++ b/src/eval.c
@@ -451,6 +451,9 @@ static dict_T *dict_copy(dict_T *orig, i
 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_invert(typval_T *argvars, 
 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 @@ tv_op(typval_T *tv1, typval_T *tv2, char
     {
 	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 @@ item_lock(typval_T *tv, int deep, int lo
 	case VAR_FUNC:
 	case VAR_FLOAT:
 	case VAR_SPECIAL:
+	case VAR_JOB:
 	    break;
 
 	case VAR_LIST:
@@ -5339,6 +5349,7 @@ eval_index(
 	    return FAIL;
 #endif
 	case VAR_SPECIAL:
+	case VAR_JOB:
 	    if (verbose)
 		EMSG(_("E909: Cannot index a special variable"));
 	    return FAIL;
@@ -5446,10 +5457,11 @@ eval_index(
 
 	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 @@ tv_equal(
 
     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 @@ tv_equal(
 	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 @@ tv_equal(
 
 	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 @@ garbage_collect(void)
 }
 
 /*
- * 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 @@ free_unref_items(int copyID)
 	}
 	ll = ll_next;
     }
+
     return did_free;
 }
 
@@ -7694,6 +7710,40 @@ failret:
     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 @@ echo_string(
 	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 @@ tv2string(
 	case VAR_LIST:
 	case VAR_DICT:
 	case VAR_SPECIAL:
+	case VAR_JOB:
 	case VAR_UNKNOWN:
 	    break;
     }
@@ -8148,6 +8200,11 @@ static struct fst
     {"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 @@ f_empty(typval_T *argvars, typval_T *ret
 	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 @@ f_empty(typval_T *argvars, typval_T *ret
 	    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 @@ f_has(typval_T *argvars, typval_T *rettv
 #ifdef FEAT_INS_EXPAND
 	"insert_expand",
 #endif
+#ifdef FEAT_JOB
+	"job",
+#endif
 #ifdef FEAT_JUMPLIST
 	"jumplist",
 #endif
@@ -14188,6 +14253,161 @@ f_items(typval_T *argvars, typval_T *ret
     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 @@ f_len(typval_T *argvars, typval_T *rettv
 	case VAR_SPECIAL:
 	case VAR_FLOAT:
 	case VAR_FUNC:
+	case VAR_JOB:
 	    EMSG(_("E701: Invalid type for len()"));
 	    break;
     }
@@ -19658,6 +19879,9 @@ f_type(typval_T *argvars, typval_T *rett
 	     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 @@ free_tv(typval_T *varp)
 	    case VAR_DICT:
 		dict_unref(varp->vval.v_dict);
 		break;
+	    case VAR_JOB:
+#ifdef FEAT_JOB
+		job_unref(varp->vval.v_job);
+		break;
+#endif
 	    case VAR_NUMBER:
-#ifdef FEAT_FLOAT
 	    case VAR_FLOAT:
-#endif
 	    case VAR_UNKNOWN:
 	    case VAR_SPECIAL:
 		break;
@@ -21065,11 +21292,17 @@ clear_tv(typval_T *varp)
 	    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 @@ get_tv_number_chk(typval_T *varp, int *d
     {
 	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 @@ get_tv_number_chk(typval_T *varp, int *d
 	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 @@ get_tv_float(typval_T *varp)
     {
 	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 @@ get_tv_float(typval_T *varp)
 	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 @@ get_tv_string_buf_chk(typval_T *varp, ch
 	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 @@ get_tv_string_buf_chk(typval_T *varp, ch
 	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 @@ copy_tv(typval_T *from, typval_T *to)
 	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 @@ item_copy(
     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 @@ write_viminfo_varlist(FILE *fp)
 
 		    case VAR_UNKNOWN:
 		    case VAR_FUNC:
+		    case VAR_JOB:
 				     continue;
 		}
 		fprintf(fp, "!%s\t%s\t", this_var->di_key, s);
--- 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.
  */
--- a/src/os_unix.c
+++ b/src/os_unix.c
@@ -3919,6 +3919,66 @@ wait4pid(pid_t child, waitstatus *status
     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 @@ mch_call_shell(
 # 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 @@ mch_call_shell(
     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 @@ mch_call_shell(
     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);
-	    }
-
-	    argv = (char **)alloc((unsigned)((argc + 4) * sizeof(char *)));
-	    if (argv == NULL)	    /* out of memory */
-		goto error;
-	}
-    }
+    if (mch_parse_cmd(newcmd, TRUE, &argv, &argc) == FAIL)
+	goto error;
+
     if (cmd != NULL)
     {
 	char_u	*s;
@@ -5006,6 +5021,97 @@ error:
 #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.
--- a/src/proto/os_unix.pro
+++ b/src/proto/os_unix.pro
@@ -55,7 +55,11 @@ int mch_screenmode(char_u *arg);
 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);
--- a/src/structs.h
+++ b/src/structs.h
@@ -1110,17 +1110,19 @@ typedef double	float_T;
 
 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 @@ typedef struct
 	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 @@ struct dictitem_S
     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 @@ struct dictvar_S
     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
 {
--- a/src/testdir/test_channel.vim
+++ b/src/testdir/test_channel.vim
@@ -8,8 +8,9 @@ endif
 " 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 @@ func s:start_server()
   " 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 @@ func s:start_server()
 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")
--- a/src/version.c
+++ b/src/version.c
@@ -289,6 +289,11 @@ static char *(features[]) =
 #else
 	"-insert_expand",
 #endif
+#ifdef FEAT_JOB
+	"+job",
+#else
+	"-job",
+#endif
 #ifdef FEAT_JUMPLIST
 	"+jumplist",
 #else
@@ -743,6 +748,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1274,
+/**/
     1273,
 /**/
     1272,