diff --git a/Filelist b/Filelist
index b324933..77fc0a9 100644
--- a/Filelist
+++ b/Filelist
@@ -33,6 +33,7 @@ SRC_ALL =	\
 		src/gui_beval.h \
 		src/hardcopy.c \
 		src/hashtab.c \
+		src/job.c \
 		src/keymap.h \
 		src/macros.h \
 		src/main.c \
@@ -113,6 +114,7 @@ SRC_ALL =	\
 		src/proto/gui_beval.pro \
 		src/proto/hardcopy.pro \
 		src/proto/hashtab.pro \
+		src/proto/job.pro \
 		src/proto/main.pro \
 		src/proto/mark.pro \
 		src/proto/mbyte.pro \
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index be0e667..d09afe1 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -6410,6 +6410,8 @@ ebcdic			Compiled on a machine with ebcdic character set.
 emacs_tags		Compiled with support for Emacs tags.
 eval			Compiled with expression evaluation support.  Always
 			true, of course!
+job_control		Compiled with job control functions and autocommand
+			for multitasking with other processes.
 ex_extra		Compiled with extra Ex commands |+ex_extra|.
 extra_search		Compiled with support for |'incsearch'| and
 			|'hlsearch'|
diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt
index 4a1c64a..f3d0b30 100644
--- a/runtime/doc/various.txt
+++ b/runtime/doc/various.txt
@@ -324,6 +324,7 @@ N  *+digraphs*		|digraphs| *E196*
    *+dnd*		Support for DnD into the "~ register |quote_~|.
 B  *+emacs_tags*	|emacs-tags| files
 N  *+eval*		expression evaluation |eval.txt|
+   *+job_control*	Compiled with job control module
 N  *+ex_extra*		Vim's extra Ex commands: |:center|, |:left|,
 			|:normal|, |:retab| and |:right|
 N  *+extra_search*	|'hlsearch'| and |'incsearch'| options.
diff --git a/src/Makefile b/src/Makefile
index bceb65c..58cd6f1 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -1443,6 +1443,7 @@ BASIC_SRC = \
 	hashtab.c \
 	if_cscope.c \
 	if_xcmdsrv.c \
+	job.c \
 	main.c \
 	mark.c \
 	memfile.c \
@@ -1533,6 +1534,7 @@ OBJ_COMMON = \
 	$(HANGULIN_OBJ) \
 	objects/if_cscope.o \
 	objects/if_xcmdsrv.o \
+	objects/job.o \
 	objects/mark.o \
         objects/memline.o \
 	objects/menu.o \
@@ -2625,6 +2627,9 @@ objects/if_tcl.o: if_tcl.c
 objects/integration.o: integration.c
 	$(CCC) -o $@ integration.c
 
+objects/job.o: job.c
+	$(CCC) -o $@ job.c
+
 objects/main.o: main.c
 	$(CCC) -o $@ main.c
 
diff --git a/src/config.h.in b/src/config.h.in
index a4e216b..501e78c 100644
--- a/src/config.h.in
+++ b/src/config.h.in
@@ -323,6 +323,9 @@
 /* Define for linking via dlopen() or LoadLibrary() */
 #undef DYNAMIC_LUA
 
+/* Define if you want to use thread-safe message queue. */
+#undef FEAT_JOB_CONTROL
+
 /* Define if you want to include the MzScheme interpreter. */
 #undef FEAT_MZSCHEME
 
diff --git a/src/configure.in b/src/configure.in
index 2718d31..795468a 100644
--- a/src/configure.in
+++ b/src/configure.in
@@ -2801,7 +2801,6 @@ AC_CHECK_HEADERS(sys/sysctl.h, [], [],
 #  include <sys/param.h>
 #endif])
 
-
 dnl pthread_np.h may exist but can only be used after including pthread.h
 AC_MSG_CHECKING([for pthread_np.h])
 AC_TRY_COMPILE([
@@ -2812,6 +2811,15 @@ AC_TRY_COMPILE([
 		      AC_DEFINE(HAVE_PTHREAD_NP_H),
 	      AC_MSG_RESULT(no))
 
+AC_MSG_CHECKING(--enable-jobcontrol argument)
+AC_ARG_ENABLE(jobcontrol,
+	[  --enable-jobcontrol[=OPTS]   Include job control module. [default=no] [OPTS=no/yes]], ,
+	[enable_jobcontrol="no"])
+AC_MSG_RESULT($enable_jobcontrol)
+if test "$enable_jobcontrol" = "yes"; then
+    AC_DEFINE(FEAT_JOB_CONTROL)
+fi
+
 AC_CHECK_HEADERS(strings.h)
 if test "x$MACOSX" = "xyes"; then
   dnl The strings.h file on OS/X contains a warning and nothing useful.
diff --git a/src/edit.c b/src/edit.c
index d3c0208..0bfb1a3 100644
--- a/src/edit.c
+++ b/src/edit.c
@@ -1188,6 +1188,12 @@ doESCkey:
 	    break;
 #endif
 
+#ifdef FEAT_JOB_CONTROL
+	case K_JOB_ACTIVITY:
+	    job_activity_autocmds();
+	    break;
+#endif
+
 #ifdef FEAT_GUI_W32
 	    /* On Win32 ignore <M-F4>, we get it when closing the window was
 	     * cancelled. */
diff --git a/src/eval.c b/src/eval.c
index 8a62fa8..34db1c5 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -359,6 +359,7 @@ static struct vimvar
     {VV_NAME("hlsearch",	 VAR_NUMBER), 0},
     {VV_NAME("oldfiles",	 VAR_LIST), 0},
     {VV_NAME("windowid",	 VAR_NUMBER), VV_RO},
+    {VV_NAME("job_data",	 VAR_LIST), 0},
 };
 
 /* shorthand */
@@ -422,7 +423,6 @@ static int dict_equal __ARGS((dict_T *d1, dict_T *d2, int ic, int recursive));
 static int tv_equal __ARGS((typval_T *tv1, typval_T *tv2, int ic, int recursive));
 static long list_find_nr __ARGS((list_T *l, long idx, int *errorp));
 static long list_idx_of_item __ARGS((list_T *l, listitem_T *item));
-static int list_append_number __ARGS((list_T *l, varnumber_T n));
 static int list_extend __ARGS((list_T	*l1, list_T *l2, listitem_T *bef));
 static int list_concat __ARGS((list_T *l1, list_T *l2, typval_T *tv));
 static list_T *list_copy __ARGS((list_T *orig, int deep, int copyID));
@@ -591,6 +591,9 @@ static void f_invert __ARGS((typval_T *argvars, typval_T *rettv));
 static void f_isdirectory __ARGS((typval_T *argvars, typval_T *rettv));
 static void f_islocked __ARGS((typval_T *argvars, typval_T *rettv));
 static void f_items __ARGS((typval_T *argvars, typval_T *rettv));
+static void f_job_start __ARGS((typval_T *argvars, typval_T *rettv));
+static void f_job_stop __ARGS((typval_T *argvars, typval_T *rettv));
+static void f_job_write __ARGS((typval_T *argvars, typval_T *rettv));
 static void f_join __ARGS((typval_T *argvars, typval_T *rettv));
 static void f_keys __ARGS((typval_T *argvars, typval_T *rettv));
 static void f_last_buffer_nr __ARGS((typval_T *argvars, typval_T *rettv));
@@ -6385,7 +6388,7 @@ list_append_string(l, str, len)
  * Append "n" to list "l".
  * Returns FAIL when out of memory.
  */
-    static int
+    int
 list_append_number(l, n)
     list_T	*l;
     varnumber_T	n;
@@ -7983,6 +7986,11 @@ static struct fst
     {"isdirectory",	1, 1, f_isdirectory},
     {"islocked",	1, 1, f_islocked},
     {"items",		1, 1, f_items},
+#ifdef FEAT_JOB_CONTROL
+    {"jobstart",	2, 3, f_job_start},
+    {"jobstop",		1, 1, f_job_stop},
+    {"jobwrite",	2, 2, f_job_write},
+#endif
     {"join",		1, 2, f_join},
     {"keys",		1, 1, f_keys},
     {"last_buffer_nr",	0, 0, f_last_buffer_nr},/* obsolete */
@@ -12248,6 +12256,9 @@ f_has(argvars, rettv)
 	"emacs_tags",
 #endif
 	"eval",	    /* always present, of course! */
+#ifdef FEAT_JOB_CONTROL
+	"job_control",
+#endif
 #ifdef FEAT_EX_EXTRA
 	"ex_extra",
 #endif
@@ -13485,6 +13496,163 @@ f_items(argvars, rettv)
     dict_list(argvars, rettv, 2);
 }
 
+#ifdef FEAT_JOB_CONTROL
+/*
+ * "jobstart()" function
+ */
+    static void
+f_job_start(argvars, rettv)
+    typval_T	*argvars;
+    typval_T	*rettv;
+{
+    list_T	*args = NULL;
+    listitem_T	*arg;
+    int		i, argvl, argsl;
+    char_u	**argv = NULL;
+
+    rettv->v_type = VAR_NUMBER;
+    rettv->vval.v_number = 0;
+
+    if (check_restricted() || check_secure())
+	goto cleanup;
+
+    if (argvars[0].v_type != VAR_STRING ||
+	    argvars[1].v_type != VAR_STRING ||
+	    (argvars[2].v_type != VAR_LIST &&
+	     argvars[2].v_type != VAR_UNKNOWN))
+    {
+	EMSG(_(e_invarg));
+	goto cleanup;
+    }
+
+    argsl = 0;
+    if (argvars[2].v_type == VAR_LIST)
+    {
+	args = argvars[2].vval.v_list;
+	argsl = args->lv_len;
+	/* Assert that all list items are strings */
+	for (arg = args->lv_first; arg != NULL; arg = arg->li_next)
+	    if (arg->li_tv.v_type != VAR_STRING)
+	    {
+		EMSG(_(e_invarg));
+		goto cleanup;
+	    }
+    }
+
+    if (!mch_can_exe(get_tv_string(&argvars[1])))
+    {
+	/* String is not executable */
+	EMSG2(e_jobexe, get_tv_string(&argvars[1]));
+	goto cleanup;
+    }
+
+    /* Allocate extra memory for the argument vector and the NULL pointer */
+    argvl = argsl + 2;
+    argv = (char_u **)alloc(sizeof(char_u *) * argvl);
+    if (argv == NULL)
+	goto cleanup;
+
+    /* Initialize the array */
+    for (i = 0; i < argvl; ++i)
+	argv[i] = NULL;
+
+    /* Copy program name */
+    argv[0] = vim_strsave(argvars[1].vval.v_string);
+    if (argv[0] == NULL)
+	goto cleanup;
+
+
+    i = 1;
+    /* Copy arguments to the vector */
+    if (argsl > 0)
+	for (arg = args->lv_first; arg != NULL; arg = arg->li_next)
+	{
+	    argv[i] = vim_strsave(arg->li_tv.vval.v_string);
+	    if (argv[i++] == NULL)
+		goto cleanup;
+	}
+
+    rettv->vval.v_number =
+       	job_start(vim_strsave(argvars[0].vval.v_string), argv);
+
+    if (rettv->vval.v_number == 0)
+    {
+	EMSG(_(e_jobtblfull));
+	goto cleanup;
+    }
+
+cleanup:
+    if (rettv->vval.v_number > 0 || argv == NULL)
+	return;
+    /* Cleanup */
+    for (i = 0; i < argvl; ++i)
+	vim_free(argv[i]);
+    vim_free(argv);
+}
+
+/*
+ * "jobstop()" function
+ */
+    static void
+f_job_stop(argvars, rettv)
+    typval_T	*argvars;
+    typval_T	*rettv;
+{
+    rettv->v_type = VAR_NUMBER;
+    rettv->vval.v_number = 0;
+
+    if (check_restricted() || check_secure())
+	return;
+
+    if (argvars[0].v_type != VAR_NUMBER)
+    {
+	EMSG(_(e_invarg));
+	return;
+    }
+
+    if (job_stop(argvars[0].vval.v_number) <= 0)
+    {
+	EMSG(_(e_invjob));
+	return;
+    }
+
+    rettv->vval.v_number = 1;
+}
+
+/*
+ * "jobwrite()" function
+ */
+    static void
+f_job_write(argvars, rettv)
+    typval_T	*argvars;
+    typval_T	*rettv;
+{
+    int res;
+    rettv->v_type = VAR_NUMBER;
+    rettv->vval.v_number = 0;
+
+    if (check_restricted() || check_secure())
+	return;
+
+    if (argvars[0].v_type != VAR_NUMBER || argvars[1].v_type != VAR_STRING)
+    {
+	EMSG(_(e_invarg));
+	return;
+    }
+
+    res = job_write(argvars[0].vval.v_number, argvars[1].vval.v_string,
+		STRLEN(argvars[1].vval.v_string));
+
+    if (res <= 0)
+    {
+	if (res == -1) EMSG(_(e_invjob));
+	else EMSG(_(e_outofmem));
+    }
+
+    rettv->vval.v_number = 1;
+}
+#endif
+
 /*
  * "join()" function
  */
diff --git a/src/ex_getln.c b/src/ex_getln.c
index f1f39d2..29e56c3 100644
--- a/src/ex_getln.c
+++ b/src/ex_getln.c
@@ -926,6 +926,11 @@ getcmdline(firstc, count, indent)
 	 */
 	switch (c)
 	{
+#ifdef FEAT_JOB_CONTROL
+	case K_JOB_ACTIVITY:
+	    job_activity_autocmds();
+	    goto cmdline_not_changed;
+#endif
 	case K_BS:
 	case Ctrl_H:
 	case K_DEL:
diff --git a/src/fileio.c b/src/fileio.c
index cb22bd3..b90fc19 100644
--- a/src/fileio.c
+++ b/src/fileio.c
@@ -7733,6 +7733,7 @@ static struct event_name
     {"InsertEnter",	EVENT_INSERTENTER},
     {"InsertLeave",	EVENT_INSERTLEAVE},
     {"InsertCharPre",	EVENT_INSERTCHARPRE},
+    {"JobActivity",	EVENT_JOBACTIVITY},
     {"MenuPopup",	EVENT_MENUPOPUP},
     {"QuickFixCmdPost",	EVENT_QUICKFIXCMDPOST},
     {"QuickFixCmdPre",	EVENT_QUICKFIXCMDPRE},
@@ -9386,7 +9387,7 @@ apply_autocmds_group(event, fname, fname_io, force, group, buf, eap)
     {
 	sfname = vim_strsave(fname);
 	/* Don't try expanding FileType, Syntax, FuncUndefined, WindowID,
-	 * ColorScheme or QuickFixCmd* */
+	 * ColorScheme, QuickFixCmd or JobActivity */
 	if (event == EVENT_FILETYPE
 		|| event == EVENT_SYNTAX
 		|| event == EVENT_FUNCUNDEFINED
@@ -9394,7 +9395,8 @@ apply_autocmds_group(event, fname, fname_io, force, group, buf, eap)
 		|| event == EVENT_SPELLFILEMISSING
 		|| event == EVENT_QUICKFIXCMDPRE
 		|| event == EVENT_COLORSCHEME
-		|| event == EVENT_QUICKFIXCMDPOST)
+		|| event == EVENT_QUICKFIXCMDPOST
+		|| event == EVENT_JOBACTIVITY)
 	    fname = vim_strsave(fname);
 	else
 	    fname = FullName_save(fname, FALSE);
diff --git a/src/getchar.c b/src/getchar.c
index fe6423d..ed195b3 100644
--- a/src/getchar.c
+++ b/src/getchar.c
@@ -3019,7 +3019,7 @@ inchar(buf, maxlen, wait_time, tb_change_cnt)
 
 	    for (;;)
 	    {
-		len = ui_inchar(dum, DUM_LEN, 0L, 0);
+		len = io_inchar(dum, DUM_LEN, 0L, 0);
 		if (len == 0 || (len == 1 && dum[0] == 3))
 		    break;
 	    }
@@ -3036,7 +3036,7 @@ inchar(buf, maxlen, wait_time, tb_change_cnt)
 	 * Fill up to a third of the buffer, because each character may be
 	 * tripled below.
 	 */
-	len = ui_inchar(buf, maxlen / 3, wait_time, tb_change_cnt);
+	len = io_inchar(buf, maxlen / 3, wait_time, tb_change_cnt);
     }
 
     if (typebuf_changed(tb_change_cnt))
@@ -3091,13 +3091,15 @@ fix_input_buffer(buf, len, script)
 	if (p[0] == NUL || (p[0] == K_SPECIAL && !script
 #ifdef FEAT_AUTOCMD
 		    /* timeout may generate K_CURSORHOLD */
-		    && (i < 2 || p[1] != KS_EXTRA || p[2] != (int)KE_CURSORHOLD)
+		    && (i < 2 || p[1] != KS_EXTRA
+		       	|| (p[2] != (int)KE_CURSORHOLD
+			    && p[2] != (int)KE_JOB_ACTIVITY)
 #endif
 #if defined(WIN3264) && !defined(FEAT_GUI)
 		    /* Win32 console passes modifiers */
 		    && (i < 2 || p[1] != KS_MODIFIER)
 #endif
-		    ))
+		    )))
 	{
 	    mch_memmove(p + 3, p + 1, (size_t)i);
 	    p[2] = K_THIRD(p[0]);
diff --git a/src/globals.h b/src/globals.h
index 916f7e3..7053051 100644
--- a/src/globals.h
+++ b/src/globals.h
@@ -1433,6 +1433,12 @@ EXTERN char_u e_invcmd[]	INIT(= N_("E476: Invalid command"));
 #if defined(UNIX) || defined(FEAT_SYN_HL) || defined(FEAT_SPELL)
 EXTERN char_u e_isadir2[]	INIT(= N_("E17: \"%s\" is a directory"));
 #endif
+#ifdef FEAT_JOB_CONTROL
+EXTERN char_u e_invjob[]	INIT(= N_("E900: Invalid job id"));
+EXTERN char_u e_jobpollerr[]	INIT(= N_("E901: Job poll failed"));
+EXTERN char_u e_jobtblfull[]	INIT(= N_("E902: Job table is full"));
+EXTERN char_u e_jobexe[]	INIT(= N_("E902: '%s' is not an executable"));
+#endif
 #ifdef FEAT_LIBCALL
 EXTERN char_u e_libcall[]	INIT(= N_("E364: Library call failed for \"%s()\""));
 #endif
diff --git a/src/job.c b/src/job.c
new file mode 100644
index 0000000..0a9e8a8
--- /dev/null
+++ b/src/job.c
@@ -0,0 +1,485 @@
+/* vi:set ts=8 sts=4 sw=4:
+ *
+ * VIM - Vi IMproved	by Bram Moolenaar
+ *
+ * Do ":help uganda"  in Vim to read copying and usage conditions.
+ * Do ":help credits" in Vim to see a list of people who contributed.
+ */
+
+/* Job control module for cooperating with child processes in a non-blocking
+ * way.
+ *
+ * Job polling is done by 'job_activity_poll', a drop-in replacement for
+ * 'ui_inchar' which is always called when idling for new characters are
+ * needed.
+ *
+ * When some job produces data, the K_JOB_ACTIVITY key is returned,
+ * which will be handled at higher levels by triggering 'JobActivity'
+ * autocommands matchin the job name. */
+#include "vim.h"
+
+#ifdef FEAT_JOB_CONTROL
+
+#include <unistd.h>
+#include <signal.h>
+#include <sys/select.h>
+#include <sys/types.h>
+
+#define POLL_INTERVAL 100 /* Interval used to poll for job activity */
+#define KILL_TIMEOUT 25
+#define MAX_RUNNING_JOBS 5
+#define BUF_SIZE 4096
+#define max(x,y) ((x) > (y) ? (x) : (y))
+
+/* Check if the job_id is valid */
+#define JOB_CHECK if (job_id <= 0 || job_id > MAX_RUNNING_JOBS || \
+	(job = job_table[job_id - 1]) == NULL) \
+    return -1
+
+/* Check if the job's process is alive */
+#define IS_ALIVE(job) kill(job->pid, 0) != -1
+
+/* Free job in a loop */
+#define JOB_FREE \
+    close(job->in); \
+    close(job->out); \
+    close(job->err); \
+    for (arg = job->argv; *arg != NULL; arg++) vim_free(*arg); \
+    vim_free(job->argv); \
+    vim_free(job->name); \
+    job_table[job->id - 1] = NULL; \
+    vim_free(job);
+
+struct job_T {
+    int id, in, out, err, stopped, kill_timeout;
+    pid_t pid;
+    char_u **argv;
+    /* Name is used to match JobActivity autocmds */
+    char_u *name;
+    /* Fixed-width buffer for stdout/stderr */
+    char_u stdout_buf[BUF_SIZE], stderr_buf[BUF_SIZE];
+    unsigned int stdout_buf_pos, stderr_buf_pos;
+    /* Pending stdin data is stored as a linked list, where each
+     * node points the data to be written and tracks its length and
+     * position in order to free and advance to the next node correctly. */
+    struct in_buf_node_T {
+	char_u *data;
+	int    len, pos;
+	struct in_buf_node_T *next;
+    } *stdin_head, *stdin_tail;
+} *job_table[MAX_RUNNING_JOBS] = {NULL};
+
+
+int job_count = 0;
+int initialized = FALSE;
+
+
+/* Poll every running job that has space in its buffers for data. */
+    static int
+jobs_poll()
+{
+    int res, mfd, i;
+    fd_set rfds, wfds;
+    struct timeval tv;
+    struct job_T *job;
+    struct in_buf_node_T *chunk;
+    size_t count, to_write;
+
+    if (!job_count)
+	return FALSE;
+
+    FD_ZERO(&rfds);
+    FD_ZERO(&wfds);
+    mfd = 0;
+
+    /* Iterate through each job, either adding their fds to the appropriate
+     * select set or killing stopped jobs */
+    for (i = 0; i < MAX_RUNNING_JOBS; i++)
+    {
+	if ((job = job_table[i]) == NULL) continue;
+	if (job->stopped)
+	{
+	    if (job->kill_timeout-- == KILL_TIMEOUT)
+	    {
+		/* Vimscript stopped this job, close stdin and
+		 * send SIGTERM */ 
+		close(job->in);
+		kill(job->pid, SIGTERM);
+	    }
+	    else if (job->kill_timeout == 0)
+	    {
+		/* We've waited too long, send SIGKILL */
+		kill(job->pid, SIGKILL);
+	    }
+	}
+	else
+	{
+	    /* Only poll stdin if we have something to write */
+	    if (job->stdin_head != NULL)
+	    {
+		FD_SET(job->in, &wfds);
+		mfd = max(mfd, job->in);
+	    }
+
+	    /* Only poll stdout/stderr if we have room in the buffer */
+	    if (job->stdout_buf_pos < BUF_SIZE)
+	    {
+		FD_SET(job->out, &rfds);
+		mfd = max(mfd, job->out);
+	    }
+
+	    if (job->stderr_buf_pos < BUF_SIZE)
+	    {
+		FD_SET(job->err, &rfds);
+		mfd = max(mfd, job->err);
+	    }
+	}
+    }
+
+    /* This argument is the maximum fd plus one */
+    mfd++;
+
+    /* Check what fds wont block when read/written */
+    tv.tv_sec = 0;
+    tv.tv_usec = 0;
+    res = select(mfd, &rfds, &wfds, NULL, &tv);
+
+    if (res <= 0)
+    {
+	if (res == -1 && errno != EINTR)
+	    EMSG(_(e_jobpollerr));
+	return FALSE;
+    }
+
+    /* Read/write pending data from/to stdfds */
+    for (i = 0; i < MAX_RUNNING_JOBS; i++)
+    {
+	if ((job = job_table[i]) == NULL) continue;
+
+	/* Collect pending stdout/stderr data into the job buffers */
+	if (FD_ISSET(job->out, &rfds))
+	{
+	    count = read(job->out, job->stdout_buf + job->stdout_buf_pos,
+		    BUF_SIZE - job->stdout_buf_pos);
+	    job->stdout_buf_pos += count;
+	}
+	if (FD_ISSET(job->err, &rfds))
+	{
+	    count = read(job->err, job->stderr_buf + job->stderr_buf_pos,
+		    BUF_SIZE - job->stderr_buf_pos);
+	    job->stderr_buf_pos += count;
+	}
+
+	if (FD_ISSET(job->in, &wfds))
+	{
+	    /* Stdin ready, write as much data as possible */
+	    while (job->stdin_head != NULL)
+	    {
+		chunk = job->stdin_head;
+		to_write = chunk->len - chunk->pos;
+		count = write(job->in, chunk->data, to_write);
+
+		if (count != to_write)
+		{
+		    /* Not enough space in the OS buffer, advance position
+		     * in the head and break */
+		    chunk->pos += count;
+		    break;
+		}
+
+		/* We have written everything on this chunk, advance
+		 * to the next */
+		job->stdin_head = chunk->next;
+		vim_free(chunk->data);
+		vim_free(chunk);
+	    }
+	}
+    }
+
+    return TRUE;
+}
+
+/* Set the JOB_ACTIVITY special key into the input buffer */
+    static int
+job_activity(buf)
+    char_u	*buf;
+{
+    buf[0] = K_SPECIAL;
+    buf[1] = KS_EXTRA;
+    buf[2] = (int)KE_JOB_ACTIVITY;
+
+    return 3;
+}
+
+
+/* Set the CURSORHOLD special key into the input buffer */
+    static int
+cursorhold(buf)
+    char_u	*buf;
+{
+    buf[0] = K_SPECIAL;
+    buf[1] = KS_EXTRA;
+    buf[2] = (int)KE_CURSORHOLD;
+
+    return 3;
+}
+
+
+/* Start a job and return its id. */
+    int
+job_start(name, argv)
+    char_u	*name;
+    char_u	**argv;
+{
+    struct job_T *job = NULL;
+    int in[2], out[2], err[2], i;
+
+    if (!initialized)
+    {
+	signal(SIGCHLD, SIG_IGN);
+	initialized = TRUE;
+    }
+
+    for (i = 0; i < MAX_RUNNING_JOBS; i++)
+	if (job_table[i] == NULL)
+	    break;
+
+    if (i == MAX_RUNNING_JOBS)
+	/* No more free slots */
+	return 0;
+
+    job = (struct job_T *)alloc(sizeof(struct job_T));
+
+    /* Create pipes for the stdio streams */
+    pipe(in);
+    pipe(out);
+    pipe(err);
+
+    if ((job->pid = fork()) == 0)
+    {
+	/* Child, copy the child parts of the pipes into the appropriate
+	 * stdio fds */
+	dup2(in[0], 0);
+	dup2(out[1], 1);
+	dup2(err[1], 2);
+    
+	/* TODO close all open file descriptors > 2 in a more reliable
+	 * way.
+	 *
+	 * For some options, see:
+	 * http://stackoverflow.com/questions/899038/getting-the-highest-allocated-file-descriptor/918469#918469 */
+	for (i = 3; i <= 2048; i++)
+	    close(i);
+
+	/* Reset ignored signal handlers(does vim ignore other signals?) */
+	signal(SIGCHLD, SIG_DFL);
+
+	/* Exec program */
+	execvp((const char *)argv[0], (char **)argv);
+    }
+    else
+    {
+	/* Close the other sides of the pipes */
+	close(in[0]);
+	close(out[1]);
+	close(err[1]);
+
+	/* Parent, initialize job structure */
+	job->id = i + 1;
+	job->name = name;
+	job->argv = argv;
+	job->in = in[1];
+	job->out = out[0];
+	job->err = err[0];
+	job->stdin_head = job->stdin_tail = NULL;
+	job->stdout_buf_pos = 0;
+	job->stderr_buf_pos = 0;
+	job->stopped = FALSE;
+	job->kill_timeout = KILL_TIMEOUT;
+
+	/* Insert into the job table */
+	job_table[i] = job;
+	job_count++;
+    }
+
+    /* Return job id */
+    return job->id;
+}
+
+/* Stop a job */
+    int
+job_stop(job_id)
+    int		job_id;
+{
+    struct job_T *job;
+
+    JOB_CHECK;
+    job->stopped = TRUE;
+
+    return 1;
+}
+
+/* Write data to the stdin of a job's process */
+    int
+job_write(job_id, data, len)
+    int		    job_id;
+    char_u	    *data;
+    unsigned int    len;
+{
+    struct job_T *job;
+    struct in_buf_node_T *chunk;
+   
+    JOB_CHECK;
+
+    chunk = (struct in_buf_node_T *)alloc(sizeof(struct in_buf_node_T));
+
+    if (chunk == NULL)
+	return 0; /* Not enough memory */
+
+    chunk->data = (char_u *)alloc(len);
+
+    if (chunk->data == NULL)
+    {
+	vim_free(chunk);
+	return 0; /* Not enough memory */
+    }
+
+    chunk->len = len;
+    chunk->pos = 0;
+    chunk->next = NULL;
+    mch_memmove(chunk->data, data, len);
+
+    if (job->stdin_head == NULL)
+	job->stdin_head = job->stdin_tail = chunk;
+    else
+	job->stdin_tail = job->stdin_tail->next = chunk;
+
+    return 1;
+}
+
+/* Cleanup all jobs */
+    void
+jobs_cleanup()
+{
+    struct timeval tv;
+    int i, kill_now = FALSE;
+    struct job_T *job;
+    char_u **arg;
+
+    /* Politely ask each job to terminate */
+    for (i = 0; i < MAX_RUNNING_JOBS; i++)
+	if ((job = job_table[i]) != NULL)
+	{
+	    close(job->in);
+	    kill(job_table[i]->pid, SIGTERM);
+	}
+
+    /* Give at most 300 ms for all jobs to exit, then start shooting */
+    for (i = 0; i < MAX_RUNNING_JOBS; i++)
+    {
+	if ((job = job_table[i]) == NULL) continue;
+	if (IS_ALIVE(job))
+	{
+	    if (!kill_now)
+	    {
+		tv.tv_sec = 0;
+		tv.tv_usec = 300000;
+		select(0, NULL, NULL, NULL, &tv);
+		kill_now = TRUE;
+	    }
+	    kill(job->pid, SIGKILL);
+	}
+	JOB_FREE;
+    }
+}
+
+/*
+ * Bridge between vim and the job control module, 'disguised' as a
+ * function that returns keys(where one of the special keys is K_JOBACTIVITY
+ */
+    int
+job_activity_poll(buf, maxlen, wtime, tb_change_cnt)
+    char_u	*buf;
+    int		maxlen;
+    long	wtime;
+    int		tb_change_cnt;
+{
+    int		len;
+    int		trig_curshold;
+    long	ellapsed;
+
+    /* Dont poll for job activity when a timeout is passed */
+    if (wtime >= 0)
+	return ui_inchar(buf, maxlen, wtime, tb_change_cnt);
+
+    trig_curshold = trigger_cursorhold();
+
+    if (trig_curshold)
+	ellapsed = 0;	    /* Time waiting for a char in milliseconds */
+    else
+	before_blocking();  /* Normally called when doing a blocking wait */
+
+    do
+    {
+	len = ui_inchar(buf, maxlen, POLL_INTERVAL, tb_change_cnt);
+	if (len > 0)
+	    return len; /* User-initiated input */
+
+	if (jobs_poll())
+	    return job_activity(buf);
+
+	/* We must trigger cursorhold events ourselves. Normally cursorholds
+	 * are triggered at a platform-specific lower function when an
+	 * infinite timeout is passed, but those won't get the chance
+	 * because we never pass infinite timeout in order to poll for
+	 * job activity */
+	if (trig_curshold)
+	{
+	    ellapsed += POLL_INTERVAL;
+	    if (ellapsed >= p_ut)
+		return cursorhold(buf);
+	}
+    } while (1);
+}
+
+/*
+ * Invoke the JobActivity autocommand. Called by other layers after we return
+ * K_JOBACTIVITY.
+ */
+    void
+job_activity_autocmds()
+{
+    struct job_T *job;
+    char_u **arg;
+    list_T *list;
+    int i, alive;
+
+    for (i = 0; i < MAX_RUNNING_JOBS; i++)
+    {
+	if ((job = job_table[i]) == NULL)
+	    continue;
+
+	alive = IS_ALIVE(job);
+	/* Ignore alive jobs that were stopped or that have no data available
+	 * stderr/stdout */
+	if (alive && job->stdout_buf_pos == 0 && job->stderr_buf_pos == 0)
+	    continue;
+
+	list = list_alloc();
+	list_append_number(list, job->id);
+	list_append_string(list, job->stdout_buf, job->stdout_buf_pos);
+	list_append_string(list, job->stderr_buf, job->stderr_buf_pos);
+	job->stdout_buf_pos = job->stderr_buf_pos = 0;
+	set_vim_var_list(VV_JOB_DATA, list);
+	apply_autocmds(EVENT_JOBACTIVITY, job->name, NULL, TRUE, NULL);
+
+	if (!alive)
+	{
+	    /* Process exited, free the job memory and remove it from
+	     * the table */
+	    JOB_FREE;
+	    continue;
+	}
+    }
+}
+#endif
diff --git a/src/keymap.h b/src/keymap.h
index 986006d..34b6dea 100644
--- a/src/keymap.h
+++ b/src/keymap.h
@@ -267,6 +267,7 @@ enum key_extra
     , KE_NOP		/* doesn't do something */
     , KE_FOCUSGAINED	/* focus gained */
     , KE_FOCUSLOST	/* focus lost */
+    , KE_JOB_ACTIVITY	/* One or more jobs have data available */
 };
 
 /*
@@ -467,6 +468,8 @@ enum key_extra
 
 #define K_CURSORHOLD	TERMCAP2KEY(KS_EXTRA, KE_CURSORHOLD)
 
+#define K_JOB_ACTIVITY	TERMCAP2KEY(KS_EXTRA, KE_JOB_ACTIVITY)
+
 /* Bits for modifier mask */
 /* 0x01 cannot be used, because the modifier must be 0x02 or higher */
 #define MOD_MASK_SHIFT	    0x02
diff --git a/src/macros.h b/src/macros.h
index f4a068c..9442348 100644
--- a/src/macros.h
+++ b/src/macros.h
@@ -303,3 +303,11 @@
 #  endif
 # endif
 #endif
+
+#if defined(FEAT_JOB_CONTROL)
+# define io_inchar(buf, maxlen, wtime, tb_change_cnt) \
+    job_activity_poll(buf, maxlen, wtime, tb_change_cnt)
+#else
+# define io_inchar(buf, maxlen, wtime, tb_change_cnt) \
+    ui_inchar(buf, maxlen, wtime, tb_change_cnt)
+#endif
diff --git a/src/main.c b/src/main.c
index 0407795..e9a75e5 100644
--- a/src/main.c
+++ b/src/main.c
@@ -171,7 +171,6 @@ main
 #ifdef STARTUPTIME
     int		i;
 #endif
-
     /*
      * Do any system-specific initialisations.  These can NOT use IObuff or
      * NameBuff.  Thus emsg2() cannot be called!
@@ -1367,6 +1366,10 @@ getout(exitval)
 
     exiting = TRUE;
 
+#ifdef FEAT_JOB_CONTROL
+    jobs_cleanup();
+#endif
+
     /* When running in Ex mode an error causes us to exit with a non-zero exit
      * code.  POSIX requires this, although it's not 100% clear from the
      * standard. */
diff --git a/src/misc1.c b/src/misc1.c
index b258d0b..eac3cfd 100644
--- a/src/misc1.c
+++ b/src/misc1.c
@@ -3378,7 +3378,7 @@ get_keystroke()
 
 	/* First time: blocking wait.  Second time: wait up to 100ms for a
 	 * terminal code to complete. */
-	n = ui_inchar(buf + len, maxlen, len == 0 ? -1L : 100L, 0);
+	n = io_inchar(buf + len, maxlen, len == 0 ? -1L : 100L, 0);
 	if (n > 0)
 	{
 	    /* Replace zero and CSI by a special key code. */
diff --git a/src/normal.c b/src/normal.c
index f76aeee..495256a 100644
--- a/src/normal.c
+++ b/src/normal.c
@@ -186,6 +186,9 @@ static void	nv_drop __ARGS((cmdarg_T *cap));
 #ifdef FEAT_AUTOCMD
 static void	nv_cursorhold __ARGS((cmdarg_T *cap));
 #endif
+#ifdef FEAT_JOB_CONTROL
+static void	nv_jobactivity __ARGS((cmdarg_T *cap));
+#endif
 
 static char *e_noident = N_("E349: No identifier under cursor");
 
@@ -454,6 +457,9 @@ static const struct nv_cmd
 #ifdef FEAT_AUTOCMD
     {K_CURSORHOLD, nv_cursorhold, NV_KEEPREG,		0},
 #endif
+#ifdef FEAT_JOB_CONTROL
+    {K_JOB_ACTIVITY, nv_jobactivity, NV_KEEPREG,	0},
+#endif
 };
 
 /* Number of commands in nv_cmds[]. */
@@ -9631,3 +9637,16 @@ nv_cursorhold(cap)
     cap->retval |= CA_COMMAND_BUSY;	/* don't call edit() now */
 }
 #endif
+
+#ifdef FEAT_JOB_CONTROL
+/*
+ * Trigger JobActivity event
+ */
+    static void
+nv_jobactivity(cap)
+    cmdarg_T	*cap;
+{
+    job_activity_autocmds();
+    cap->retval |= CA_COMMAND_BUSY;	/* don't call edit() now */
+}
+#endif
diff --git a/src/os_unix.c b/src/os_unix.c
index 784c5a3..0da6cbc 100644
--- a/src/os_unix.c
+++ b/src/os_unix.c
@@ -4535,7 +4535,7 @@ mch_call_shell(cmd, options)
 # if defined(HAVE_GETTIMEOFDAY) && defined(HAVE_SYS_TIME_H)
 			  gettimeofday(&start_tv, NULL);
 # endif
-			  len = ui_inchar(ta_buf, BUFLEN, 10L, 0);
+			  len = io_inchar(ta_buf, BUFLEN, 10L, 0);
 		      }
 		      if (ta_len > 0 || len > 0)
 		      {
diff --git a/src/os_win32.c b/src/os_win32.c
index 4feb697..6c23ef8 100644
--- a/src/os_win32.c
+++ b/src/os_win32.c
@@ -4380,7 +4380,7 @@ mch_system_piped(char *cmd, int options)
 # if defined(HAVE_GETTIMEOFDAY) && defined(HAVE_SYS_TIME_H)
 		    gettimeofday(&start_tv, NULL);
 # endif
-		    len = ui_inchar(ta_buf, BUFLEN, 10L, 0);
+		    len = io_inchar(ta_buf, BUFLEN, 10L, 0);
 		}
 		if (ta_len > 0 || len > 0)
 		{
diff --git a/src/proto.h b/src/proto.h
index 191ecd8..d1f3ce0 100644
--- a/src/proto.h
+++ b/src/proto.h
@@ -133,6 +133,10 @@ int vim_vsnprintf(char *str, size_t str_m, char *fmt, va_list ap, typval_T *tvs)
 #  endif
 # endif
 
+# ifdef FEAT_JOB_CONTROL
+#  include "job.pro"
+# endif
+
 # include "message.pro"
 # include "misc1.pro"
 # include "misc2.pro"
diff --git a/src/proto/eval.pro b/src/proto/eval.pro
index ee2da1b..18a27bf 100644
--- a/src/proto/eval.pro
+++ b/src/proto/eval.pro
@@ -57,6 +57,7 @@ char_u *list_find_str __ARGS((list_T *l, long idx));
 void list_append __ARGS((list_T *l, listitem_T *item));
 int list_append_tv __ARGS((list_T *l, typval_T *tv));
 int list_append_dict __ARGS((list_T *list, dict_T *dict));
+int list_append_number __ARGS((list_T *l, varnumber_T n));
 int list_append_string __ARGS((list_T *l, char_u *str, int len));
 int list_insert_tv __ARGS((list_T *l, typval_T *tv, listitem_T *item));
 void list_remove __ARGS((list_T *l, listitem_T *item, listitem_T *item2));
diff --git a/src/proto/job.pro b/src/proto/job.pro
new file mode 100644
index 0000000..efe24ea
--- /dev/null
+++ b/src/proto/job.pro
@@ -0,0 +1,8 @@
+/* job.c */
+int job_start __ARGS((char_u *name, char_u **argv));
+int job_stop __ARGS((int job_id));
+int job_write __ARGS((int job_id, char_u *data, unsigned int len));
+void jobs_cleanup __ARGS((void));
+int job_activity_poll __ARGS((char_u *buf, int maxlen, long wtime,
+	    int tb_change_cnt));
+void job_activity_autocmds __ARGS((void));
diff --git a/src/version.c b/src/version.c
index 1fd98ea..412cc1f 100644
--- a/src/version.c
+++ b/src/version.c
@@ -209,6 +209,11 @@ static char *(features[]) =
 #else
 	"-eval",
 #endif
+#ifdef FEAT_JOB_CONTROL
+	"+job_control",
+#else
+	"-job_control",
+#endif
 #ifdef FEAT_EX_EXTRA
 	"+ex_extra",
 #else
diff --git a/src/vim.h b/src/vim.h
index 88f3dc2..c9660c7 100644
--- a/src/vim.h
+++ b/src/vim.h
@@ -1280,6 +1280,7 @@ enum auto_event
     EVENT_INSERTCHANGE,		/* when changing Insert/Replace mode */
     EVENT_INSERTENTER,		/* when entering Insert mode */
     EVENT_INSERTLEAVE,		/* when leaving Insert mode */
+    EVENT_JOBACTIVITY,		/* when job sent some data */
     EVENT_MENUPOPUP,		/* just before popup menu is displayed */
     EVENT_QUICKFIXCMDPOST,	/* after :make, :grep etc. */
     EVENT_QUICKFIXCMDPRE,	/* before :make, :grep etc. */
@@ -1876,7 +1877,8 @@ typedef int proftime_T;	    /* dummy for function prototypes */
 #define VV_HLSEARCH	54
 #define VV_OLDFILES	55
 #define VV_WINDOWID	56
-#define VV_LEN		57	/* number of v: vars */
+#define VV_JOB_DATA	57
+#define VV_LEN		58	/* number of v: vars */
 
 #ifdef FEAT_CLIPBOARD
 
