Patch 7.4.1274

7,326 views
Skip to first unread message

Bram Moolenaar

unread,
Feb 7, 2016, 8:28:08 AM2/7/16
to vim...@googlegroups.com

Patch 7.4.1274
Problem: Cannot run a job.
Solution: Add job_start(), job_status() and job_stop(). Currently only works
for Unix.
Files: eval.c, structs.h, runtime/doc/eval.txt, src/os_unix.c,
src/proto/os_unix.pro, src/feature.h, src/version.c,
src/testdir/test_channel.vim


*** ../vim-7.4.1274/src/eval.c 2016-02-07 00:00:30.576064995 +0100
--- src/eval.c 2016-02-07 13:59:00.784058601 +0100
***************
*** 451,456 ****
--- 451,459 ----
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,624 ****
--- 622,632 ----
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,3071 ****
{
switch (tv1->v_type)
{
case VAR_DICT:
case VAR_FUNC:
case VAR_SPECIAL:
! case VAR_UNKNOWN:
break;

case VAR_LIST:
--- 3070,3080 ----
{
switch (tv1->v_type)
{
+ case VAR_UNKNOWN:
case VAR_DICT:
case VAR_FUNC:
case VAR_SPECIAL:
! case VAR_JOB:
break;

case VAR_LIST:
***************
*** 3844,3849 ****
--- 3853,3859 ----
case VAR_FUNC:
case VAR_FLOAT:
case VAR_SPECIAL:
+ case VAR_JOB:
break;

case VAR_LIST:
***************
*** 5339,5344 ****
--- 5349,5355 ----
return FAIL;
#endif
case VAR_SPECIAL:
+ case VAR_JOB:
if (verbose)
EMSG(_("E909: Cannot index a special variable"));
return FAIL;
***************
*** 5446,5455 ****

switch (rettv->v_type)
{
! case VAR_SPECIAL:
case VAR_FUNC:
case VAR_FLOAT:
! case VAR_UNKNOWN:
break; /* not evaluating, skipping over subscript */

case VAR_NUMBER:
--- 5457,5467 ----

switch (rettv->v_type)
{
! case VAR_UNKNOWN:
case VAR_FUNC:
case VAR_FLOAT:
! case VAR_SPECIAL:
! case VAR_JOB:
break; /* not evaluating, skipping over subscript */

case VAR_NUMBER:
***************
*** 6167,6175 ****

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);
--- 6179,6184 ----
***************
*** 6190,6200 ****
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);
--- 6199,6204 ----
***************
*** 6202,6207 ****
--- 6206,6222 ----

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,6930 ****
}

/*
! * Free lists and dictionaries that are no longer referenced.
*/
static int
free_unref_items(int copyID)
--- 6939,6945 ----
}

/*
! * Free lists, dictionaries and jobs that are no longer referenced.
*/
static int
free_unref_items(int copyID)
***************
*** 6969,6974 ****
--- 6984,6990 ----
}
ll = ll_next;
}
+
return did_free;
}

***************
*** 7694,7699 ****
--- 7710,7749 ----
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,7800 ****
case VAR_STRING:
case VAR_NUMBER:
case VAR_UNKNOWN:
*tofree = NULL;
r = get_tv_string_buf(tv, numbuf);
break;

- #ifdef FEAT_FLOAT
case VAR_FLOAT:
*tofree = NULL;
vim_snprintf((char *)numbuf, NUMBUFLEN, "%g", tv->vval.v_float);
r = numbuf;
--- 7839,7851 ----
case VAR_STRING:
case VAR_NUMBER:
case VAR_UNKNOWN:
+ case VAR_JOB:
*tofree = NULL;
r = get_tv_string_buf(tv, numbuf);
break;

case VAR_FLOAT:
+ #ifdef FEAT_FLOAT
*tofree = NULL;
vim_snprintf((char *)numbuf, NUMBUFLEN, "%g", tv->vval.v_float);
r = numbuf;
***************
*** 7844,7849 ****
--- 7895,7901 ----
case VAR_LIST:
case VAR_DICT:
case VAR_SPECIAL:
+ case VAR_JOB:
case VAR_UNKNOWN:
break;
}
***************
*** 8148,8153 ****
--- 8200,8210 ----
{"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,10542 ****
case VAR_NUMBER:
n = argvars[0].vval.v_number == 0;
break;
- #ifdef FEAT_FLOAT
case VAR_FLOAT:
n = argvars[0].vval.v_float == 0.0;
break;
#endif
--- 10592,10599 ----
case VAR_NUMBER:
n = argvars[0].vval.v_number == 0;
break;
case VAR_FLOAT:
+ #ifdef FEAT_FLOAT
n = argvars[0].vval.v_float == 0.0;
break;
#endif
***************
*** 10552,10557 ****
--- 10609,10619 ----
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,13065 ****
--- 13122,13130 ----
#ifdef FEAT_INS_EXPAND
"insert_expand",
#endif
+ #ifdef FEAT_JOB
+ "job",
+ #endif
#ifdef FEAT_JUMPLIST
"jumplist",
#endif
***************
*** 14188,14193 ****
--- 14253,14413 ----
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,14300 ****
--- 14515,14521 ----
case VAR_SPECIAL:
case VAR_FLOAT:
case VAR_FUNC:
+ case VAR_JOB:
EMSG(_("E701: Invalid type for len()"));
break;
}
***************
*** 19658,19663 ****
--- 19879,19887 ----
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,21033 ****
case VAR_DICT:
dict_unref(varp->vval.v_dict);
break;
case VAR_NUMBER:
- #ifdef FEAT_FLOAT
case VAR_FLOAT:
- #endif
case VAR_UNKNOWN:
case VAR_SPECIAL:
break;
--- 21248,21260 ----
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:
case VAR_FLOAT:
case VAR_UNKNOWN:
case VAR_SPECIAL:
break;
***************
*** 21065,21075 ****
case VAR_SPECIAL:
varp->vval.v_number = 0;
break;
- #ifdef FEAT_FLOAT
case VAR_FLOAT:
varp->vval.v_float = 0.0;
break;
#endif
case VAR_UNKNOWN:
break;
}
--- 21292,21308 ----
case VAR_SPECIAL:
varp->vval.v_number = 0;
break;
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,21119 ****
{
case VAR_NUMBER:
return (long)(varp->vval.v_number);
- #ifdef FEAT_FLOAT
case VAR_FLOAT:
EMSG(_("E805: Using a Float as a Number"));
break;
#endif
--- 21345,21352 ----
{
case VAR_NUMBER:
return (long)(varp->vval.v_number);
case VAR_FLOAT:
+ #ifdef FEAT_FLOAT
EMSG(_("E805: Using a Float as a Number"));
break;
#endif
***************
*** 21134,21139 ****
--- 21367,21377 ----
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,21162 ****
{
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;
--- 21391,21398 ----
***************
*** 21172,21177 ****
--- 21408,21418 ----
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,21279 ****
case VAR_DICT:
EMSG(_("E731: using Dictionary as a String"));
break;
- #ifdef FEAT_FLOAT
case VAR_FLOAT:
EMSG(_(e_float_as_string));
break;
#endif
--- 21513,21520 ----
case VAR_DICT:
EMSG(_("E731: using Dictionary as a String"));
break;
case VAR_FLOAT:
+ #ifdef FEAT_FLOAT
EMSG(_(e_float_as_string));
break;
#endif
***************
*** 21284,21289 ****
--- 21525,21548 ----
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,21913 ****
case VAR_SPECIAL:
to->vval.v_number = from->vval.v_number;
break;
- #ifdef FEAT_FLOAT
case VAR_FLOAT:
to->vval.v_float = from->vval.v_float;
break;
#endif
case VAR_STRING:
case VAR_FUNC:
if (from->vval.v_string == NULL)
--- 22162,22178 ----
case VAR_SPECIAL:
to->vval.v_number = from->vval.v_number;
break;
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,21981 ****
switch (from->v_type)
{
case VAR_NUMBER:
- #ifdef FEAT_FLOAT
case VAR_FLOAT:
- #endif
case VAR_STRING:
case VAR_FUNC:
case VAR_SPECIAL:
copy_tv(from, to);
break;
case VAR_LIST:
--- 22235,22245 ----
switch (from->v_type)
{
case VAR_NUMBER:
case VAR_FLOAT:
case VAR_STRING:
case VAR_FUNC:
case VAR_SPECIAL:
+ case VAR_JOB:
copy_tv(from, to);
break;
case VAR_LIST:
***************
*** 24649,24654 ****
--- 24913,24919 ----

case VAR_UNKNOWN:
case VAR_FUNC:
+ case VAR_JOB:
continue;
}
fprintf(fp, "!%s\t%s\t", this_var->di_key, s);
*** ../vim-7.4.1274/src/structs.h 2016-02-06 18:09:53.067952958 +0100
--- src/structs.h 2016-02-07 13:56:50.617415037 +0100
***************
*** 1110,1126 ****

typedef struct listvar_S list_T;
typedef struct dictvar_S dict_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 */
} vartype_T;

/*
--- 1110,1128 ----

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_JOB /* "v_job" is used */
} vartype_T;

/*
***************
*** 1139,1144 ****
--- 1141,1149 ----
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,1210 ****
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 */
--- 1209,1214 ----
***************
*** 1228,1233 ****
--- 1232,1261 ----
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
{
*** ../vim-7.4.1274/runtime/doc/eval.txt 2016-02-07 14:23:21.744830821 +0100
--- runtime/doc/eval.txt 2016-02-07 14:10:33.428839814 +0100
***************
*** 59,64 ****
--- 56,68 ----
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.

***************
*** 1922,1927 ****
--- 1952,1960 ----
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
***************
*** 4200,4205 ****
--- 4303,4375 ----
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
*** ../vim-7.4.1274/src/os_unix.c 2016-02-07 14:23:21.744830821 +0100
--- src/os_unix.c 2016-02-07 13:46:32.215854387 +0100
***************
*** 3919,3924 ****
--- 3919,3984 ----
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,4052 ****
# define EXEC_FAILED 122 /* Exit code when shell didn't execute. Don't use
127, some shells use that already */

! char_u *newcmd = NULL;
pid_t pid;
pid_t wpid = 0;
pid_t wait_pid = 0;
--- 4106,4112 ----
# define EXEC_FAILED 122 /* Exit code when shell didn't execute. Don't use
127, some shells use that already */

! char_u *newcmd;
pid_t pid;
pid_t wpid = 0;
pid_t wait_pid = 0;
***************
*** 4061,4067 ****
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;
--- 4121,4126 ----
***************
*** 4086,4138 ****
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 (cmd != NULL)
{
char_u *s;
--- 4145,4153 ----
if (options & SHELL_COOKED)
settmode(TMODE_COOK); /* set to normal mode */

! if (mch_parse_cmd(newcmd, TRUE, &argv, &argc) == FAIL)
! goto error;

if (cmd != NULL)
{
char_u *s;
***************
*** 5006,5011 ****
--- 5021,5117 ----
#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.
*** ../vim-7.4.1274/src/proto/os_unix.pro 2016-02-07 14:23:21.744830821 +0100
--- src/proto/os_unix.pro 2016-02-07 13:42:04.306643148 +0100
***************
*** 55,61 ****
--- 55,65 ----
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);
*** ../vim-7.4.1274/src/feature.h 2016-02-07 14:23:21.744830821 +0100
--- src/feature.h 2016-02-06 22:07:35.019457345 +0100
***************
*** 1255,1267 ****
#endif

/*
! * The Channel feature requires +eval.
*/
#if !defined(FEAT_EVAL) && defined(FEAT_CHANNEL)
# undef FEAT_CHANNEL
#endif

/*
* +signs Allow signs to be displayed to the left of text lines.
* Adds the ":sign" command.
*/
--- 1255,1274 ----
#endif

/*
! * 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.
*/
*** ../vim-7.4.1274/src/version.c 2016-02-07 14:23:21.744830821 +0100
--- src/version.c 2016-02-07 14:15:33.405713009 +0100
***************
*** 289,294 ****
--- 289,299 ----
#else
"-insert_expand",
#endif
+ #ifdef FEAT_JOB
+ "+job",
+ #else
+ "-job",
+ #endif
#ifdef FEAT_JUMPLIST
"+jumplist",
#else
*** ../vim-7.4.1274/src/testdir/test_channel.vim 2016-02-07 14:23:21.744830821 +0100
--- src/testdir/test_channel.vim 2016-02-07 14:08:57.777836779 +0100
***************
*** 8,15 ****
" 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')
finish
endif
elseif has('win32')
--- 8,16 ----
" 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 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,33 ****
" The Python program writes the port number in Xportnr.
call delete("Xportnr")

! if has('win32')
silent !start cmd /c start "test_channel" py test_channel.py
else
silent !python test_channel.py&
--- 28,36 ----
" The Python program writes the port number in Xportnr.
call delete("Xportnr")

! 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,68 ****
endfunc

func s:kill_server()
! if has('win32')
call system('taskkill /IM py.exe /T /F /FI "WINDOWTITLE eq test_channel"')
else
call system("pkill -f test_channel.py")
--- 65,73 ----
endfunc

func s:kill_server()
! 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")
*** ../vim-7.4.1274/src/version.c 2016-02-07 14:23:21.744830821 +0100
--- src/version.c 2016-02-07 14:15:33.405713009 +0100
***************
*** 744,745 ****
--- 749,752 ----
{ /* Add new patch number below this line */
+ /**/
+ 1274,
/**/

--
hundred-and-one symptoms of being an internet addict:
167. You have more than 200 websites bookmarked.

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

James McCoy

unread,
Feb 7, 2016, 9:42:52 AM2/7/16
to vim...@googlegroups.com
On Sun, Feb 07, 2016 at 02:27:53PM +0100, Bram Moolenaar wrote:
>
> Patch 7.4.1274
> Problem: Cannot run a job.
> Solution: Add job_start(), job_status() and job_stop(). Currently only works
> for Unix.

Have you had a chance to look at the job API that neovim implemented?

https://neovim.io/doc/user/eval.html#jobclose%28%29

It would be nice to not have unnecessary divergence.

Cheers,
--
James
GPG Key: 4096R/331BA3DB 2011-12-05 James McCoy <jame...@jamessan.com>

tyru

unread,
Feb 7, 2016, 10:13:04 AM2/7/16
to vim...@googlegroups.com
Hi Bram!
> --
> --
> You received this message from the "vim_dev" maillist.
> Do not top-post! Type your reply below the text you are replying to.
> For more information, visit http://www.vim.org/maillist.php
>
> ---
> You received this message because you are subscribed to the Google Groups "vim_dev" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to vim_dev+u...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.

This is a really big news :)
Do you have a plan to add more channel functions, like

* Output to a spawned process's stdin
* Input from a spawned process's stdout / stderr

I think it is useful if Vim has more communication APIs.

Bram Moolenaar

unread,
Feb 7, 2016, 10:18:38 AM2/7/16
to James McCoy, vim...@googlegroups.com

James McCoy wrote:

> On Sun, Feb 07, 2016 at 02:27:53PM +0100, Bram Moolenaar wrote:
> >
> > Patch 7.4.1274
> > Problem: Cannot run a job.
> > Solution: Add job_start(), job_status() and job_stop(). Currently only works
> > for Unix.
>
> Have you had a chance to look at the job API that neovim implemented?
>
> https://neovim.io/doc/user/eval.html#jobclose%28%29
>
> It would be nice to not have unnecessary divergence.

Now that the basic channel and job support is there, I would like to
invite plugin writers to give feedback. We probably need some more
features and/or change how things work.

NeoVim is free to follow. It appears NeoVim's ideas about jobs and
channels are quite different. And it seems to be quite complicated.

--
"Hit any key to continue" is a lie.

tyru

unread,
Feb 7, 2016, 10:37:15 AM2/7/16
to vim...@googlegroups.com
Sorry, my post are folded in Google Groups because patch content is too big.
Send again.

On Sun, Feb 7, 2016 at 10:27 PM, Bram Moolenaar <Br...@moolenaar.net> wrote:
>
> Patch 7.4.1274
> Problem: Cannot run a job.
> Solution: Add job_start(), job_status() and job_stop(). Currently only works
> for Unix.

This is a really big news :)
Do you have a plan to add more channel functions, like

* Output to a spawned process's stdin
* Input from a spawned process's stdout / stderr

I think it is useful if Vim has more communication APIs.
This is my opinion as a Vim plugin writer.

Björn Linse

unread,
Feb 7, 2016, 11:47:19 AM2/7/16
to vim_dev, jame...@jamessan.com
On Sunday, February 7, 2016 at 4:18:38 PM UTC+1, Bram Moolenaar wrote:
> Now that the basic channel and job support is there, I would like to
> invite plugin writers to give feedback. We probably need some more
> features and/or change how things work.
>
> NeoVim is free to follow. It appears NeoVim's ideas about jobs and
> channels are quite different. And it seems to be quite complicated.

neovim implementation is indeed quite different as it uses libuv, and this difference will probably remain. But what specifically about the interface (that neovim exposes to plugin developer) do you think is too complicated? vim and neovim will likely diverge to the exact features supported, but it would be nice if the shared base features had the same interface.

Bram Moolenaar

unread,
Feb 7, 2016, 1:22:53 PM2/7/16
to tyru, vim...@googlegroups.com

Tyru wrote:

On Sun, Feb 7, 2016 at 10:27 PM, Bram Moolenaar <Br...@moolenaar.net> wrote:
>
> Patch 7.4.1274
> Problem: Cannot run a job.

Please don't quote the whole patch!

> This is a really big news :)
> Do you have a plan to add more channel functions, like
>
> * Output to a spawned process's stdin
> * Input from a spawned process's stdout / stderr

Using a job stdin and/or stdout to connect a channel to would be
possible. It's some work to handle the difference between a socket and
a file handle, but it's doable. In fact, dealing with stdin/stdout
already exists for system(). Main difference is how to handle
non-blocking wait and timeout.

> I think it is useful if Vim has more communication APIs.

I don't have a goal of just adding more, it needs to be useful for
plugin writers. It would be good to have examples of how a feature
would be used.

--
hundred-and-one symptoms of being an internet addict:
169. You hire a housekeeper for your home page.

Justin M. Keyes

unread,
Feb 7, 2016, 2:49:18 PM2/7/16
to vim...@googlegroups.com, James McCoy


On Feb 7, 2016 10:18 AM, "Bram Moolenaar" <Br...@moolenaar.net> wrote:
>
>
> James McCoy wrote:
>
> > On Sun, Feb 07, 2016 at 02:27:53PM +0100, Bram Moolenaar wrote:
> > >
> > > Patch 7.4.1274
> > > Problem:    Cannot run a job.
> > > Solution:   Add job_start(), job_status() and job_stop(). Currently only works
> > >             for Unix.
> >
> > Have you had a chance to look at the job API that neovim implemented?
> >
> > https://neovim.io/doc/user/eval.html#jobclose%28%29
> >
> > It would be nice to not have unnecessary divergence.
>
> Now that the basic channel and job support is there, I would like to
> invite plugin writers to give feedback.  We probably need some more
> features and/or change how things work.
>
> NeoVim is free to follow.  It appears NeoVim's ideas about jobs and
> channels are quite different.  And it seems to be quite complicated.

So just to be clear: before implementing jobs and channels, you did not seriously look at the existing work in Neovim and libuv?

Bram Moolenaar

unread,
Feb 7, 2016, 3:19:50 PM2/7/16
to Björn Linse, vim_dev, jame...@jamessan.com
I didn't spend much time on it, but I failed to see what messages are
exchanged between NeoVim and a plugin. It mentions using an API and
generating docs for that... Since the messages are binary, it's
impossible to use them without the msgpack library. Makes debugging
difficult.

--
hundred-and-one symptoms of being an internet addict:
175. You send yourself e-mail before you go to bed to remind you
what to do when you wake up.

Christian J. Robinson

unread,
Feb 7, 2016, 3:27:32 PM2/7/16
to vim...@googlegroups.com
I spent some time playing with this, and one thing I noticed is that there's no way to get the status of a channel "out of band".  For example, when I was writing my little Perl server for Vim to communicate with occasionally it would die on me, but my Vim script didn't know that until one of the ch_* commands threw an error, which I currently handle using either :silent or :try/:catch/:endtry.  A ch_status() or something like it that returns a dictionary of info about the channel would be useful. For example, it could indicate the mode, callback, waittime, last message number sent/received, etc. and if the channel is dead it could just return an empty dictionary.

The other thing I noticed is that even though it says in the documentation that you can specify a callback in ch_open, it doesn't actually call the callback--I still have to specify the callback on every call to ch_sendexpr.  I'm assuming it's just not implemented yet and I should be patient.

A feature I wouldn't mind seeing added, but would understand if it wasn't due to possible drawbacks is a flag to ch_open to make it automatically try reconnecting a specified number of times if the channel dies without ch_close being called.  Or perhaps it would be better to allow a handler to be defined for when the channel dies without ch_close being called.

- Christian

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

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



--
Christian J. Robinson <hep...@gmail.com>

Björn Linse

unread,
Feb 7, 2016, 3:35:47 PM2/7/16
to vim_dev, bjorn...@gmail.com, jame...@jamessan.com
On Sunday, February 7, 2016 at 9:19:50 PM UTC+1, Bram Moolenaar wrote:
> Björn Linse wrote:
>
> I didn't spend much time on it, but I failed to see what messages are
> exchanged between NeoVim and a plugin. It mentions using an API and
> generating docs for that... Since the messages are binary, it's
> impossible to use them without the msgpack library. Makes debugging
> difficult.

Maybe, but I was primarily referring to just the basic job functionality that this patch implements. For instance, a basic neovim example that just roundtrips some data over cat:

function! s:JobHandler(job_id, data, event)
call append(line('$'), a:data)
endfunction

let job1 = jobstart(['cat'], {'on_stdout': function('s:JobHandler')})
call jobsend(job1, ['some', 'text',''])
call jobclose(job1, 'stdin')

some more documentation here https://neovim.io/doc/user/job_control.html

Bram Moolenaar

unread,
Feb 7, 2016, 4:55:23 PM2/7/16
to Justin M. Keyes, vim...@googlegroups.com, James McCoy
I had a look and didn't like it.

--
If Microsoft would build a car...
... You'd have to press the "Start" button to turn the engine off.

Bram Moolenaar

unread,
Feb 7, 2016, 4:55:23 PM2/7/16
to Christian J. Robinson, vim...@googlegroups.com

Christian J. Robinson wrote:

> I spent some time playing with this, and one thing I noticed is that
> there's no way to get the status of a channel "out of band". For example,
> when I was writing my little Perl server for Vim to communicate with
> occasionally it would die on me, but my Vim script didn't know that until
> one of the ch_* commands threw an error, which I currently handle using
> either :silent or :try/:catch/:endtry. A ch_status() or something like it
> that returns a dictionary of info about the channel would be useful. For
> example, it could indicate the mode, callback, waittime, last message
> number sent/received, etc. and if the channel is dead it could just return
> an empty dictionary.

That's a good point. I suppose that when Vim uses select() on the
channel it will notice that it closed.

I would think that in most cases a simple string would be sufficient.
Like we have job_status().

We can have other functions to get the mode and timeout, although in
most cases you would already know.

Not sure why you would want statistics. For debugging? I plan to add a
log file for that.

Also, the channel callback can be invoked when the channel closes.
Currently it's only called when nothing else handles the received
message. Perhaps it's more useful to have a separate "close callback"
or "error callback".

> The other thing I noticed is that even though it says in the documentation
> that you can specify a callback in ch_open, it doesn't actually call the
> callback--I still have to specify the callback on every call to
> ch_sendexpr. I'm assuming it's just not implemented yet and I should be
> patient.

It is implemented, but it is only invoked when the sequence number is
zero.

> A feature I wouldn't mind seeing added, but would understand if it wasn't
> due to possible drawbacks is a flag to ch_open to make it automatically try
> reconnecting a specified number of times if the channel dies without
> ch_close being called. Or perhaps it would be better to allow a handler to
> be defined for when the channel dies without ch_close being called.

As mentioned above, a callback for when the channel closes is planned.
I'm not sure how useful it is to retry opening the channel in the
background. If the channel closes unexpectedly, I would think Vim needs
to do something (e.g., restart the server).

I was just looking at the old netbeans code to retry connecting. It
would keep trying 36 times 3 seconds, and block Vim during that time.
Not really useful. This is from before when we had a timeout on the
connection. I believe PyClewn is the main user of netbeans these days,
perhaps the developer has an opinion.

Anyway, a blocking wait can be annoying. Retrying the connection (with
zero timeout) in the main loop might be usefel.


--
hundred-and-one symptoms of being an internet addict:
176. You lie, even to user-friends, about how long you were online yesterday.

Nikolay Aleksandrovich Pavlov

unread,
Feb 7, 2016, 5:22:05 PM2/7/16
to vim_dev, Justin M. Keyes, James McCoy
2016-02-08 0:55 GMT+03:00 Bram Moolenaar <Br...@moolenaar.net>:
>
> Justin M. Keyes wrote:
>
>> On Feb 7, 2016 10:18 AM, "Bram Moolenaar" <Br...@moolenaar.net> wrote:
>> >
>> >
>> > James McCoy wrote:
>> >
>> > > On Sun, Feb 07, 2016 at 02:27:53PM +0100, Bram Moolenaar wrote:
>> > > >
>> > > > Patch 7.4.1274
>> > > > Problem: Cannot run a job.
>> > > > Solution: Add job_start(), job_status() and job_stop(). Currently
>> only works
>> > > > for Unix.
>> > >
>> > > Have you had a chance to look at the job API that neovim implemented?
>> > >
>> > > https://neovim.io/doc/user/eval.html#jobclose%28%29
>> > >
>> > > It would be nice to not have unnecessary divergence.
>> >
>> > Now that the basic channel and job support is there, I would like to
>> > invite plugin writers to give feedback. We probably need some more
>> > features and/or change how things work.
>> >
>> > NeoVim is free to follow. It appears NeoVim's ideas about jobs and
>> > channels are quite different. And it seems to be quite complicated.
>>
>> So just to be clear: before implementing jobs and channels, you did not
>> seriously look at the existing work in Neovim and libuv?
>
> I had a look and didn't like it.

Why? Also you did not like the API exposed to the plugins (i.e. job*
functions, msgpack RPC, especially the first) or the underlying
implementation?

>
> --
> If Microsoft would build a car...
> ... You'd have to press the "Start" button to turn the engine off.
>
> /// Bram Moolenaar -- Br...@Moolenaar.net -- http://www.Moolenaar.net \\\
> /// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\
> \\\ an exciting new programming language -- http://www.Zimbu.org ///
> \\\ help me help AIDS victims -- http://ICCF-Holland.org ///
>

Christian J. Robinson

unread,
Feb 7, 2016, 5:34:01 PM2/7/16
to Bram Moolenaar, vim...@googlegroups.com
On Sun, 7 Feb 2016, Bram Moolenaar wrote:

>> A ch_status() or something like it that returns a dictionary of
>> info about the channel would be useful. For example, it could
>> indicate the mode, callback, waittime, last message number
>> sent/received, etc. and if the channel is dead it could just return
>> an empty dictionary.
>
> We can have other functions to get the mode and timeout, although in
> most cases you would already know.
>
> Not sure why you would want statistics. For debugging? I plan to
> add a log file for that.

It's surprising what users might find useful, so if it's not costly in
terms of code maintenance it might make sense to provide the
functionality. The reason I suggested a single function returning a
dictionary is that it's extendable without having to add new functions
or breaking people's Vim code.

> Also, the channel callback can be invoked when the channel closes.
> Currently it's only called when nothing else handles the received
> message. Perhaps it's more useful to have a separate "close
> callback" or "error callback".

Either way is fine with me, as long as it's possible to distinguish an
intentionally closed channel from an unexpectedly closed channel.

>> The other thing I noticed is that even though it says in the
>> documentation that you can specify a callback in ch_open, it
>> doesn't actually call the callback [...] I'm assuming it's just not
>> implemented yet
>
> It is implemented, but it is only invoked when the sequence number
> is zero.

I should have realized that, since I'm using the feature while somehow
missing how it works. Perhaps a note in the documentation is in
order.

> I'm not sure how useful it is to retry opening the channel in the
> background. If the channel closes unexpectedly, I would think Vim
> needs to do something (e.g., restart the server).
>
[...]
>
> Anyway, a blocking wait can be annoying. Retrying the connection
> (with zero timeout) in the main loop might be usefel.

I think you have a point. If a retry loop makes sense in either
context the user can easily implement one in VimL.

- Christian

--
The road to success is always under construction.
Christian J. Robinson <hep...@gmail.com> http://christianrobinson.name/

Thiago Arruda

unread,
Feb 8, 2016, 8:47:00 AM2/8/16
to vim_dev, jame...@jamessan.com
> NeoVim is free to follow. It appears NeoVim's ideas about jobs and
> channels are quite different. And it seems to be quite complicated.

Bram

I'd love to see Neovim and Vim have the same API for shared features, in fact I have read the documentation about Vim's new channel feature and saw it can very easily be implemented on top Neovim existing async infrastructure(Basically we have a single async core that is used for implementing all async features, so adding new sources of asynchronous events is very easy at this point).

I will be glad to add a compatibility layer to Vim's channel feature if plugin authors start to use it, but unfortunately I can't do the same for this job control API as it will only make things harder for users.

When using a shell like bash or zsh, one can send programs to background to work on something else, and its fine that background jobs reuse the terminal stdout/stdin for communication since the consumer is sitting in the front of the terminal.

But in the context of an editor like Vim, the main use case for background jobs is to allow implementation of asynchronous plugins that parse/send data to the process(like the coprocess feature in bash/zsh), so it would make more sense to simply have `job_start` receive callbacks that are invoked when the process sends data, and add a function to send data to the job's object(IMO this is simpler than going through the additional step of setting up a channel)

Take the neomake plugin for example(https://github.com/benekastah/neomake), it currently parses compilation data from programs directly, but with this new API it would probably have to write a wrapper program in some scripting language to read the job's redirected data and communicate with Vim through sockets.

IMHO you should consider decoupling job control from the channel feature, or it will make things more complicated for plugin authors since they will need to setup an additional layer for socket communication with the job(correct me if I'm wrong, but from what I could understand, the channel feature only works through sockets and not from the job's stdio). I'm sure plugin authors and the community that already use Neovim's job control would be very happy if you followed our API, especially since we implemented it first(Am I wrong to say that Neovim was the main driving force behind you implementing job control and the channel feature?)

I know that at the moment it is impossible for Vim to use Neovim implementation since it diverged a lot by using libuv, but maybe you could base this feature on the patch I sent two years ago?( https://groups.google.com/forum/#!topic/vim_dev/QF7Bzh1YABU). The patch is old and API is different from what Neovim currently exposes, but if your are open to the idea I will be glad to assist in updating the patch to the current API and Vim's codebase.

It would be much better for the community if we helped each other. During this time developing Neovim I have faced many challenges in implementing asynchronous features, I'd be happy to share my experiences to make things easier for you. Neovim could also benefit a lot from your vast knowledge of the code. BTW have you ever considered using Neovim as a "test bench" for new Vim features? We would be happy to receive PRs from you every once in a while.

Ken Takata

unread,
Feb 8, 2016, 8:51:20 AM2/8/16
to vim_dev
Hi Bram,

2016/2/7 Sun 22:28:08 UTC+9 Bram Moolenaar wrote:
> Patch 7.4.1274
> Problem: Cannot run a job.
> Solution: Add job_start(), job_status() and job_stop(). Currently only works
> for Unix.
> Files: eval.c, structs.h, runtime/doc/eval.txt, src/os_unix.c,
> src/proto/os_unix.pro, src/feature.h, src/version.c,
> src/testdir/test_channel.vim

> ***************


> *** 1922,1927 ****
> --- 1952,1960 ----
> 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
> ***************


I think white space is needed after each '(':

--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -1959,9 +1959,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
+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

jsdecode( {string}) any decode JS style JSON
jsencode( {expr}) String encode JS style JSON

Regards,
Ken Takata

Bram Moolenaar

unread,
Feb 8, 2016, 10:25:12 AM2/8/16
to Ken Takata, vim_dev

Ken Takata wrote:

> 2016/2/7 Sun 22:28:08 UTC+9 Bram Moolenaar wrote:
> > Patch 7.4.1274
> > Problem: Cannot run a job.
> > Solution: Add job_start(), job_status() and job_stop(). Currently only works
> > for Unix.
> > Files: eval.c, structs.h, runtime/doc/eval.txt, src/os_unix.c,
> > src/proto/os_unix.pro, src/feature.h, src/version.c,
> > src/testdir/test_channel.vim
>
> > ***************
> > *** 1922,1927 ****
> > --- 1952,1960 ----
> > 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
> > ***************
>
>
> I think white space is needed after each '(':

Yes, thanks.

--
hundred-and-one symptoms of being an internet addict:
179. You wonder why your household garbage can doesn't have an
"empty recycle bin" button.

LCD 47

unread,
Feb 8, 2016, 11:11:10 AM2/8/16
to vim_dev
On 8 February 2016, Thiago Arruda <tpadi...@gmail.com> wrote:
> > NeoVim is free to follow. It appears NeoVim's ideas about jobs and
> > channels are quite different. And it seems to be quite complicated.
[...]
> But in the context of an editor like Vim, the main use case for
> background jobs is to allow implementation of asynchronous plugins
> that parse/send data to the process(like the coprocess feature in
> bash/zsh), so it would make more sense to simply have `job_start`
> receive callbacks that are invoked when the process sends data, and
> add a function to send data to the job's object(IMO this is simpler
> than going through the additional step of setting up a channel)
[...]

+1 for these particular points. IMO the main use case for jobs
won't be Vim spawning a daemon and talking to it occasionally, but
rather Vim running a lengthy process in background, reading results, and
updating them on the fly. And callbacks for the job's stdin, stdout,
and stderr is a nice way to handle that.

The alternative to callbacks would be polling the job's output, and
that would be less than great. This is how the existing async plugins
(vimproc etc.) work, and that's a major pain point because there is no
good way to do polling in Vim.

Incidentally, it's also important to be able to keep the job's
stderr and stdout separate from one another. Not being able to do that
is a deficiency of system().

Unrelated: the default for killonexit for job_start() should be
1. I think there's a tiny minority of situations when Vim 0 would be
desirable. In the vast majority of cases background processes that
would survive Vim would be runaway processes that would just consume
resources.

/lcd

tyru

unread,
Feb 8, 2016, 11:15:47 AM2/8/16
to Bram Moolenaar, vim...@googlegroups.com
On Mon, Feb 8, 2016 at 3:22 AM, Bram Moolenaar <Br...@moolenaar.net> wrote:
>
> Tyru wrote:
>
> On Sun, Feb 7, 2016 at 10:27 PM, Bram Moolenaar <Br...@moolenaar.net> wrote:
>>
>> Patch 7.4.1274
>> Problem: Cannot run a job.
>
> Please don't quote the whole patch!
>
>> This is a really big news :)
>> Do you have a plan to add more channel functions, like
>>
>> * Output to a spawned process's stdin
>> * Input from a spawned process's stdout / stderr
>
> Using a job stdin and/or stdout to connect a channel to would be
> possible. It's some work to handle the difference between a socket and
> a file handle, but it's doable. In fact, dealing with stdin/stdout
> already exists for system(). Main difference is how to handle
> non-blocking wait and timeout.
>
>> I think it is useful if Vim has more communication APIs.
>
> I don't have a goal of just adding more, it needs to be useful for
> plugin writers. It would be good to have examples of how a feature
> would be used.

Hm, you mean:

1. You are going to add a feature which connect job's stdin/stdout/stderr
2. But you don't want to add more APIs(functions)

Is this right?
If so, by when are you going to add (1)'s feature?
(Is it until the next release?)

Diego Viola

unread,
Feb 8, 2016, 8:27:31 PM2/8/16
to vim_dev
I've just compiled vim from git and tried the new job functionality.

I tried playing a song in the background first, as in:

:call job_start('mpv /path/to/song.ogg')

But it just slowed down the editor a lot, I got some output back in the editor though.

I also tried:

:call job_start('ls')

And I got output but I could not move around or anything, and it slowed down vim a lot too.

Any ideas what I'm doing wrong?

Diego Viola

unread,
Feb 8, 2016, 8:58:09 PM2/8/16
to vim_dev

I've just tried doing the same with Neovim and it worked perfectly there, no hang or anything. It just worked.

Bram Moolenaar

unread,
Feb 9, 2016, 5:03:39 AM2/9/16
to Diego Viola, vim_dev
Please read the documentation, this is not doing what you think it's
doing.

--
hundred-and-one symptoms of being an internet addict:
195. Your cat has its own home page.

Diego Viola

unread,
Feb 10, 2016, 8:28:22 PM2/10/16
to Bram Moolenaar, vim_dev
Hi Bram,

I read the documentation for job_start() and it's clear to me what it
does, so I tried this:

let job = job_start(["/bin/sh", "-c", "mpv --really-quiet /path/to/foo.ogg"])

But everything still gets too slow even though the music is playing.

Am I missing something?

Diego

Ken Takata

unread,
Feb 11, 2016, 2:34:55 AM2/11/16
to vim_dev
Hi Bram,

2016/2/7 Sun 22:28:08 UTC+9 Bram Moolenaar wrote:

> Patch 7.4.1274
> Problem: Cannot run a job.
> Solution: Add job_start(), job_status() and job_stop(). Currently only works
> for Unix.
> Files: eval.c, structs.h, runtime/doc/eval.txt, src/os_unix.c,
> src/proto/os_unix.pro, src/feature.h, src/version.c,
> src/testdir/test_channel.vim

> + /*
> + * 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 */
> + };

Why only jf_pi is prefixed "jf_"? Other members have a prefix "jv_".
Is this intended or just a mistake?

Ken Takata

Bram Moolenaar

unread,
Feb 11, 2016, 3:25:31 AM2/11/16
to Diego Viola, vim_dev

Diego Viola wrote:

> I read the documentation for job_start() and it's clear to me what it
> does, so I tried this:
>
> let job = job_start(["/bin/sh", "-c", "mpv --really-quiet /path/to/foo.ogg"])
>
> But everything still gets too slow even though the music is playing.
>
> Am I missing something?

I don't know what mpv does or how it manages to make Vim slow down.
You could try:

let job = job_start(["/bin/sh", "-c", "mpv --really-quiet /path/to/foo.ogg </dev/null >/dev/null"])

Not sure why you would want to run a job to play music. Or what foo.ogg
sounds like.

--
Your fault: core dumped

bo...@airbladesoftware.com

unread,
Feb 11, 2016, 5:16:50 AM2/11/16
to vim_dev, jame...@jamessan.com
On Sunday, 7 February 2016 15:18:38 UTC, Bram Moolenaar wrote:
>
> Now that the basic channel and job support is there, I would like to
> invite plugin writers to give feedback. We probably need some more
> features and/or change how things work.

Is it possible to read the job's stdout once it has finished?

I'm thinking of vim-gitgutter which runs git-diff, parses the diff and updates signs. How would I read the resulting diff if I ran it in a vim job?

Yours,
Andrew Stewart

Bram Moolenaar

unread,
Feb 11, 2016, 6:49:29 AM2/11/16
to Ken Takata, vim_dev
That's a typo. I added the jf_pi as a placeholder and Yasuhiro
implemented the stuff without correcting me. I'll fix it now.
Thanks for pointing out.

--
If evolution theories are correct, humans will soon grow a third
hand for operating the mouse.

Bram Moolenaar

unread,
Feb 11, 2016, 7:43:52 AM2/11/16
to bo...@airbladesoftware.com, vim_dev, jame...@jamessan.com
Yes, you will be able to start the job with only an exit handler, and
when that handler is called do a ch_readall() on stdout. Something like
that. I didn't have this in the spec, but we do actually already have
it implemented internally, a non-blocking read on the channel.

--
hundred-and-one symptoms of being an internet addict:
224. You set up your own Web page. You set up a Web page for each
of your kids... and your pets.

Diego Viola

unread,
Feb 11, 2016, 3:38:29 PM2/11/16
to Bram Moolenaar, vim_dev
Hi Bram,

mpv is a music player (mplayer fork), see https://mpv.io/

I don't need to play music in vim, I just wanted to try the job
functionality and see how it works, and this was the first idea that
came to my mind. :-)

I tried the command you suggested and vim is not slow when I use
</dev/null >/dev/null as part of the mpv command.

Diego

Diego Viola

unread,
Feb 11, 2016, 3:39:04 PM2/11/16
to Bram Moolenaar, vim_dev
Sorry, not just music player, but video/audio player.

Diego

Christian Brabandt

unread,
Feb 11, 2016, 3:45:19 PM2/11/16
to vim_dev

On Do, 11 Feb 2016, Diego Viola wrote:

> Sorry, not just music player, but video/audio player.

And it produces lot of output when running in the terminal, correct? So
redirecting stdin and stdout is the way to go.

Best,
Christian
--
Wie man sein Kind nicht nennen sollte:
Jo Seff

Diego Viola

unread,
Feb 14, 2016, 1:15:14 PM2/14/16
to Bram Moolenaar, vim_dev
So is this a bug or what?

Diego

Christian Brabandt

unread,
Feb 14, 2016, 2:28:25 PM2/14/16
to vim_dev
Hi Diego!

On So, 14 Feb 2016, Diego Viola wrote:

> So is this a bug or what?

do you need the output?

Best,
Christian
--
Die Ideen sind nicht verantwortlich für das, was die Menschen aus
ihnen machen.
-- Werner Heisenberg

Diego Viola

unread,
Apr 13, 2016, 5:10:40 PM4/13/16
to vim_dev
Sorry for the late reply, I didn't saw it before.

No, I don't need the output.

Diego
Reply all
Reply to author
Forward
0 new messages