Mercurial > vim
changeset 7957:b74549818500 v7.4.1274
commit https://github.com/vim/vim/commit/835dc636a5350f610b62f110227d2363b5b2880a
Author: Bram Moolenaar <Bram@vim.org>
Date: Sun Feb 7 14:27:38 2016 +0100
patch 7.4.1274
Problem: Cannot run a job.
Solution: Add job_start(), job_status() and job_stop(). Currently only works
for Unix.
author | Christian Brabandt <cb@256bit.org> |
---|---|
date | Sun, 07 Feb 2016 14:30:04 +0100 |
parents | 31dfe91e5016 |
children | 91b85ecdfd4d |
files | runtime/doc/channel.txt runtime/doc/eval.txt src/eval.c src/feature.h src/os_unix.c src/proto/os_unix.pro src/structs.h src/testdir/test_channel.vim src/version.c |
diffstat | 9 files changed, 611 insertions(+), 104 deletions(-) [+] |
line wrap: on
line diff
--- a/runtime/doc/channel.txt +++ b/runtime/doc/channel.txt @@ -1,4 +1,4 @@ -*channel.txt* For Vim version 7.4. Last change: 2016 Feb 05 +*channel.txt* For Vim version 7.4. Last change: 2016 Feb 06 VIM REFERENCE MANUAL by Bram Moolenaar @@ -93,7 +93,7 @@ The default is zero, don't wait, which i be running already. A negative number waits forever. "timeout" is the time to wait for a request when blocking, using -ch_sendexpr(). Again in millisecons. The default si 2000 (2 seconds). +ch_sendexpr(). Again in milliseconds. The default is 2000 (2 seconds). When "mode" is "json" the "msg" argument is the body of the received message, converted to Vim types. @@ -104,13 +104,13 @@ possible to receive a message after send The handler can be added or changed later: > call ch_setcallback(handle, {callback}) -When "callback is empty (zero or an empty string) the handler is removed. +When "callback" is empty (zero or an empty string) the handler is removed. NOT IMPLEMENTED YET The timeout can be changed later: > call ch_settimeout(handle, {msec}) NOT IMPLEMENTED YET - + *E906* Once done with the channel, disconnect it like this: > call ch_close(handle)
--- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -1,4 +1,4 @@ -*eval.txt* For Vim version 7.4. Last change: 2016 Feb 05 +*eval.txt* For Vim version 7.4. Last change: 2016 Feb 07 VIM REFERENCE MANUAL by Bram Moolenaar @@ -37,7 +37,7 @@ 1. Variables *variables* 1.1 Variable types ~ *E712* -There are six types of variables: +There are eight types of variables: Number A 32 or 64 bit signed number. |expr-number| *Number* Examples: -123 0x10 0177 @@ -49,9 +49,6 @@ Float A floating point number. |floatin String A NUL terminated string of 8-bit unsigned characters (bytes). |expr-string| Examples: "ab\txx\"--" 'x-z''a,c' -Funcref A reference to a function |Funcref|. - Example: function("strlen") - List An ordered sequence of items |List|. Example: [1, 2, ['a', 'b']] @@ -59,6 +56,13 @@ Dictionary An associative, unordered arr value. |Dictionary| Example: {'blue': "#0000ff", 'red': "#ff0000"} +Funcref A reference to a function |Funcref|. + Example: function("strlen") + +Special v:false, v:true, v:none and v:null + +Job Used for job control, see |job_start()|. + The Number and String types are converted automatically, depending on how they are used. @@ -95,15 +99,16 @@ Note that in the command > "foo" is converted to 0, which means FALSE. To test for a non-empty string, use empty(): > :if !empty("foo") -< *E745* *E728* *E703* *E729* *E730* *E731* -List, Dictionary and Funcref types are not automatically converted. +< + *E745* *E728* *E703* *E729* *E730* *E731* *E908* *E910* +List, Dictionary, Funcref and Job types are not automatically converted. *E805* *E806* *E808* When mixing Number and Float the Number is converted to Float. Otherwise there is no automatic conversion of Float. You can use str2float() for String to Float, printf() for Float to String and float2nr() for Float to Number. - *E891* *E892* *E893* *E894* + *E891* *E892* *E893* *E894* *E907* *E911* When expecting a Float a Number can also be used, but nothing else. *E706* *sticky-type-checking* @@ -864,7 +869,7 @@ These three can be repeated and mixed. E expr8 *expr8* ----- expr8[expr1] item of String or |List| *expr-[]* *E111* - + *E909* If expr8 is a Number or String this results in a String that contains the expr1'th single byte from expr8. expr8 is used as a String, expr1 as a Number. This doesn't recognize multi-byte encodings, see |byteidx()| for @@ -1947,6 +1952,9 @@ invert( {expr}) Number bitwise invert isdirectory( {directory}) Number TRUE if {directory} is a directory islocked( {expr}) Number TRUE if {expr} is locked items( {dict}) List key-value pairs in {dict} +job_start({command} [, {options}]) Job start a job +job_status({job}) String get the status of a job +job_stop({job} [, {how}]) Number stop a job join( {list} [, {sep}]) String join {list} items into one String jsondecode( {string}) any decode JSON jsonencode( {expr}) String encode JSON @@ -2668,6 +2676,7 @@ confirm({msg} [, {choices} [, {default} ch_close({handle}) *ch_close()* Close channel {handle}. See |channel|. + {only available when compiled with the |+channel| feature} ch_open({address} [, {argdict}]) *ch_open()* Open a channel to {address}. See |channel|. @@ -2677,7 +2686,7 @@ ch_open({address} [, {argdict}]) *ch_ {address} has the form "hostname:port", e.g., "localhost:8765". - If {argdict} is given it must be a |Directory|. The optional + If {argdict} is given it must be a |Dictionary|. The optional items are: mode "raw" or "json". Default "json". @@ -2686,10 +2695,11 @@ ch_open({address} [, {argdict}]) *ch_ Default: none. waittime Specify connect timeout as milliseconds. Negative means forever. - Default: 0. + Default: 0 (don't wait) timeout Specify response read timeout value as milliseconds. Default: 2000. + {only available when compiled with the |+channel| feature} ch_sendexpr({handle}, {expr} [, {callback}]) *ch_sendexpr()* Send {expr} over JSON channel {handle}. See |channel-use|. @@ -2704,10 +2714,14 @@ ch_sendexpr({handle}, {expr} [, {callbac function. It is called when the response is received. See |channel-callback|. + {only available when compiled with the |+channel| feature} + ch_sendraw({handle}, {string} [, {callback}]) *ch_sendraw()* Send {string} over raw channel {handle}. See |channel-raw|. Works like |ch_sendexpr()|, but does not decode the response. + {only available when compiled with the |+channel| feature} + *copy()* copy({expr}) Make a copy of {expr}. For Numbers and Strings this isn't different from using {expr} directly. @@ -2888,9 +2902,12 @@ diff_hlID({lnum}, {col}) *diff_hlID() empty({expr}) *empty()* Return the Number 1 if {expr} is empty, zero otherwise. - A |List| or |Dictionary| is empty when it does not have any - items. A Number is empty when its value is zero. - |v:false|, |v:none| and |v:null| are empty, |v:true| is not. + - A |List| or |Dictionary| is empty when it does not have any + items. + - A Number and Float is empty when its value is zero. + - |v:false|, |v:none| and |v:null| are empty, |v:true| is not. + - A Job is empty when it failed to start. + For a long |List| this is much faster than comparing the length with zero. @@ -4286,6 +4303,73 @@ items({dict}) *items()* order. +job_start({command} [, {options}]) *job_start()* + Start a job and return a Job object. Unlike |system()| and + |:!cmd| this does not wait for the job to finish. + + {command} can be a string. This works best on MS-Windows. On + Unix it is split up in white-separated parts to be passed to + execvp(). Arguments in double quotes can contain white space. + + {command} can be a list, where the first item is the executable + and further items are the arguments. All items are converted + to String. This works best on Unix. + + The command is executed directly, not through a shell, the + 'shell' option is not used. To use the shell: > + let job = job_start(["/bin/sh", "-c", "echo hello"]) +< Or: > + let job = job_start('/bin/sh -c "echo hello"') +< However, the status of the job will now be the status of the + shell, and stopping the job means stopping the shell and the + command may continue to run. + + On Unix $PATH is used to search for the executable only when + the command does not contain a slash. + + The job will use the same terminal as Vim. If it reads from + stdin the job and Vim will be fighting over input, that + doesn't work. Redirect stdin and stdout to avoid problems: > + let job = job_start(['sh', '-c', "myserver </dev/null >/dev/null"]) +< + The returned Job object can be used to get the status with + |job_status()| and stop the job with |job_stop()|. + + {options} must be a Dictionary. It can contain these optional + items: + killonexit When non-zero kill the job when Vim + exits. (default: 0, don't kill) + + {only available when compiled with the |+channel| feature} + +job_status({job}) *job_status()* + Returns a String with the status of {job}: + "run" job is running + "fail" job failed to start + "dead" job died or was stopped after running + + {only available when compiled with the |+channel| feature} + +job_stop({job} [, {how}]) *job_stop()* + Stop the {job}. This can also be used to signal the job. + + When {how} is omitted or is "term" the job will be terminated + normally. For Unix SIGTERM is sent. + Other values: + "hup" Unix: SIGHUP + "quit" Unix: SIGQUIT + "kill" Unix: SIGKILL (strongest way to stop) + number Unix: signal with that number + + The result is a Number: 1 if the operation could be executed, + 0 if "how" is not supported on the system. + Note that even when the operation was executed, whether the + job was actually stopped needs to be checked with + job_status(). + The operation will even be done when the job wasn't running. + + {only available when compiled with the |+channel| feature} + join({list} [, {sep}]) *join()* Join the items in {list} together into one String. When {sep} is specified it is put in between the items. If @@ -6692,6 +6776,7 @@ type({expr}) The result is a Number, dep Float: 5 Boolean: 6 (v:false and v:true) None 7 (v:null and v:none) + Job 8 To avoid the magic numbers it should be used this way: > :if type(myvar) == type(0) :if type(myvar) == type("")
--- a/src/eval.c +++ b/src/eval.c @@ -451,6 +451,9 @@ static dict_T *dict_copy(dict_T *orig, i static long dict_len(dict_T *d); static char_u *dict2string(typval_T *tv, int copyID); static int get_dict_tv(char_u **arg, typval_T *rettv, int evaluate); +#ifdef FEAT_JOB +static void job_free(job_T *job); +#endif static char_u *echo_string(typval_T *tv, char_u **tofree, char_u *numbuf, int copyID); static char_u *tv2string(typval_T *tv, char_u **tofree, char_u *numbuf, int copyID); static char_u *string_quote(char_u *str, int function); @@ -619,6 +622,11 @@ static void f_invert(typval_T *argvars, static void f_isdirectory(typval_T *argvars, typval_T *rettv); static void f_islocked(typval_T *argvars, typval_T *rettv); static void f_items(typval_T *argvars, typval_T *rettv); +#ifdef FEAT_JOB +static void f_job_start(typval_T *argvars, typval_T *rettv); +static void f_job_stop(typval_T *argvars, typval_T *rettv); +static void f_job_status(typval_T *argvars, typval_T *rettv); +#endif static void f_join(typval_T *argvars, typval_T *rettv); static void f_jsondecode(typval_T *argvars, typval_T *rettv); static void f_jsonencode(typval_T *argvars, typval_T *rettv); @@ -3062,10 +3070,11 @@ tv_op(typval_T *tv1, typval_T *tv2, char { switch (tv1->v_type) { + case VAR_UNKNOWN: case VAR_DICT: case VAR_FUNC: case VAR_SPECIAL: - case VAR_UNKNOWN: + case VAR_JOB: break; case VAR_LIST: @@ -3844,6 +3853,7 @@ item_lock(typval_T *tv, int deep, int lo case VAR_FUNC: case VAR_FLOAT: case VAR_SPECIAL: + case VAR_JOB: break; case VAR_LIST: @@ -5339,6 +5349,7 @@ eval_index( return FAIL; #endif case VAR_SPECIAL: + case VAR_JOB: if (verbose) EMSG(_("E909: Cannot index a special variable")); return FAIL; @@ -5446,10 +5457,11 @@ eval_index( switch (rettv->v_type) { - case VAR_SPECIAL: + case VAR_UNKNOWN: case VAR_FUNC: case VAR_FLOAT: - case VAR_UNKNOWN: + case VAR_SPECIAL: + case VAR_JOB: break; /* not evaluating, skipping over subscript */ case VAR_NUMBER: @@ -6167,9 +6179,6 @@ tv_equal( switch (tv1->v_type) { - case VAR_UNKNOWN: - break; - case VAR_LIST: ++recursive_cnt; r = list_equal(tv1->vval.v_list, tv2->vval.v_list, ic, TRUE); @@ -6190,11 +6199,6 @@ tv_equal( case VAR_NUMBER: return tv1->vval.v_number == tv2->vval.v_number; -#ifdef FEAT_FLOAT - case VAR_FLOAT: - return tv1->vval.v_float == tv2->vval.v_float; -#endif - case VAR_STRING: s1 = get_tv_string_buf(tv1, buf1); s2 = get_tv_string_buf(tv2, buf2); @@ -6202,6 +6206,17 @@ tv_equal( case VAR_SPECIAL: return tv1->vval.v_number == tv2->vval.v_number; + + case VAR_FLOAT: +#ifdef FEAT_FLOAT + return tv1->vval.v_float == tv2->vval.v_float; +#endif + case VAR_JOB: +#ifdef FEAT_JOB + return tv1->vval.v_job == tv2->vval.v_job; +#endif + case VAR_UNKNOWN: + break; } /* VAR_UNKNOWN can be the result of a invalid expression, let's say it @@ -6924,7 +6939,7 @@ garbage_collect(void) } /* - * Free lists and dictionaries that are no longer referenced. + * Free lists, dictionaries and jobs that are no longer referenced. */ static int free_unref_items(int copyID) @@ -6969,6 +6984,7 @@ free_unref_items(int copyID) } ll = ll_next; } + return did_free; } @@ -7694,6 +7710,40 @@ failret: return OK; } +#ifdef FEAT_JOB + static void +job_free(job_T *job) +{ + /* TODO: free any handles */ + + vim_free(job); +} + + static void +job_unref(job_T *job) +{ + if (job != NULL && --job->jv_refcount <= 0) + job_free(job); +} + +/* + * Allocate a job. Sets the refcount to one. + */ + static job_T * +job_alloc(void) +{ + job_T *job; + + job = (job_T *)alloc_clear(sizeof(job_T)); + if (job != NULL) + { + job->jv_refcount = 1; + } + return job; +} + +#endif + static char * get_var_special_name(int nr) { @@ -7789,12 +7839,13 @@ echo_string( case VAR_STRING: case VAR_NUMBER: case VAR_UNKNOWN: + case VAR_JOB: *tofree = NULL; r = get_tv_string_buf(tv, numbuf); break; -#ifdef FEAT_FLOAT case VAR_FLOAT: +#ifdef FEAT_FLOAT *tofree = NULL; vim_snprintf((char *)numbuf, NUMBUFLEN, "%g", tv->vval.v_float); r = numbuf; @@ -7844,6 +7895,7 @@ tv2string( case VAR_LIST: case VAR_DICT: case VAR_SPECIAL: + case VAR_JOB: case VAR_UNKNOWN: break; } @@ -8148,6 +8200,11 @@ static struct fst {"isdirectory", 1, 1, f_isdirectory}, {"islocked", 1, 1, f_islocked}, {"items", 1, 1, f_items}, +#ifdef FEAT_CHANNEL + {"job_start", 1, 2, f_job_start}, + {"job_status", 1, 1, f_job_status}, + {"job_stop", 1, 1, f_job_stop}, +#endif {"join", 1, 2, f_join}, {"jsondecode", 1, 1, f_jsondecode}, {"jsonencode", 1, 1, f_jsonencode}, @@ -10535,8 +10592,8 @@ f_empty(typval_T *argvars, typval_T *ret case VAR_NUMBER: n = argvars[0].vval.v_number == 0; break; -#ifdef FEAT_FLOAT case VAR_FLOAT: +#ifdef FEAT_FLOAT n = argvars[0].vval.v_float == 0.0; break; #endif @@ -10552,6 +10609,11 @@ f_empty(typval_T *argvars, typval_T *ret n = argvars[0].vval.v_number != VVAL_TRUE; break; + case VAR_JOB: +#ifdef FEAT_JOB + n = argvars[0].vval.v_job->jv_status != JOB_STARTED; + break; +#endif case VAR_UNKNOWN: EMSG2(_(e_intern2), "f_empty(UNKNOWN)"); n = TRUE; @@ -13060,6 +13122,9 @@ f_has(typval_T *argvars, typval_T *rettv #ifdef FEAT_INS_EXPAND "insert_expand", #endif +#ifdef FEAT_JOB + "job", +#endif #ifdef FEAT_JUMPLIST "jumplist", #endif @@ -14188,6 +14253,161 @@ f_items(typval_T *argvars, typval_T *ret dict_list(argvars, rettv, 2); } +#ifdef FEAT_JOB +/* + * "job_start()" function + */ + static void +f_job_start(typval_T *argvars UNUSED, typval_T *rettv) +{ + job_T *job; + char_u *cmd = NULL; +#if defined(UNIX) +# define USE_ARGV + char **argv = NULL; + int argc = 0; +#else + garray_T ga; +#endif + + rettv->v_type = VAR_JOB; + job = job_alloc(); + rettv->vval.v_job = job; + if (job == NULL) + return; + + rettv->vval.v_job->jv_status = JOB_FAILED; +#ifndef USE_ARGV + ga_init2(&ga, 200); +#endif + + if (argvars[0].v_type == VAR_STRING) + { + /* Command is a string. */ + cmd = argvars[0].vval.v_string; +#ifdef USE_ARGV + if (mch_parse_cmd(cmd, FALSE, &argv, &argc) == FAIL) + return; + argv[argc] = NULL; +#endif + } + else if (argvars[0].v_type != VAR_LIST + || argvars[0].vval.v_list == NULL + || argvars[0].vval.v_list->lv_len < 1) + { + EMSG(_(e_invarg)); + return; + } + else + { + list_T *l = argvars[0].vval.v_list; + listitem_T *li; + char_u *s; + +#ifdef USE_ARGV + /* Pass argv[] to mch_call_shell(). */ + argv = (char **)alloc(sizeof(char *) * (l->lv_len + 1)); + if (argv == NULL) + return; +#endif + for (li = l->lv_first; li != NULL; li = li->li_next) + { + s = get_tv_string_chk(&li->li_tv); + if (s == NULL) + goto theend; +#ifdef USE_ARGV + argv[argc++] = (char *)s; +#else + if (li != l->lv_first) + { + s = vim_strsave_shellescape(s, FALSE, TRUE); + if (s == NULL) + goto theend; + } + ga_concat(&ga, s); + vim_free(s); + if (li->li_next != NULL) + ga_append(&ga, ' '); +#endif + } +#ifdef USE_ARGV + argv[argc] = NULL; +#else + cmd = ga.ga_data; +#endif + } +#ifdef USE_ARGV + mch_start_job(argv, job); +#else + mch_start_job(cmd, job); +#endif + +theend: +#ifdef USE_ARGV + if (argv != NULL) + vim_free(argv); +#else + vim_free(ga.ga_data); +#endif +} + +/* + * "job_status()" function + */ + static void +f_job_status(typval_T *argvars UNUSED, typval_T *rettv UNUSED) +{ + char *result; + + if (argvars[0].v_type != VAR_JOB) + EMSG(_(e_invarg)); + else + { + job_T *job = argvars[0].vval.v_job; + + if (job->jv_status == JOB_ENDED) + /* No need to check, dead is dead. */ + result = "dead"; + else if (job->jv_status == JOB_FAILED) + result = "fail"; + else + result = mch_job_status(job); + rettv->v_type = VAR_STRING; + rettv->vval.v_string = vim_strsave((char_u *)result); + } +} + +/* + * "job_stop()" function + */ + static void +f_job_stop(typval_T *argvars UNUSED, typval_T *rettv UNUSED) +{ + if (argvars[0].v_type != VAR_JOB) + EMSG(_(e_invarg)); + else + { + char_u *arg; + + if (argvars[1].v_type == VAR_UNKNOWN) + arg = (char_u *)""; + else + { + arg = get_tv_string_chk(&argvars[1]); + if (arg == NULL) + { + EMSG(_(e_invarg)); + return; + } + } + if (mch_stop_job(argvars[0].vval.v_job, arg) == FAIL) + rettv->vval.v_number = 0; + else + rettv->vval.v_number = 1; + } +} +#endif + /* * "join()" function */ @@ -14295,6 +14515,7 @@ f_len(typval_T *argvars, typval_T *rettv case VAR_SPECIAL: case VAR_FLOAT: case VAR_FUNC: + case VAR_JOB: EMSG(_("E701: Invalid type for len()")); break; } @@ -19658,6 +19879,9 @@ f_type(typval_T *argvars, typval_T *rett else n = 7; break; +#ifdef FEAT_JOB + case VAR_JOB: n = 8; break; +#endif case VAR_UNKNOWN: EMSG2(_(e_intern2), "f_type(UNKNOWN)"); n = -1; @@ -21024,10 +21248,13 @@ free_tv(typval_T *varp) case VAR_DICT: dict_unref(varp->vval.v_dict); break; + case VAR_JOB: +#ifdef FEAT_JOB + job_unref(varp->vval.v_job); + break; +#endif case VAR_NUMBER: -#ifdef FEAT_FLOAT case VAR_FLOAT: -#endif case VAR_UNKNOWN: case VAR_SPECIAL: break; @@ -21065,11 +21292,17 @@ clear_tv(typval_T *varp) case VAR_SPECIAL: varp->vval.v_number = 0; break; -#ifdef FEAT_FLOAT case VAR_FLOAT: +#ifdef FEAT_FLOAT varp->vval.v_float = 0.0; break; #endif + case VAR_JOB: +#ifdef FEAT_JOB + job_unref(varp->vval.v_job); + varp->vval.v_job = NULL; +#endif + break; case VAR_UNKNOWN: break; } @@ -21112,8 +21345,8 @@ get_tv_number_chk(typval_T *varp, int *d { case VAR_NUMBER: return (long)(varp->vval.v_number); -#ifdef FEAT_FLOAT case VAR_FLOAT: +#ifdef FEAT_FLOAT EMSG(_("E805: Using a Float as a Number")); break; #endif @@ -21134,6 +21367,11 @@ get_tv_number_chk(typval_T *varp, int *d case VAR_SPECIAL: return varp->vval.v_number == VVAL_TRUE ? 1 : 0; break; + case VAR_JOB: +#ifdef FEAT_JOB + EMSG(_("E910: Using a Job as a Number")); + break; +#endif case VAR_UNKNOWN: EMSG2(_(e_intern2), "get_tv_number(UNKNOWN)"); break; @@ -21153,10 +21391,8 @@ get_tv_float(typval_T *varp) { case VAR_NUMBER: return (float_T)(varp->vval.v_number); -#ifdef FEAT_FLOAT case VAR_FLOAT: return varp->vval.v_float; -#endif case VAR_FUNC: EMSG(_("E891: Using a Funcref as a Float")); break; @@ -21172,6 +21408,11 @@ get_tv_float(typval_T *varp) case VAR_SPECIAL: EMSG(_("E907: Using a special value as a Float")); break; + case VAR_JOB: +# ifdef FEAT_JOB + EMSG(_("E911: Using a Job as a Float")); + break; +# endif case VAR_UNKNOWN: EMSG2(_(e_intern2), "get_tv_float(UNKNOWN)"); break; @@ -21272,8 +21513,8 @@ get_tv_string_buf_chk(typval_T *varp, ch case VAR_DICT: EMSG(_("E731: using Dictionary as a String")); break; -#ifdef FEAT_FLOAT case VAR_FLOAT: +#ifdef FEAT_FLOAT EMSG(_(e_float_as_string)); break; #endif @@ -21284,6 +21525,24 @@ get_tv_string_buf_chk(typval_T *varp, ch case VAR_SPECIAL: STRCPY(buf, get_var_special_name(varp->vval.v_number)); return buf; + case VAR_JOB: +#ifdef FEAT_JOB + { + job_T *job = varp->vval.v_job; + char *status = job->jv_status == JOB_FAILED ? "fail" + : job->jv_status == JOB_ENDED ? "dead" + : "run"; +# ifdef UNIX + vim_snprintf((char *)buf, NUMBUFLEN, + "process %ld %s", (long)job->jv_pid, status); +# else + /* TODO */ + vim_snprintf((char *)buf, NUMBUFLEN, "process ? %s", status); +# endif + return buf; + } +#endif + break; case VAR_UNKNOWN: EMSG(_("E908: using an invalid value as a String")); break; @@ -21903,11 +22162,17 @@ copy_tv(typval_T *from, typval_T *to) case VAR_SPECIAL: to->vval.v_number = from->vval.v_number; break; -#ifdef FEAT_FLOAT case VAR_FLOAT: +#ifdef FEAT_FLOAT to->vval.v_float = from->vval.v_float; break; #endif + case VAR_JOB: +#ifdef FEAT_FLOAT + to->vval.v_job = from->vval.v_job; + ++to->vval.v_job->jv_refcount; + break; +#endif case VAR_STRING: case VAR_FUNC: if (from->vval.v_string == NULL) @@ -21970,12 +22235,11 @@ item_copy( switch (from->v_type) { case VAR_NUMBER: -#ifdef FEAT_FLOAT case VAR_FLOAT: -#endif case VAR_STRING: case VAR_FUNC: case VAR_SPECIAL: + case VAR_JOB: copy_tv(from, to); break; case VAR_LIST: @@ -24649,6 +24913,7 @@ write_viminfo_varlist(FILE *fp) case VAR_UNKNOWN: case VAR_FUNC: + case VAR_JOB: continue; } fprintf(fp, "!%s\t%s\t", this_var->di_key, s);
--- a/src/feature.h +++ b/src/feature.h @@ -1255,13 +1255,20 @@ #endif /* - * The Channel feature requires +eval. + * The +channel feature requires +eval. */ #if !defined(FEAT_EVAL) && defined(FEAT_CHANNEL) # undef FEAT_CHANNEL #endif /* + * The +job feature requires Unix and +eval. + */ +#if defined(UNIX) && defined(FEAT_EVAL) +# define FEAT_JOB +#endif + +/* * +signs Allow signs to be displayed to the left of text lines. * Adds the ":sign" command. */
--- a/src/os_unix.c +++ b/src/os_unix.c @@ -3919,6 +3919,66 @@ wait4pid(pid_t child, waitstatus *status return wait_pid; } +#if defined(FEAT_JOB) || !defined(USE_SYSTEM) || defined(PROTO) + int +mch_parse_cmd(char_u *cmd, int use_shcf, char ***argv, int *argc) +{ + int i; + char_u *p; + int inquote; + + /* + * Do this loop twice: + * 1: find number of arguments + * 2: separate them and build argv[] + */ + for (i = 0; i < 2; ++i) + { + p = cmd; + inquote = FALSE; + *argc = 0; + for (;;) + { + if (i == 1) + (*argv)[*argc] = (char *)p; + ++*argc; + while (*p != NUL && (inquote || (*p != ' ' && *p != TAB))) + { + if (*p == '"') + inquote = !inquote; + ++p; + } + if (*p == NUL) + break; + if (i == 1) + *p++ = NUL; + p = skipwhite(p); + } + if (*argv == NULL) + { + if (use_shcf) + { + /* Account for possible multiple args in p_shcf. */ + p = p_shcf; + for (;;) + { + p = skiptowhite(p); + if (*p == NUL) + break; + ++*argc; + p = skipwhite(p); + } + } + + *argv = (char **)alloc((unsigned)((*argc + 4) * sizeof(char *))); + if (*argv == NULL) /* out of memory */ + return FAIL; + } + } + return OK; +} +#endif + int mch_call_shell( char_u *cmd, @@ -4046,7 +4106,7 @@ mch_call_shell( # define EXEC_FAILED 122 /* Exit code when shell didn't execute. Don't use 127, some shells use that already */ - char_u *newcmd = NULL; + char_u *newcmd; pid_t pid; pid_t wpid = 0; pid_t wait_pid = 0; @@ -4061,7 +4121,6 @@ mch_call_shell( char_u *p_shcf_copy = NULL; int i; char_u *p; - int inquote; int pty_master_fd = -1; /* for pty's */ # ifdef FEAT_GUI int pty_slave_fd = -1; @@ -4086,53 +4145,9 @@ mch_call_shell( if (options & SHELL_COOKED) settmode(TMODE_COOK); /* set to normal mode */ - /* - * Do this loop twice: - * 1: find number of arguments - * 2: separate them and build argv[] - */ - for (i = 0; i < 2; ++i) - { - p = newcmd; - inquote = FALSE; - argc = 0; - for (;;) - { - if (i == 1) - argv[argc] = (char *)p; - ++argc; - while (*p && (inquote || (*p != ' ' && *p != TAB))) - { - if (*p == '"') - inquote = !inquote; - ++p; - } - if (*p == NUL) - break; - if (i == 1) - *p++ = NUL; - p = skipwhite(p); - } - if (argv == NULL) - { - /* - * Account for possible multiple args in p_shcf. - */ - p = p_shcf; - for (;;) - { - p = skiptowhite(p); - if (*p == NUL) - break; - ++argc; - p = skipwhite(p); - } - - argv = (char **)alloc((unsigned)((argc + 4) * sizeof(char *))); - if (argv == NULL) /* out of memory */ - goto error; - } - } + if (mch_parse_cmd(newcmd, TRUE, &argv, &argc) == FAIL) + goto error; + if (cmd != NULL) { char_u *s; @@ -5006,6 +5021,97 @@ error: #endif /* USE_SYSTEM */ } +#if defined(FEAT_JOB) || defined(PROTO) + void +mch_start_job(char **argv, job_T *job) +{ + pid_t pid = fork(); + + if (pid == -1) /* maybe we should use vfork() */ + { + job->jv_status = JOB_FAILED; + } + else if (pid == 0) + { + /* child */ + reset_signals(); /* handle signals normally */ + +# ifdef HAVE_SETSID + /* Create our own process group, so that the child and all its + * children can be kill()ed. Don't do this when using pipes, + * because stdin is not a tty, we would lose /dev/tty. */ + (void)setsid(); +# endif + + /* See above for type of argv. */ + execvp(argv[0], argv); + + perror("executing job failed"); + _exit(EXEC_FAILED); /* exec failed, return failure code */ + } + else + { + /* parent */ + job->jv_pid = pid; + job->jv_status = JOB_STARTED; + } +} + + char * +mch_job_status(job_T *job) +{ +# ifdef HAVE_UNION_WAIT + union wait status; +# else + int status = -1; +# endif + pid_t wait_pid = 0; + +# ifdef __NeXT__ + wait_pid = wait4(job->jv_pid, &status, WNOHANG, (struct rusage *)0); +# else + wait_pid = waitpid(job->jv_pid, &status, WNOHANG); +# endif + if (wait_pid == -1) + { + /* process must have exited */ + job->jv_status = JOB_ENDED; + return "dead"; + } + if (wait_pid == 0) + return "run"; + if (WIFEXITED(status)) + { + /* LINTED avoid "bitwise operation on signed value" */ + job->jv_exitval = WEXITSTATUS(status); + job->jv_status = JOB_ENDED; + return "dead"; + } + return "run"; +} + + int +mch_stop_job(job_T *job, char_u *how) +{ + int sig = -1; + + if (STRCMP(how, "hup") == 0) + sig = SIGHUP; + else if (*how == NUL || STRCMP(how, "term") == 0) + sig = SIGTERM; + else if (STRCMP(how, "quit") == 0) + sig = SIGQUIT; + else if (STRCMP(how, "kill") == 0) + sig = SIGKILL; + else if (isdigit(*how)) + sig = atoi((char *)how); + else + return FAIL; + kill(job->jv_pid, sig); + return OK; +} +#endif + /* * Check for CTRL-C typed by reading all available characters. * In cooked mode we should get SIGINT, no need to check.
--- a/src/proto/os_unix.pro +++ b/src/proto/os_unix.pro @@ -55,7 +55,11 @@ int mch_screenmode(char_u *arg); int mch_get_shellsize(void); void mch_set_shellsize(void); void mch_new_shellsize(void); +int mch_parse_cmd(char_u *cmd, int use_shcf, char ***argv, int *argc); int mch_call_shell(char_u *cmd, int options); +void mch_start_job(char **argv, job_T *job); +char *mch_job_status(job_T *job); +int mch_stop_job(job_T *job, char_u *how); void mch_breakcheck(void); int mch_expandpath(garray_T *gap, char_u *path, int flags); int mch_expand_wildcards(int num_pat, char_u **pat, int *num_file, char_u ***file, int flags);
--- a/src/structs.h +++ b/src/structs.h @@ -1110,17 +1110,19 @@ typedef double float_T; typedef struct listvar_S list_T; typedef struct dictvar_S dict_T; +typedef struct jobvar_S job_T; typedef enum { VAR_UNKNOWN = 0, - VAR_NUMBER, /* "v_number" is used */ - VAR_STRING, /* "v_string" is used */ - VAR_FUNC, /* "v_string" is function name */ - VAR_LIST, /* "v_list" is used */ - VAR_DICT, /* "v_dict" is used */ - VAR_FLOAT, /* "v_float" is used */ - VAR_SPECIAL /* "v_number" is used */ + VAR_NUMBER, /* "v_number" is used */ + VAR_STRING, /* "v_string" is used */ + VAR_FUNC, /* "v_string" is function name */ + VAR_LIST, /* "v_list" is used */ + VAR_DICT, /* "v_dict" is used */ + VAR_FLOAT, /* "v_float" is used */ + VAR_SPECIAL, /* "v_number" is used */ + VAR_JOB /* "v_job" is used */ } vartype_T; /* @@ -1139,6 +1141,9 @@ typedef struct char_u *v_string; /* string value (can be NULL!) */ list_T *v_list; /* list value (can be NULL!) */ dict_T *v_dict; /* dict value (can be NULL!) */ +#ifdef FEAT_JOB + job_T *v_job; /* job value (can be NULL!) */ +#endif } vval; } typval_T; @@ -1204,7 +1209,6 @@ struct dictitem_S char_u di_flags; /* flags (only used for variable) */ char_u di_key[1]; /* key (actually longer!) */ }; - typedef struct dictitem_S dictitem_T; #define DI_FLAGS_RO 1 /* "di_flags" value: read-only variable */ @@ -1228,6 +1232,30 @@ struct dictvar_S dict_T *dv_used_prev; /* previous dict in used dicts list */ }; +typedef enum +{ + JOB_FAILED, + JOB_STARTED, + JOB_ENDED +} jobstatus_T; + +/* + * Structure to hold info about a Job. + */ +struct jobvar_S +{ +#ifdef UNIX + pid_t jv_pid; + int jv_exitval; +#endif +#ifdef WIN32 + PROCESS_INFORMATION jf_pi; +#endif + jobstatus_T jv_status; + + int jv_refcount; /* reference count */ +}; + /* structure used for explicit stack while garbage collecting hash tables */ typedef struct ht_stack_S {
--- a/src/testdir/test_channel.vim +++ b/src/testdir/test_channel.vim @@ -8,8 +8,9 @@ endif " This test requires the Python command to run the test server. " This most likely only works on Unix and Windows. if has('unix') - " We also need the pkill command to make sure the server can be stopped. - if !executable('python') || !executable('pkill') + " We also need the job feature or the pkill command to make sure the server + " can be stopped. + if !(executable('python') && (has('job') || executable('pkill'))) finish endif elseif has('win32') @@ -27,7 +28,9 @@ func s:start_server() " The Python program writes the port number in Xportnr. call delete("Xportnr") - if has('win32') + if has('job') + let s:job = job_start("python test_channel.py") + elseif has('win32') silent !start cmd /c start "test_channel" py test_channel.py else silent !python test_channel.py& @@ -62,7 +65,9 @@ func s:start_server() endfunc func s:kill_server() - if has('win32') + if has('job') + call job_stop(s:job) + elseif has('win32') call system('taskkill /IM py.exe /T /F /FI "WINDOWTITLE eq test_channel"') else call system("pkill -f test_channel.py")
--- a/src/version.c +++ b/src/version.c @@ -289,6 +289,11 @@ static char *(features[]) = #else "-insert_expand", #endif +#ifdef FEAT_JOB + "+job", +#else + "-job", +#endif #ifdef FEAT_JUMPLIST "+jumplist", #else @@ -743,6 +748,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1274, +/**/ 1273, /**/ 1272,