# HG changeset patch # User Christian Brabandt # Date 1457786704 -3600 # Node ID 42277980a76d8dc477e5209a3073ec1ad50f8ca3 # Parent da01d5da2cfa367dbe17993241a537ef4b964c89 commit https://github.com/vim/vim/commit/8e2c942ce49f2555d7dc2088cf3aa856820c5e32 Author: Bram Moolenaar Date: Sat Mar 12 13:43:33 2016 +0100 patch 7.4.1539 Problem: Too much code in eval.c. Solution: Move job and channel code to channel.c. diff --git a/src/channel.c b/src/channel.c --- a/src/channel.c +++ b/src/channel.c @@ -97,11 +97,24 @@ static proftime_T log_start; #endif void -ch_logfile(FILE *file) +ch_logfile(char_u *fname, char_u *opt) { + FILE *file = NULL; + if (log_fd != NULL) fclose(log_fd); + + if (*fname != NUL) + { + file = fopen((char *)fname, *opt == 'w' ? "w" : "a"); + if (file == NULL) + { + EMSG2(_(e_notopen), fname); + return; + } + } log_fd = file; + if (log_fd != NULL) { fprintf(log_fd, "==== start log session ====\n"); @@ -360,7 +373,7 @@ channel_still_useful(channel_T *channel) * killed. * Return TRUE if the channel was freed. */ - int + static int channel_may_free(channel_T *channel) { if (!channel_still_useful(channel)) @@ -372,6 +385,19 @@ channel_may_free(channel_T *channel) } /* + * Decrement the reference count on "channel" and maybe free it when it goes + * down to zero. Don't free it if there is a pending action. + * Returns TRUE when the channel is no longer referenced. + */ + int +channel_unref(channel_T *channel) +{ + if (channel != NULL && --channel->ch_refcount <= 0) + return channel_may_free(channel); + return FALSE; +} + +/* * Close a channel and free all its resources. */ void @@ -820,6 +846,65 @@ channel_open( return channel; } +/* + * Implements ch_open(). + */ + channel_T * +channel_open_func(typval_T *argvars) +{ + char_u *address; + char_u *p; + char *rest; + int port; + jobopt_T opt; + channel_T *channel; + + address = get_tv_string(&argvars[0]); + if (argvars[1].v_type != VAR_UNKNOWN + && (argvars[1].v_type != VAR_DICT || argvars[1].vval.v_dict == NULL)) + { + EMSG(_(e_invarg)); + return NULL; + } + + /* parse address */ + p = vim_strchr(address, ':'); + if (p == NULL) + { + EMSG2(_(e_invarg2), address); + return NULL; + } + *p++ = NUL; + port = strtol((char *)p, &rest, 10); + if (*address == NUL || port <= 0 || *rest != NUL) + { + p[-1] = ':'; + EMSG2(_(e_invarg2), address); + return NULL; + } + + /* parse options */ + clear_job_options(&opt); + opt.jo_mode = MODE_JSON; + opt.jo_timeout = 2000; + if (get_job_options(&argvars[1], &opt, + JO_MODE_ALL + JO_CB_ALL + JO_WAITTIME + JO_TIMEOUT_ALL) == FAIL) + return NULL; + if (opt.jo_timeout < 0) + { + EMSG(_(e_invarg)); + return NULL; + } + + channel = channel_open((char *)address, port, opt.jo_waittime, NULL); + if (channel != NULL) + { + opt.jo_set = JO_ALL; + channel_set_options(channel, &opt); + } + return channel; +} + static void may_close_part(sock_T *fd) { @@ -2307,6 +2392,62 @@ channel_read_json_block( return FAIL; } +/* + * Common for ch_read() and ch_readraw(). + */ + void +common_channel_read(typval_T *argvars, typval_T *rettv, int raw) +{ + channel_T *channel; + int part; + jobopt_T opt; + int mode; + int timeout; + int id = -1; + typval_T *listtv = NULL; + + /* return an empty string by default */ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + clear_job_options(&opt); + if (get_job_options(&argvars[1], &opt, JO_TIMEOUT + JO_PART + JO_ID) + == FAIL) + return; + + channel = get_channel_arg(&argvars[0], TRUE); + if (channel != NULL) + { + if (opt.jo_set & JO_PART) + part = opt.jo_part; + else + part = channel_part_read(channel); + mode = channel_get_mode(channel, part); + timeout = channel_get_timeout(channel, part); + if (opt.jo_set & JO_TIMEOUT) + timeout = opt.jo_timeout; + + if (raw || mode == MODE_RAW || mode == MODE_NL) + rettv->vval.v_string = channel_read_block(channel, part, timeout); + else + { + if (opt.jo_set & JO_ID) + id = opt.jo_id; + channel_read_json_block(channel, part, timeout, id, &listtv); + if (listtv != NULL) + { + *rettv = *listtv; + vim_free(listtv); + } + else + { + rettv->v_type = VAR_SPECIAL; + rettv->vval.v_number = VVAL_NONE; + } + } + } +} + # if defined(WIN32) || defined(FEAT_GUI_X11) || defined(FEAT_GUI_GTK) \ || defined(PROTO) /* @@ -2412,6 +2553,146 @@ channel_send(channel_T *channel, int par return OK; } +/* + * Common for "ch_sendexpr()" and "ch_sendraw()". + * Returns the channel if the caller should read the response. + * Sets "part_read" to the the read fd. + * Otherwise returns NULL. + */ + channel_T * +send_common( + typval_T *argvars, + char_u *text, + int id, + int eval, + jobopt_T *opt, + char *fun, + int *part_read) +{ + channel_T *channel; + int part_send; + + channel = get_channel_arg(&argvars[0], TRUE); + if (channel == NULL) + return NULL; + part_send = channel_part_send(channel); + *part_read = channel_part_read(channel); + + clear_job_options(opt); + if (get_job_options(&argvars[2], opt, JO_CALLBACK + JO_TIMEOUT) == FAIL) + return NULL; + + /* Set the callback. An empty callback means no callback and not reading + * the response. With "ch_evalexpr()" and "ch_evalraw()" a callback is not + * allowed. */ + if (opt->jo_callback != NULL && *opt->jo_callback != NUL) + { + if (eval) + { + EMSG2(_("E917: Cannot use a callback with %s()"), fun); + return NULL; + } + channel_set_req_callback(channel, part_send, opt->jo_callback, id); + } + + if (channel_send(channel, part_send, text, fun) == OK + && opt->jo_callback == NULL) + return channel; + return NULL; +} + +/* + * common for "ch_evalexpr()" and "ch_sendexpr()" + */ + void +ch_expr_common(typval_T *argvars, typval_T *rettv, int eval) +{ + char_u *text; + typval_T *listtv; + channel_T *channel; + int id; + ch_mode_T ch_mode; + int part_send; + int part_read; + jobopt_T opt; + int timeout; + + /* return an empty string by default */ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + channel = get_channel_arg(&argvars[0], TRUE); + if (channel == NULL) + return; + part_send = channel_part_send(channel); + + ch_mode = channel_get_mode(channel, part_send); + if (ch_mode == MODE_RAW || ch_mode == MODE_NL) + { + EMSG(_("E912: cannot use ch_evalexpr()/ch_sendexpr() with a raw or nl channel")); + return; + } + + id = channel_get_id(); + text = json_encode_nr_expr(id, &argvars[1], + ch_mode == MODE_JS ? JSON_JS : 0); + if (text == NULL) + return; + + channel = send_common(argvars, text, id, eval, &opt, + eval ? "ch_evalexpr" : "ch_sendexpr", &part_read); + vim_free(text); + if (channel != NULL && eval) + { + if (opt.jo_set & JO_TIMEOUT) + timeout = opt.jo_timeout; + else + timeout = channel_get_timeout(channel, part_read); + timeout = channel_get_timeout(channel, part_read); + if (channel_read_json_block(channel, part_read, timeout, id, &listtv) + == OK) + { + list_T *list = listtv->vval.v_list; + + /* Move the item from the list and then change the type to + * avoid the value being freed. */ + *rettv = list->lv_last->li_tv; + list->lv_last->li_tv.v_type = VAR_NUMBER; + free_tv(listtv); + } + } +} + +/* + * common for "ch_evalraw()" and "ch_sendraw()" + */ + void +ch_raw_common(typval_T *argvars, typval_T *rettv, int eval) +{ + char_u buf[NUMBUFLEN]; + char_u *text; + channel_T *channel; + int part_read; + jobopt_T opt; + int timeout; + + /* return an empty string by default */ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + text = get_tv_string_buf(&argvars[1], buf); + channel = send_common(argvars, text, 0, eval, &opt, + eval ? "ch_evalraw" : "ch_sendraw", &part_read); + if (channel != NULL && eval) + { + if (opt.jo_set & JO_TIMEOUT) + timeout = opt.jo_timeout; + else + timeout = channel_get_timeout(channel, part_read); + rettv->vval.v_string = channel_read_block(channel, part_read, timeout); + } +} + # if (defined(UNIX) && !defined(HAVE_SELECT)) || defined(PROTO) /* * Add open channels to the poll struct. @@ -2695,4 +2976,782 @@ channel_get_timeout(channel_T *channel, return channel->ch_part[part].ch_timeout; } +/* + * Get a callback from "arg". It can be a Funcref or a function name. + * When "arg" is zero return an empty string. + * Return NULL for an invalid argument. + */ + static char_u * +get_callback(typval_T *arg) +{ + if (arg->v_type == VAR_FUNC || arg->v_type == VAR_STRING) + return arg->vval.v_string; + if (arg->v_type == VAR_NUMBER && arg->vval.v_number == 0) + return (char_u *)""; + EMSG(_("E999: Invalid callback argument")); + return NULL; +} + + static int +handle_mode(typval_T *item, jobopt_T *opt, ch_mode_T *modep, int jo) +{ + char_u *val = get_tv_string(item); + + opt->jo_set |= jo; + if (STRCMP(val, "nl") == 0) + *modep = MODE_NL; + else if (STRCMP(val, "raw") == 0) + *modep = MODE_RAW; + else if (STRCMP(val, "js") == 0) + *modep = MODE_JS; + else if (STRCMP(val, "json") == 0) + *modep = MODE_JSON; + else + { + EMSG2(_(e_invarg2), val); + return FAIL; + } + return OK; +} + + static int +handle_io(typval_T *item, int part, jobopt_T *opt) +{ + char_u *val = get_tv_string(item); + + opt->jo_set |= JO_OUT_IO << (part - PART_OUT); + if (STRCMP(val, "null") == 0) + opt->jo_io[part] = JIO_NULL; + else if (STRCMP(val, "pipe") == 0) + opt->jo_io[part] = JIO_PIPE; + else if (STRCMP(val, "file") == 0) + opt->jo_io[part] = JIO_FILE; + else if (STRCMP(val, "buffer") == 0) + opt->jo_io[part] = JIO_BUFFER; + else if (STRCMP(val, "out") == 0 && part == PART_ERR) + opt->jo_io[part] = JIO_OUT; + else + { + EMSG2(_(e_invarg2), val); + return FAIL; + } + return OK; +} + + void +clear_job_options(jobopt_T *opt) +{ + vim_memset(opt, 0, sizeof(jobopt_T)); +} + +/* + * Get the PART_ number from the first character of an option name. + */ + static int +part_from_char(int c) +{ + return c == 'i' ? PART_IN : c == 'o' ? PART_OUT: PART_ERR; +} + +/* + * Get the option entries from the dict in "tv", parse them and put the result + * in "opt". + * Only accept options in "supported". + * If an option value is invalid return FAIL. + */ + int +get_job_options(typval_T *tv, jobopt_T *opt, int supported) +{ + typval_T *item; + char_u *val; + dict_T *dict; + int todo; + hashitem_T *hi; + int part; + + opt->jo_set = 0; + if (tv->v_type == VAR_UNKNOWN) + return OK; + if (tv->v_type != VAR_DICT) + { + EMSG(_(e_invarg)); + return FAIL; + } + dict = tv->vval.v_dict; + if (dict == NULL) + return OK; + + todo = (int)dict->dv_hashtab.ht_used; + for (hi = dict->dv_hashtab.ht_array; todo > 0; ++hi) + if (!HASHITEM_EMPTY(hi)) + { + item = &dict_lookup(hi)->di_tv; + + if (STRCMP(hi->hi_key, "mode") == 0) + { + if (!(supported & JO_MODE)) + break; + if (handle_mode(item, opt, &opt->jo_mode, JO_MODE) == FAIL) + return FAIL; + } + else if (STRCMP(hi->hi_key, "in-mode") == 0) + { + if (!(supported & JO_IN_MODE)) + break; + if (handle_mode(item, opt, &opt->jo_in_mode, JO_IN_MODE) + == FAIL) + return FAIL; + } + else if (STRCMP(hi->hi_key, "out-mode") == 0) + { + if (!(supported & JO_OUT_MODE)) + break; + if (handle_mode(item, opt, &opt->jo_out_mode, JO_OUT_MODE) + == FAIL) + return FAIL; + } + else if (STRCMP(hi->hi_key, "err-mode") == 0) + { + if (!(supported & JO_ERR_MODE)) + break; + if (handle_mode(item, opt, &opt->jo_err_mode, JO_ERR_MODE) + == FAIL) + return FAIL; + } + else if (STRCMP(hi->hi_key, "in-io") == 0 + || STRCMP(hi->hi_key, "out-io") == 0 + || STRCMP(hi->hi_key, "err-io") == 0) + { + if (!(supported & JO_OUT_IO)) + break; + if (handle_io(item, part_from_char(*hi->hi_key), opt) == FAIL) + return FAIL; + } + else if (STRCMP(hi->hi_key, "in-name") == 0 + || STRCMP(hi->hi_key, "out-name") == 0 + || STRCMP(hi->hi_key, "err-name") == 0) + { + part = part_from_char(*hi->hi_key); + + if (!(supported & JO_OUT_IO)) + break; + opt->jo_set |= JO_OUT_NAME << (part - PART_OUT); + opt->jo_io_name[part] = + get_tv_string_buf_chk(item, opt->jo_io_name_buf[part]); + } + else if (STRCMP(hi->hi_key, "in-buf") == 0 + || STRCMP(hi->hi_key, "out-buf") == 0 + || STRCMP(hi->hi_key, "err-buf") == 0) + { + part = part_from_char(*hi->hi_key); + + if (!(supported & JO_OUT_IO)) + break; + opt->jo_set |= JO_OUT_BUF << (part - PART_OUT); + opt->jo_io_buf[part] = get_tv_number(item); + if (opt->jo_io_buf[part] <= 0) + { + EMSG2(_(e_invarg2), get_tv_string(item)); + return FAIL; + } + if (buflist_findnr(opt->jo_io_buf[part]) == NULL) + { + EMSGN(_(e_nobufnr), (long)opt->jo_io_buf[part]); + return FAIL; + } + } + else if (STRCMP(hi->hi_key, "in-top") == 0 + || STRCMP(hi->hi_key, "in-bot") == 0) + { + linenr_T *lp; + + if (!(supported & JO_OUT_IO)) + break; + if (hi->hi_key[3] == 't') + { + lp = &opt->jo_in_top; + opt->jo_set |= JO_IN_TOP; + } + else + { + lp = &opt->jo_in_bot; + opt->jo_set |= JO_IN_BOT; + } + *lp = get_tv_number(item); + if (*lp < 0) + { + EMSG2(_(e_invarg2), get_tv_string(item)); + return FAIL; + } + } + else if (STRCMP(hi->hi_key, "channel") == 0) + { + if (!(supported & JO_OUT_IO)) + break; + opt->jo_set |= JO_CHANNEL; + if (item->v_type != VAR_CHANNEL) + { + EMSG2(_(e_invarg2), "channel"); + return FAIL; + } + opt->jo_channel = item->vval.v_channel; + } + else if (STRCMP(hi->hi_key, "callback") == 0) + { + if (!(supported & JO_CALLBACK)) + break; + opt->jo_set |= JO_CALLBACK; + opt->jo_callback = get_callback(item); + if (opt->jo_callback == NULL) + { + EMSG2(_(e_invarg2), "callback"); + return FAIL; + } + } + else if (STRCMP(hi->hi_key, "out-cb") == 0) + { + if (!(supported & JO_OUT_CALLBACK)) + break; + opt->jo_set |= JO_OUT_CALLBACK; + opt->jo_out_cb = get_callback(item); + if (opt->jo_out_cb == NULL) + { + EMSG2(_(e_invarg2), "out-cb"); + return FAIL; + } + } + else if (STRCMP(hi->hi_key, "err-cb") == 0) + { + if (!(supported & JO_ERR_CALLBACK)) + break; + opt->jo_set |= JO_ERR_CALLBACK; + opt->jo_err_cb = get_callback(item); + if (opt->jo_err_cb == NULL) + { + EMSG2(_(e_invarg2), "err-cb"); + return FAIL; + } + } + else if (STRCMP(hi->hi_key, "close-cb") == 0) + { + if (!(supported & JO_CLOSE_CALLBACK)) + break; + opt->jo_set |= JO_CLOSE_CALLBACK; + opt->jo_close_cb = get_callback(item); + if (opt->jo_close_cb == NULL) + { + EMSG2(_(e_invarg2), "close-cb"); + return FAIL; + } + } + else if (STRCMP(hi->hi_key, "waittime") == 0) + { + if (!(supported & JO_WAITTIME)) + break; + opt->jo_set |= JO_WAITTIME; + opt->jo_waittime = get_tv_number(item); + } + else if (STRCMP(hi->hi_key, "timeout") == 0) + { + if (!(supported & JO_TIMEOUT)) + break; + opt->jo_set |= JO_TIMEOUT; + opt->jo_timeout = get_tv_number(item); + } + else if (STRCMP(hi->hi_key, "out-timeout") == 0) + { + if (!(supported & JO_OUT_TIMEOUT)) + break; + opt->jo_set |= JO_OUT_TIMEOUT; + opt->jo_out_timeout = get_tv_number(item); + } + else if (STRCMP(hi->hi_key, "err-timeout") == 0) + { + if (!(supported & JO_ERR_TIMEOUT)) + break; + opt->jo_set |= JO_ERR_TIMEOUT; + opt->jo_err_timeout = get_tv_number(item); + } + else if (STRCMP(hi->hi_key, "part") == 0) + { + if (!(supported & JO_PART)) + break; + opt->jo_set |= JO_PART; + val = get_tv_string(item); + if (STRCMP(val, "err") == 0) + opt->jo_part = PART_ERR; + else + { + EMSG2(_(e_invarg2), val); + return FAIL; + } + } + else if (STRCMP(hi->hi_key, "id") == 0) + { + if (!(supported & JO_ID)) + break; + opt->jo_set |= JO_ID; + opt->jo_id = get_tv_number(item); + } + else if (STRCMP(hi->hi_key, "stoponexit") == 0) + { + if (!(supported & JO_STOPONEXIT)) + break; + opt->jo_set |= JO_STOPONEXIT; + opt->jo_stoponexit = get_tv_string_buf_chk(item, + opt->jo_soe_buf); + if (opt->jo_stoponexit == NULL) + { + EMSG2(_(e_invarg2), "stoponexit"); + return FAIL; + } + } + else if (STRCMP(hi->hi_key, "exit-cb") == 0) + { + if (!(supported & JO_EXIT_CB)) + break; + opt->jo_set |= JO_EXIT_CB; + opt->jo_exit_cb = get_tv_string_buf_chk(item, opt->jo_ecb_buf); + if (opt->jo_exit_cb == NULL) + { + EMSG2(_(e_invarg2), "exit-cb"); + return FAIL; + } + } + else + break; + --todo; + } + if (todo > 0) + { + EMSG2(_(e_invarg2), hi->hi_key); + return FAIL; + } + + return OK; +} + +/* + * Get the channel from the argument. + * Returns NULL if the handle is invalid. + */ + channel_T * +get_channel_arg(typval_T *tv, int check_open) +{ + channel_T *channel = NULL; + + if (tv->v_type == VAR_JOB) + { + if (tv->vval.v_job != NULL) + channel = tv->vval.v_job->jv_channel; + } + else if (tv->v_type == VAR_CHANNEL) + { + channel = tv->vval.v_channel; + } + else + { + EMSG2(_(e_invarg2), get_tv_string(tv)); + return NULL; + } + + if (check_open && (channel == NULL || !channel_is_open(channel))) + { + EMSG(_("E906: not an open channel")); + return NULL; + } + return channel; +} + +static job_T *first_job = NULL; + + static void +job_free(job_T *job) +{ + ch_log(job->jv_channel, "Freeing job"); + if (job->jv_channel != NULL) + { + /* The link from the channel to the job doesn't count as a reference, + * thus don't decrement the refcount of the job. The reference from + * the job to the channel does count the refrence, decrement it and + * NULL the reference. We don't set ch_job_killed, unreferencing the + * job doesn't mean it stops running. */ + job->jv_channel->ch_job = NULL; + channel_unref(job->jv_channel); + } + mch_clear_job(job); + + if (job->jv_next != NULL) + job->jv_next->jv_prev = job->jv_prev; + if (job->jv_prev == NULL) + first_job = job->jv_next; + else + job->jv_prev->jv_next = job->jv_next; + + vim_free(job->jv_stoponexit); + vim_free(job->jv_exit_cb); + vim_free(job); +} + + void +job_unref(job_T *job) +{ + if (job != NULL && --job->jv_refcount <= 0) + { + /* Do not free the job when it has not ended yet and there is a + * "stoponexit" flag or an exit callback. */ + if (job->jv_status != JOB_STARTED + || (job->jv_stoponexit == NULL && job->jv_exit_cb == NULL)) + { + job_free(job); + } + else if (job->jv_channel != NULL) + { + /* Do remove the link to the channel, otherwise it hangs + * around until Vim exits. See job_free() for refcount. */ + job->jv_channel->ch_job = NULL; + channel_unref(job->jv_channel); + job->jv_channel = NULL; + } + } +} + +/* + * Allocate a job. Sets the refcount to one and sets options default. + */ + static job_T * +job_alloc(void) +{ + job_T *job; + + job = (job_T *)alloc_clear(sizeof(job_T)); + if (job != NULL) + { + job->jv_refcount = 1; + job->jv_stoponexit = vim_strsave((char_u *)"term"); + + if (first_job != NULL) + { + first_job->jv_prev = job; + job->jv_next = first_job; + } + first_job = job; + } + return job; +} + + void +job_set_options(job_T *job, jobopt_T *opt) +{ + if (opt->jo_set & JO_STOPONEXIT) + { + vim_free(job->jv_stoponexit); + if (opt->jo_stoponexit == NULL || *opt->jo_stoponexit == NUL) + job->jv_stoponexit = NULL; + else + job->jv_stoponexit = vim_strsave(opt->jo_stoponexit); + } + if (opt->jo_set & JO_EXIT_CB) + { + vim_free(job->jv_exit_cb); + if (opt->jo_exit_cb == NULL || *opt->jo_exit_cb == NUL) + job->jv_exit_cb = NULL; + else + job->jv_exit_cb = vim_strsave(opt->jo_exit_cb); + } +} + +/* + * Called when Vim is exiting: kill all jobs that have the "stoponexit" flag. + */ + void +job_stop_on_exit() +{ + job_T *job; + + for (job = first_job; job != NULL; job = job->jv_next) + if (job->jv_status == JOB_STARTED && job->jv_stoponexit != NULL) + mch_stop_job(job, job->jv_stoponexit); +} + +/* + * Called once in a while: check if any jobs with an "exit-cb" have ended. + */ + void +job_check_ended(void) +{ + static time_t last_check = 0; + time_t now; + job_T *job; + job_T *next; + + /* Only do this once in 10 seconds. */ + now = time(NULL); + if (last_check + 10 < now) + { + last_check = now; + for (job = first_job; job != NULL; job = next) + { + next = job->jv_next; + if (job->jv_status == JOB_STARTED && job->jv_exit_cb != NULL) + job_status(job); /* may free "job" */ + } + } +} + +/* + * "job_start()" function + */ + job_T * +job_start(typval_T *argvars) +{ + job_T *job; + char_u *cmd = NULL; +#if defined(UNIX) +# define USE_ARGV + char **argv = NULL; + int argc = 0; +#else + garray_T ga; +#endif + jobopt_T opt; + int part; + + job = job_alloc(); + if (job == NULL) + return NULL; + + job->jv_status = JOB_FAILED; + + /* Default mode is NL. */ + clear_job_options(&opt); + opt.jo_mode = MODE_NL; + if (get_job_options(&argvars[1], &opt, + JO_MODE_ALL + JO_CB_ALL + JO_TIMEOUT_ALL + + JO_STOPONEXIT + JO_EXIT_CB + JO_OUT_IO) == FAIL) + return job; + + /* Check that when io is "file" that there is a file name. */ + for (part = PART_OUT; part <= PART_IN; ++part) + if ((opt.jo_set & (JO_OUT_IO << (part - PART_OUT))) + && opt.jo_io[part] == JIO_FILE + && (!(opt.jo_set & (JO_OUT_NAME << (part - PART_OUT))) + || *opt.jo_io_name[part] == NUL)) + { + EMSG(_("E920: -io file requires -name to be set")); + return job; + } + + if ((opt.jo_set & JO_IN_IO) && opt.jo_io[PART_IN] == JIO_BUFFER) + { + buf_T *buf = NULL; + + /* check that we can find the buffer before starting the job */ + if (opt.jo_set & JO_IN_BUF) + { + buf = buflist_findnr(opt.jo_io_buf[PART_IN]); + if (buf == NULL) + EMSGN(_(e_nobufnr), (long)opt.jo_io_buf[PART_IN]); + } + else if (!(opt.jo_set & JO_IN_NAME)) + { + EMSG(_("E915: in-io buffer requires in-buf or in-name to be set")); + } + else + buf = buflist_find_by_name(opt.jo_io_name[PART_IN], FALSE); + if (buf == NULL) + return job; + if (buf->b_ml.ml_mfp == NULL) + { + char_u numbuf[NUMBUFLEN]; + char_u *s; + + if (opt.jo_set & JO_IN_BUF) + { + sprintf((char *)numbuf, "%d", opt.jo_io_buf[PART_IN]); + s = numbuf; + } + else + s = opt.jo_io_name[PART_IN]; + EMSG2(_("E918: buffer must be loaded: %s"), s); + return job; + } + job->jv_in_buf = buf; + } + + job_set_options(job, &opt); + +#ifndef USE_ARGV + ga_init2(&ga, (int)sizeof(char*), 20); +#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 job; + 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 job; + } + 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 job; +#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 + /* Only escape when needed, double quotes are not always allowed. */ + if (li != l->lv_first && vim_strpbrk(s, (char_u *)" \t\"") != NULL) + { + s = vim_strsave_shellescape(s, FALSE, TRUE); + if (s == NULL) + goto theend; + ga_concat(&ga, s); + vim_free(s); + } + else + ga_concat(&ga, 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 + if (ch_log_active()) + { + garray_T ga; + int i; + + ga_init2(&ga, (int)sizeof(char), 200); + for (i = 0; i < argc; ++i) + { + if (i > 0) + ga_concat(&ga, (char_u *)" "); + ga_concat(&ga, (char_u *)argv[i]); + } + ch_logs(NULL, "Starting job: %s", (char *)ga.ga_data); + ga_clear(&ga); + } + mch_start_job(argv, job, &opt); +#else + ch_logs(NULL, "Starting job: %s", (char *)cmd); + mch_start_job((char *)cmd, job, &opt); +#endif + + /* If the channel is reading from a buffer, write lines now. */ + if (job->jv_channel != NULL) + channel_write_in(job->jv_channel); + +theend: +#ifdef USE_ARGV + vim_free(argv); +#else + vim_free(ga.ga_data); +#endif + return job; +} + +/* + * Get the status of "job" and invoke the exit callback when needed. + * The returned string is not allocated. + */ + char * +job_status(job_T *job) +{ + char *result; + + 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); + if (job->jv_status == JOB_ENDED) + ch_log(job->jv_channel, "Job ended"); + if (job->jv_status == JOB_ENDED && job->jv_exit_cb != NULL) + { + typval_T argv[3]; + typval_T rettv; + int dummy; + + /* invoke the exit callback; make sure the refcount is > 0 */ + ++job->jv_refcount; + argv[0].v_type = VAR_JOB; + argv[0].vval.v_job = job; + argv[1].v_type = VAR_NUMBER; + argv[1].vval.v_number = job->jv_exitval; + call_func(job->jv_exit_cb, (int)STRLEN(job->jv_exit_cb), + &rettv, 2, argv, 0L, 0L, &dummy, TRUE, NULL); + clear_tv(&rettv); + --job->jv_refcount; + } + if (job->jv_status == JOB_ENDED && job->jv_refcount == 0) + { + /* The job was already unreferenced, now that it ended it can be + * freed. Careful: caller must not use "job" after this! */ + job_free(job); + } + } + return result; +} + + int +job_stop(job_T *job, typval_T *argvars) +{ + 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 0; + } + } + ch_logs(job->jv_channel, "Stopping job with '%s'", (char *)arg); + if (mch_stop_job(job, arg) == FAIL) + return 0; + + /* Assume that "hup" does not kill the job. */ + if (job->jv_channel != NULL && STRCMP(arg, "hup") != 0) + job->jv_channel->ch_job_killed = TRUE; + + /* We don't try freeing the job, obviously the caller still has a + * reference to it. */ + return 1; +} + #endif /* FEAT_JOB_CHANNEL */ diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -836,14 +836,11 @@ static int get_var_tv(char_u *name, int static int handle_subscript(char_u **arg, typval_T *rettv, int evaluate, int verbose); static typval_T *alloc_string_tv(char_u *string); static void init_tv(typval_T *varp); -static long get_tv_number(typval_T *varp); #ifdef FEAT_FLOAT static float_T get_tv_float(typval_T *varp); #endif static linenr_T get_tv_lnum(typval_T *argvars); static linenr_T get_tv_lnum_buf(typval_T *argvars, buf_T *buf); -static char_u *get_tv_string(typval_T *varp); -static char_u *get_tv_string_buf(typval_T *varp, char_u *buf); static dictitem_T *find_var(char_u *name, hashtab_T **htp, int no_autoload); static dictitem_T *find_var_in_ht(hashtab_T *ht, int htname, char_u *varname, int no_autoload); static hashtab_T *find_var_ht(char_u *name, char_u **varname); @@ -7735,129 +7732,6 @@ failret: } #if defined(FEAT_JOB_CHANNEL) || defined(PROTO) -/* - * Decrement the reference count on "channel" and maybe free it when it goes - * down to zero. Don't free it if there is a pending action. - * Returns TRUE when the channel is no longer referenced. - */ - int -channel_unref(channel_T *channel) -{ - if (channel != NULL && --channel->ch_refcount <= 0) - return channel_may_free(channel); - return FALSE; -} - -static job_T *first_job = NULL; - - static void -job_free(job_T *job) -{ - ch_log(job->jv_channel, "Freeing job"); - if (job->jv_channel != NULL) - { - /* The link from the channel to the job doesn't count as a reference, - * thus don't decrement the refcount of the job. The reference from - * the job to the channel does count the refrence, decrement it and - * NULL the reference. We don't set ch_job_killed, unreferencing the - * job doesn't mean it stops running. */ - job->jv_channel->ch_job = NULL; - channel_unref(job->jv_channel); - } - mch_clear_job(job); - - if (job->jv_next != NULL) - job->jv_next->jv_prev = job->jv_prev; - if (job->jv_prev == NULL) - first_job = job->jv_next; - else - job->jv_prev->jv_next = job->jv_next; - - vim_free(job->jv_stoponexit); - vim_free(job->jv_exit_cb); - vim_free(job); -} - - static void -job_unref(job_T *job) -{ - if (job != NULL && --job->jv_refcount <= 0) - { - /* Do not free the job when it has not ended yet and there is a - * "stoponexit" flag or an exit callback. */ - if (job->jv_status != JOB_STARTED - || (job->jv_stoponexit == NULL && job->jv_exit_cb == NULL)) - { - job_free(job); - } - else if (job->jv_channel != NULL) - { - /* Do remove the link to the channel, otherwise it hangs - * around until Vim exits. See job_free() for refcount. */ - job->jv_channel->ch_job = NULL; - channel_unref(job->jv_channel); - job->jv_channel = NULL; - } - } -} - -/* - * Allocate a job. Sets the refcount to one and sets options default. - */ - static job_T * -job_alloc(void) -{ - job_T *job; - - job = (job_T *)alloc_clear(sizeof(job_T)); - if (job != NULL) - { - job->jv_refcount = 1; - job->jv_stoponexit = vim_strsave((char_u *)"term"); - - if (first_job != NULL) - { - first_job->jv_prev = job; - job->jv_next = first_job; - } - first_job = job; - } - return job; -} - - static void -job_set_options(job_T *job, jobopt_T *opt) -{ - if (opt->jo_set & JO_STOPONEXIT) - { - vim_free(job->jv_stoponexit); - if (opt->jo_stoponexit == NULL || *opt->jo_stoponexit == NUL) - job->jv_stoponexit = NULL; - else - job->jv_stoponexit = vim_strsave(opt->jo_stoponexit); - } - if (opt->jo_set & JO_EXIT_CB) - { - vim_free(job->jv_exit_cb); - if (opt->jo_exit_cb == NULL || *opt->jo_exit_cb == NUL) - job->jv_exit_cb = NULL; - else - job->jv_exit_cb = vim_strsave(opt->jo_exit_cb); - } -} - -/* - * Called when Vim is exiting: kill all jobs that have the "stoponexit" flag. - */ - void -job_stop_on_exit() -{ - job_T *job; - - for (job = first_job; job != NULL; job = job->jv_next) - if (job->jv_status == JOB_STARTED && job->jv_stoponexit != NULL) - mch_stop_job(job, job->jv_stoponexit); -} #endif static char * @@ -9650,7 +9524,7 @@ f_bufloaded(typval_T *argvars, typval_T rettv->vval.v_number = (buf != NULL && buf->b_ml.ml_mfp != NULL); } - static buf_T * + buf_T * buflist_find_by_name(char_u *name, int curtab_only) { int save_magic; @@ -9941,397 +9815,8 @@ f_ceil(typval_T *argvars, typval_T *rett } #endif -#if defined(FEAT_JOB_CHANNEL) -/* - * Get a callback from "arg". It can be a Funcref or a function name. - * When "arg" is zero return an empty string. - * Return NULL for an invalid argument. - */ - static char_u * -get_callback(typval_T *arg) -{ - if (arg->v_type == VAR_FUNC || arg->v_type == VAR_STRING) - return arg->vval.v_string; - if (arg->v_type == VAR_NUMBER && arg->vval.v_number == 0) - return (char_u *)""; - EMSG(_("E999: Invalid callback argument")); - return NULL; -} - - static int -handle_mode(typval_T *item, jobopt_T *opt, ch_mode_T *modep, int jo) -{ - char_u *val = get_tv_string(item); - - opt->jo_set |= jo; - if (STRCMP(val, "nl") == 0) - *modep = MODE_NL; - else if (STRCMP(val, "raw") == 0) - *modep = MODE_RAW; - else if (STRCMP(val, "js") == 0) - *modep = MODE_JS; - else if (STRCMP(val, "json") == 0) - *modep = MODE_JSON; - else - { - EMSG2(_(e_invarg2), val); - return FAIL; - } - return OK; -} - - static int -handle_io(typval_T *item, int part, jobopt_T *opt) -{ - char_u *val = get_tv_string(item); - - opt->jo_set |= JO_OUT_IO << (part - PART_OUT); - if (STRCMP(val, "null") == 0) - opt->jo_io[part] = JIO_NULL; - else if (STRCMP(val, "pipe") == 0) - opt->jo_io[part] = JIO_PIPE; - else if (STRCMP(val, "file") == 0) - opt->jo_io[part] = JIO_FILE; - else if (STRCMP(val, "buffer") == 0) - opt->jo_io[part] = JIO_BUFFER; - else if (STRCMP(val, "out") == 0 && part == PART_ERR) - opt->jo_io[part] = JIO_OUT; - else - { - EMSG2(_(e_invarg2), val); - return FAIL; - } - return OK; -} - - static void -clear_job_options(jobopt_T *opt) -{ - vim_memset(opt, 0, sizeof(jobopt_T)); -} - -/* - * Get the PART_ number from the first character of an option name. - */ - static int -part_from_char(int c) -{ - return c == 'i' ? PART_IN : c == 'o' ? PART_OUT: PART_ERR; -} - -/* - * Get the option entries from the dict in "tv", parse them and put the result - * in "opt". - * Only accept options in "supported". - * If an option value is invalid return FAIL. - */ - static int -get_job_options(typval_T *tv, jobopt_T *opt, int supported) -{ - typval_T *item; - char_u *val; - dict_T *dict; - int todo; - hashitem_T *hi; - int part; - - opt->jo_set = 0; - if (tv->v_type == VAR_UNKNOWN) - return OK; - if (tv->v_type != VAR_DICT) - { - EMSG(_(e_invarg)); - return FAIL; - } - dict = tv->vval.v_dict; - if (dict == NULL) - return OK; - - todo = (int)dict->dv_hashtab.ht_used; - for (hi = dict->dv_hashtab.ht_array; todo > 0; ++hi) - if (!HASHITEM_EMPTY(hi)) - { - item = &HI2DI(hi)->di_tv; - - if (STRCMP(hi->hi_key, "mode") == 0) - { - if (!(supported & JO_MODE)) - break; - if (handle_mode(item, opt, &opt->jo_mode, JO_MODE) == FAIL) - return FAIL; - } - else if (STRCMP(hi->hi_key, "in-mode") == 0) - { - if (!(supported & JO_IN_MODE)) - break; - if (handle_mode(item, opt, &opt->jo_in_mode, JO_IN_MODE) - == FAIL) - return FAIL; - } - else if (STRCMP(hi->hi_key, "out-mode") == 0) - { - if (!(supported & JO_OUT_MODE)) - break; - if (handle_mode(item, opt, &opt->jo_out_mode, JO_OUT_MODE) - == FAIL) - return FAIL; - } - else if (STRCMP(hi->hi_key, "err-mode") == 0) - { - if (!(supported & JO_ERR_MODE)) - break; - if (handle_mode(item, opt, &opt->jo_err_mode, JO_ERR_MODE) - == FAIL) - return FAIL; - } - else if (STRCMP(hi->hi_key, "in-io") == 0 - || STRCMP(hi->hi_key, "out-io") == 0 - || STRCMP(hi->hi_key, "err-io") == 0) - { - if (!(supported & JO_OUT_IO)) - break; - if (handle_io(item, part_from_char(*hi->hi_key), opt) == FAIL) - return FAIL; - } - else if (STRCMP(hi->hi_key, "in-name") == 0 - || STRCMP(hi->hi_key, "out-name") == 0 - || STRCMP(hi->hi_key, "err-name") == 0) - { - part = part_from_char(*hi->hi_key); - - if (!(supported & JO_OUT_IO)) - break; - opt->jo_set |= JO_OUT_NAME << (part - PART_OUT); - opt->jo_io_name[part] = - get_tv_string_buf_chk(item, opt->jo_io_name_buf[part]); - } - else if (STRCMP(hi->hi_key, "in-buf") == 0 - || STRCMP(hi->hi_key, "out-buf") == 0 - || STRCMP(hi->hi_key, "err-buf") == 0) - { - part = part_from_char(*hi->hi_key); - - if (!(supported & JO_OUT_IO)) - break; - opt->jo_set |= JO_OUT_BUF << (part - PART_OUT); - opt->jo_io_buf[part] = get_tv_number(item); - if (opt->jo_io_buf[part] <= 0) - { - EMSG2(_(e_invarg2), get_tv_string(item)); - return FAIL; - } - if (buflist_findnr(opt->jo_io_buf[part]) == NULL) - { - EMSGN(_(e_nobufnr), (long)opt->jo_io_buf[part]); - return FAIL; - } - } - else if (STRCMP(hi->hi_key, "in-top") == 0 - || STRCMP(hi->hi_key, "in-bot") == 0) - { - linenr_T *lp; - - if (!(supported & JO_OUT_IO)) - break; - if (hi->hi_key[3] == 't') - { - lp = &opt->jo_in_top; - opt->jo_set |= JO_IN_TOP; - } - else - { - lp = &opt->jo_in_bot; - opt->jo_set |= JO_IN_BOT; - } - *lp = get_tv_number(item); - if (*lp < 0) - { - EMSG2(_(e_invarg2), get_tv_string(item)); - return FAIL; - } - } - else if (STRCMP(hi->hi_key, "channel") == 0) - { - if (!(supported & JO_OUT_IO)) - break; - opt->jo_set |= JO_CHANNEL; - if (item->v_type != VAR_CHANNEL) - { - EMSG2(_(e_invarg2), "channel"); - return FAIL; - } - opt->jo_channel = item->vval.v_channel; - } - else if (STRCMP(hi->hi_key, "callback") == 0) - { - if (!(supported & JO_CALLBACK)) - break; - opt->jo_set |= JO_CALLBACK; - opt->jo_callback = get_callback(item); - if (opt->jo_callback == NULL) - { - EMSG2(_(e_invarg2), "callback"); - return FAIL; - } - } - else if (STRCMP(hi->hi_key, "out-cb") == 0) - { - if (!(supported & JO_OUT_CALLBACK)) - break; - opt->jo_set |= JO_OUT_CALLBACK; - opt->jo_out_cb = get_callback(item); - if (opt->jo_out_cb == NULL) - { - EMSG2(_(e_invarg2), "out-cb"); - return FAIL; - } - } - else if (STRCMP(hi->hi_key, "err-cb") == 0) - { - if (!(supported & JO_ERR_CALLBACK)) - break; - opt->jo_set |= JO_ERR_CALLBACK; - opt->jo_err_cb = get_callback(item); - if (opt->jo_err_cb == NULL) - { - EMSG2(_(e_invarg2), "err-cb"); - return FAIL; - } - } - else if (STRCMP(hi->hi_key, "close-cb") == 0) - { - if (!(supported & JO_CLOSE_CALLBACK)) - break; - opt->jo_set |= JO_CLOSE_CALLBACK; - opt->jo_close_cb = get_callback(item); - if (opt->jo_close_cb == NULL) - { - EMSG2(_(e_invarg2), "close-cb"); - return FAIL; - } - } - else if (STRCMP(hi->hi_key, "waittime") == 0) - { - if (!(supported & JO_WAITTIME)) - break; - opt->jo_set |= JO_WAITTIME; - opt->jo_waittime = get_tv_number(item); - } - else if (STRCMP(hi->hi_key, "timeout") == 0) - { - if (!(supported & JO_TIMEOUT)) - break; - opt->jo_set |= JO_TIMEOUT; - opt->jo_timeout = get_tv_number(item); - } - else if (STRCMP(hi->hi_key, "out-timeout") == 0) - { - if (!(supported & JO_OUT_TIMEOUT)) - break; - opt->jo_set |= JO_OUT_TIMEOUT; - opt->jo_out_timeout = get_tv_number(item); - } - else if (STRCMP(hi->hi_key, "err-timeout") == 0) - { - if (!(supported & JO_ERR_TIMEOUT)) - break; - opt->jo_set |= JO_ERR_TIMEOUT; - opt->jo_err_timeout = get_tv_number(item); - } - else if (STRCMP(hi->hi_key, "part") == 0) - { - if (!(supported & JO_PART)) - break; - opt->jo_set |= JO_PART; - val = get_tv_string(item); - if (STRCMP(val, "err") == 0) - opt->jo_part = PART_ERR; - else - { - EMSG2(_(e_invarg2), val); - return FAIL; - } - } - else if (STRCMP(hi->hi_key, "id") == 0) - { - if (!(supported & JO_ID)) - break; - opt->jo_set |= JO_ID; - opt->jo_id = get_tv_number(item); - } - else if (STRCMP(hi->hi_key, "stoponexit") == 0) - { - if (!(supported & JO_STOPONEXIT)) - break; - opt->jo_set |= JO_STOPONEXIT; - opt->jo_stoponexit = get_tv_string_buf_chk(item, - opt->jo_soe_buf); - if (opt->jo_stoponexit == NULL) - { - EMSG2(_(e_invarg2), "stoponexit"); - return FAIL; - } - } - else if (STRCMP(hi->hi_key, "exit-cb") == 0) - { - if (!(supported & JO_EXIT_CB)) - break; - opt->jo_set |= JO_EXIT_CB; - opt->jo_exit_cb = get_tv_string_buf_chk(item, opt->jo_ecb_buf); - if (opt->jo_exit_cb == NULL) - { - EMSG2(_(e_invarg2), "exit-cb"); - return FAIL; - } - } - else - break; - --todo; - } - if (todo > 0) - { - EMSG2(_(e_invarg2), hi->hi_key); - return FAIL; - } - - return OK; -} -#endif - #ifdef FEAT_JOB_CHANNEL /* - * Get the channel from the argument. - * Returns NULL if the handle is invalid. - */ - static channel_T * -get_channel_arg(typval_T *tv, int check_open) -{ - channel_T *channel = NULL; - - if (tv->v_type == VAR_JOB) - { - if (tv->vval.v_job != NULL) - channel = tv->vval.v_job->jv_channel; - } - else if (tv->v_type == VAR_CHANNEL) - { - channel = tv->vval.v_channel; - } - else - { - EMSG2(_(e_invarg2), get_tv_string(tv)); - return NULL; - } - - if (check_open && (channel == NULL || !channel_is_open(channel))) - { - EMSG(_("E906: not an open channel")); - return NULL; - } - return channel; -} - -/* * "ch_close()" function */ static void @@ -10414,21 +9899,11 @@ f_ch_logfile(typval_T *argvars, typval_T char_u *fname; char_u *opt = (char_u *)""; char_u buf[NUMBUFLEN]; - FILE *file = NULL; fname = get_tv_string(&argvars[0]); if (argvars[1].v_type == VAR_STRING) opt = get_tv_string_buf(&argvars[1], buf); - if (*fname != NUL) - { - file = fopen((char *)fname, *opt == 'w' ? "w" : "a"); - if (file == NULL) - { - EMSG2(_(e_notopen), fname); - return; - } - } - ch_logfile(file); + ch_logfile(fname, opt); } /* @@ -10437,117 +9912,8 @@ f_ch_logfile(typval_T *argvars, typval_T static void f_ch_open(typval_T *argvars, typval_T *rettv) { - char_u *address; - char_u *p; - char *rest; - int port; - jobopt_T opt; - channel_T *channel; - - /* default: fail */ rettv->v_type = VAR_CHANNEL; - rettv->vval.v_channel = NULL; - - address = get_tv_string(&argvars[0]); - if (argvars[1].v_type != VAR_UNKNOWN - && (argvars[1].v_type != VAR_DICT || argvars[1].vval.v_dict == NULL)) - { - EMSG(_(e_invarg)); - return; - } - - /* parse address */ - p = vim_strchr(address, ':'); - if (p == NULL) - { - EMSG2(_(e_invarg2), address); - return; - } - *p++ = NUL; - port = strtol((char *)p, &rest, 10); - if (*address == NUL || port <= 0 || *rest != NUL) - { - p[-1] = ':'; - EMSG2(_(e_invarg2), address); - return; - } - - /* parse options */ - clear_job_options(&opt); - opt.jo_mode = MODE_JSON; - opt.jo_timeout = 2000; - if (get_job_options(&argvars[1], &opt, - JO_MODE_ALL + JO_CB_ALL + JO_WAITTIME + JO_TIMEOUT_ALL) == FAIL) - return; - if (opt.jo_timeout < 0) - { - EMSG(_(e_invarg)); - return; - } - - channel = channel_open((char *)address, port, opt.jo_waittime, NULL); - if (channel != NULL) - { - rettv->vval.v_channel = channel; - opt.jo_set = JO_ALL; - channel_set_options(channel, &opt); - } -} - -/* - * Common for ch_read() and ch_readraw(). - */ - static void -common_channel_read(typval_T *argvars, typval_T *rettv, int raw) -{ - channel_T *channel; - int part; - jobopt_T opt; - int mode; - int timeout; - int id = -1; - typval_T *listtv = NULL; - - /* return an empty string by default */ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - clear_job_options(&opt); - if (get_job_options(&argvars[1], &opt, JO_TIMEOUT + JO_PART + JO_ID) - == FAIL) - return; - - channel = get_channel_arg(&argvars[0], TRUE); - if (channel != NULL) - { - if (opt.jo_set & JO_PART) - part = opt.jo_part; - else - part = channel_part_read(channel); - mode = channel_get_mode(channel, part); - timeout = channel_get_timeout(channel, part); - if (opt.jo_set & JO_TIMEOUT) - timeout = opt.jo_timeout; - - if (raw || mode == MODE_RAW || mode == MODE_NL) - rettv->vval.v_string = channel_read_block(channel, part, timeout); - else - { - if (opt.jo_set & JO_ID) - id = opt.jo_id; - channel_read_json_block(channel, part, timeout, id, &listtv); - if (listtv != NULL) - { - *rettv = *listtv; - vim_free(listtv); - } - else - { - rettv->v_type = VAR_SPECIAL; - rettv->vval.v_number = VVAL_NONE; - } - } - } + rettv->vval.v_channel = channel_open_func(argvars); } /* @@ -10569,116 +9935,6 @@ f_ch_readraw(typval_T *argvars, typval_T } /* - * common for "sendexpr()" and "sendraw()" - * Returns the channel if the caller should read the response. - * Sets "part_read" to the the read fd. - * Otherwise returns NULL. - */ - static channel_T * -send_common( - typval_T *argvars, - char_u *text, - int id, - int eval, - jobopt_T *opt, - char *fun, - int *part_read) -{ - channel_T *channel; - int part_send; - - channel = get_channel_arg(&argvars[0], TRUE); - if (channel == NULL) - return NULL; - part_send = channel_part_send(channel); - *part_read = channel_part_read(channel); - - clear_job_options(opt); - if (get_job_options(&argvars[2], opt, JO_CALLBACK + JO_TIMEOUT) == FAIL) - return NULL; - - /* Set the callback. An empty callback means no callback and not reading - * the response. With "ch_evalexpr()" and "ch_evalraw()" a callback is not - * allowed. */ - if (opt->jo_callback != NULL && *opt->jo_callback != NUL) - { - if (eval) - { - EMSG2(_("E917: Cannot use a callback with %s()"), fun); - return NULL; - } - channel_set_req_callback(channel, part_send, opt->jo_callback, id); - } - - if (channel_send(channel, part_send, text, fun) == OK - && opt->jo_callback == NULL) - return channel; - return NULL; -} - -/* - * common for "ch_evalexpr()" and "ch_sendexpr()" - */ - static void -ch_expr_common(typval_T *argvars, typval_T *rettv, int eval) -{ - char_u *text; - typval_T *listtv; - channel_T *channel; - int id; - ch_mode_T ch_mode; - int part_send; - int part_read; - jobopt_T opt; - int timeout; - - /* return an empty string by default */ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - channel = get_channel_arg(&argvars[0], TRUE); - if (channel == NULL) - return; - part_send = channel_part_send(channel); - - ch_mode = channel_get_mode(channel, part_send); - if (ch_mode == MODE_RAW || ch_mode == MODE_NL) - { - EMSG(_("E912: cannot use ch_evalexpr()/ch_sendexpr() with a raw or nl channel")); - return; - } - - id = channel_get_id(); - text = json_encode_nr_expr(id, &argvars[1], - ch_mode == MODE_JS ? JSON_JS : 0); - if (text == NULL) - return; - - channel = send_common(argvars, text, id, eval, &opt, - eval ? "ch_evalexpr" : "ch_sendexpr", &part_read); - vim_free(text); - if (channel != NULL && eval) - { - if (opt.jo_set & JO_TIMEOUT) - timeout = opt.jo_timeout; - else - timeout = channel_get_timeout(channel, part_read); - timeout = channel_get_timeout(channel, part_read); - if (channel_read_json_block(channel, part_read, timeout, id, &listtv) - == OK) - { - list_T *list = listtv->vval.v_list; - - /* Move the item from the list and then change the type to - * avoid the value being freed. */ - *rettv = list->lv_last->li_tv; - list->lv_last->li_tv.v_type = VAR_NUMBER; - free_tv(listtv); - } - } -} - -/* * "ch_evalexpr()" function */ static void @@ -10697,36 +9953,6 @@ f_ch_sendexpr(typval_T *argvars, typval_ } /* - * common for "ch_evalraw()" and "ch_sendraw()" - */ - static void -ch_raw_common(typval_T *argvars, typval_T *rettv, int eval) -{ - char_u buf[NUMBUFLEN]; - char_u *text; - channel_T *channel; - int part_read; - jobopt_T opt; - int timeout; - - /* return an empty string by default */ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - text = get_tv_string_buf(&argvars[1], buf); - channel = send_common(argvars, text, 0, eval, &opt, - eval ? "ch_evalraw" : "ch_sendraw", &part_read); - if (channel != NULL && eval) - { - if (opt.jo_set & JO_TIMEOUT) - timeout = opt.jo_timeout; - else - timeout = channel_get_timeout(channel, part_read); - rettv->vval.v_string = channel_read_block(channel, part_read, timeout); - } -} - -/* * "ch_evalraw()" function */ static void @@ -15138,250 +14364,8 @@ f_job_setoptions(typval_T *argvars, typv static void f_job_start(typval_T *argvars, 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 - jobopt_T opt; - int part; - 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; - - /* Default mode is NL. */ - clear_job_options(&opt); - opt.jo_mode = MODE_NL; - if (get_job_options(&argvars[1], &opt, - JO_MODE_ALL + JO_CB_ALL + JO_TIMEOUT_ALL - + JO_STOPONEXIT + JO_EXIT_CB + JO_OUT_IO) == FAIL) - return; - - /* Check that when io is "file" that there is a file name. */ - for (part = PART_OUT; part <= PART_IN; ++part) - if ((opt.jo_set & (JO_OUT_IO << (part - PART_OUT))) - && opt.jo_io[part] == JIO_FILE - && (!(opt.jo_set & (JO_OUT_NAME << (part - PART_OUT))) - || *opt.jo_io_name[part] == NUL)) - { - EMSG(_("E920: -io file requires -name to be set")); - return; - } - - if ((opt.jo_set & JO_IN_IO) && opt.jo_io[PART_IN] == JIO_BUFFER) - { - buf_T *buf = NULL; - - /* check that we can find the buffer before starting the job */ - if (opt.jo_set & JO_IN_BUF) - { - buf = buflist_findnr(opt.jo_io_buf[PART_IN]); - if (buf == NULL) - EMSGN(_(e_nobufnr), (long)opt.jo_io_buf[PART_IN]); - } - else if (!(opt.jo_set & JO_IN_NAME)) - { - EMSG(_("E915: in-io buffer requires in-buf or in-name to be set")); - } - else - buf = buflist_find_by_name(opt.jo_io_name[PART_IN], FALSE); - if (buf == NULL) - return; - if (buf->b_ml.ml_mfp == NULL) - { - char_u numbuf[NUMBUFLEN]; - char_u *s; - - if (opt.jo_set & JO_IN_BUF) - { - sprintf((char *)numbuf, "%d", opt.jo_io_buf[PART_IN]); - s = numbuf; - } - else - s = opt.jo_io_name[PART_IN]; - EMSG2(_("E918: buffer must be loaded: %s"), s); - return; - } - job->jv_in_buf = buf; - } - - job_set_options(job, &opt); - -#ifndef USE_ARGV - ga_init2(&ga, (int)sizeof(char*), 20); -#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 - /* Only escape when needed, double quotes are not always allowed. */ - if (li != l->lv_first && vim_strpbrk(s, (char_u *)" \t\"") != NULL) - { - s = vim_strsave_shellescape(s, FALSE, TRUE); - if (s == NULL) - goto theend; - ga_concat(&ga, s); - vim_free(s); - } - else - ga_concat(&ga, 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 - if (ch_log_active()) - { - garray_T ga; - int i; - - ga_init2(&ga, (int)sizeof(char), 200); - for (i = 0; i < argc; ++i) - { - if (i > 0) - ga_concat(&ga, (char_u *)" "); - ga_concat(&ga, (char_u *)argv[i]); - } - ch_logs(NULL, "Starting job: %s", (char *)ga.ga_data); - ga_clear(&ga); - } - mch_start_job(argv, job, &opt); -#else - ch_logs(NULL, "Starting job: %s", (char *)cmd); - mch_start_job((char *)cmd, job, &opt); -#endif - - /* If the channel is reading from a buffer, write lines now. */ - if (job->jv_channel != NULL) - channel_write_in(job->jv_channel); - -theend: -#ifdef USE_ARGV - vim_free(argv); -#else - vim_free(ga.ga_data); -#endif -} - -/* - * Get the status of "job" and invoke the exit callback when needed. - * The returned string is not allocated. - */ - static char * -job_status(job_T *job) -{ - char *result; - - 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); - if (job->jv_status == JOB_ENDED) - ch_log(job->jv_channel, "Job ended"); - if (job->jv_status == JOB_ENDED && job->jv_exit_cb != NULL) - { - typval_T argv[3]; - typval_T rettv; - int dummy; - - /* invoke the exit callback; make sure the refcount is > 0 */ - ++job->jv_refcount; - argv[0].v_type = VAR_JOB; - argv[0].vval.v_job = job; - argv[1].v_type = VAR_NUMBER; - argv[1].vval.v_number = job->jv_exitval; - call_func(job->jv_exit_cb, (int)STRLEN(job->jv_exit_cb), - &rettv, 2, argv, 0L, 0L, &dummy, TRUE, NULL); - clear_tv(&rettv); - --job->jv_refcount; - } - if (job->jv_status == JOB_ENDED && job->jv_refcount == 0) - { - /* The job was already unreferenced, now that it ended it can be - * freed. Careful: caller must not use "job" after this! */ - job_free(job); - } - } - return result; -} - -/* - * Called once in a while: check if any jobs with an "exit-cb" have ended. - */ - void -job_check_ended(void) -{ - static time_t last_check = 0; - time_t now; - job_T *job; - job_T *next; - - /* Only do this once in 10 seconds. */ - now = time(NULL); - if (last_check + 10 < now) - { - last_check = now; - for (job = first_job; job != NULL; job = next) - { - next = job->jv_next; - if (job->jv_status == JOB_STARTED && job->jv_exit_cb != NULL) - job_status(job); /* may free "job" */ - } - } + rettv->vval.v_job = job_start(argvars); } /* @@ -15405,38 +14389,12 @@ f_job_status(typval_T *argvars, typval_T * "job_stop()" function */ static void -f_job_stop(typval_T *argvars UNUSED, typval_T *rettv UNUSED) +f_job_stop(typval_T *argvars, typval_T *rettv) { job_T *job = get_job_arg(&argvars[0]); if (job != NULL) - { - 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; - } - } - ch_logs(job->jv_channel, "Stopping job with '%s'", (char *)arg); - if (mch_stop_job(job, arg) == FAIL) - rettv->vval.v_number = 0; - else - { - rettv->vval.v_number = 1; - /* Assume that "hup" does not kill the job. */ - if (job->jv_channel != NULL && STRCMP(arg, "hup") != 0) - job->jv_channel->ch_job_killed = TRUE; - } - /* We don't try freeing the job, obviously the caller still has a - * reference to it. */ - } + rettv->vval.v_number = job_stop(job, argvars); } #endif @@ -22475,7 +21433,7 @@ init_tv(typval_T *varp) * caller of incompatible types: it sets *denote to TRUE if "denote" * is not NULL or returns -1 otherwise. */ - static long + long get_tv_number(typval_T *varp) { int error = FALSE; @@ -22626,7 +21584,7 @@ get_tv_lnum_buf(typval_T *argvars, buf_T * get_tv_string_chk() and get_tv_string_buf_chk() are similar, but return * NULL on error. */ - static char_u * + char_u * get_tv_string(typval_T *varp) { static char_u mybuf[NUMBUFLEN]; @@ -22634,7 +21592,7 @@ get_tv_string(typval_T *varp) return get_tv_string_buf(varp, mybuf); } - static char_u * + char_u * get_tv_string_buf(typval_T *varp, char_u *buf) { char_u *res = get_tv_string_buf_chk(varp, buf); diff --git a/src/proto/channel.pro b/src/proto/channel.pro --- a/src/proto/channel.pro +++ b/src/proto/channel.pro @@ -1,13 +1,14 @@ /* channel.c */ -void ch_logfile(FILE *file); +void ch_logfile(char_u *fname, char_u *opt); int ch_log_active(void); void ch_log(channel_T *ch, char *msg); void ch_logs(channel_T *ch, char *msg, char *name); channel_T *add_channel(void); -int channel_may_free(channel_T *channel); +int channel_unref(channel_T *channel); void channel_free(channel_T *channel); void channel_gui_register_all(void); channel_T *channel_open(char *hostname, int port_in, int waittime, void (*nb_close_cb)(void)); +channel_T *channel_open_func(typval_T *argvars); void channel_set_pipes(channel_T *channel, sock_T in, sock_T out, sock_T err); void channel_set_job(channel_T *channel, job_T *job, jobopt_T *options); void channel_set_options(channel_T *channel, jobopt_T *opt); @@ -27,9 +28,13 @@ int channel_get_id(void); void channel_read(channel_T *channel, int part, char *func); char_u *channel_read_block(channel_T *channel, int part, int timeout); int channel_read_json_block(channel_T *channel, int part, int timeout, int id, typval_T **rettv); +void common_channel_read(typval_T *argvars, typval_T *rettv, int raw); channel_T *channel_fd2channel(sock_T fd, int *partp); void channel_handle_events(void); int channel_send(channel_T *channel, int part, char_u *buf, char *fun); +channel_T *send_common(typval_T *argvars, char_u *text, int id, int eval, jobopt_T *opt, char *fun, int *part_read); +void ch_expr_common(typval_T *argvars, typval_T *rettv, int eval); +void ch_raw_common(typval_T *argvars, typval_T *rettv, int eval); int channel_poll_setup(int nfd_in, void *fds_in); int channel_poll_check(int ret_in, void *fds_in); int channel_select_setup(int maxfd_in, void *rfds_in); @@ -40,4 +45,14 @@ int channel_part_send(channel_T *channel int channel_part_read(channel_T *channel); ch_mode_T channel_get_mode(channel_T *channel, int part); int channel_get_timeout(channel_T *channel, int part); +void clear_job_options(jobopt_T *opt); +int get_job_options(typval_T *tv, jobopt_T *opt, int supported); +channel_T *get_channel_arg(typval_T *tv, int check_open); +void job_unref(job_T *job); +void job_set_options(job_T *job, jobopt_T *opt); +void job_stop_on_exit(void); +void job_check_ended(void); +job_T *job_start(typval_T *argvars); +char *job_status(job_T *job); +int job_stop(job_T *job, typval_T *argvars); /* vim: set ft=c : */ diff --git a/src/proto/eval.pro b/src/proto/eval.pro --- a/src/proto/eval.pro +++ b/src/proto/eval.pro @@ -79,15 +79,13 @@ int dict_add_list(dict_T *d, char *key, dictitem_T *dict_find(dict_T *d, char_u *key, int len); char_u *get_dict_string(dict_T *d, char_u *key, int save); long get_dict_number(dict_T *d, char_u *key); -int channel_unref(channel_T *channel); -void job_stop_on_exit(void); int string2float(char_u *text, float_T *value); char_u *get_function_name(expand_T *xp, int idx); char_u *get_expr_name(expand_T *xp, int idx); int call_func(char_u *funcname, int len, typval_T *rettv, int argcount, typval_T *argvars, linenr_T firstline, linenr_T lastline, int *doesrange, int evaluate, dict_T *selfdict); +buf_T *buflist_find_by_name(char_u *name, int curtab_only); int func_call(char_u *name, typval_T *args, dict_T *selfdict, typval_T *rettv); void dict_extend(dict_T *d1, dict_T *d2, char_u *action); -void job_check_ended(void); void mzscheme_call_vim(char_u *name, typval_T *args, typval_T *rettv); float_T vim_round(float_T f); long do_searchpair(char_u *spat, char_u *mpat, char_u *epat, int dir, char_u *skip, int flags, pos_T *match_pos, linenr_T lnum_stop, long time_limit); @@ -107,7 +105,10 @@ char_u *set_cmdarg(exarg_T *eap, char_u typval_T *alloc_tv(void); void free_tv(typval_T *varp); void clear_tv(typval_T *varp); +long get_tv_number(typval_T *varp); long get_tv_number_chk(typval_T *varp, int *denote); +char_u *get_tv_string(typval_T *varp); +char_u *get_tv_string_buf(typval_T *varp, char_u *buf); char_u *get_tv_string_chk(typval_T *varp); char_u *get_tv_string_buf_chk(typval_T *varp, char_u *buf); char_u *get_var_value(char_u *name); diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -744,6 +744,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1539, +/**/ 1538, /**/ 1537,