# HG changeset patch # User Christian Brabandt # Date 1456580705 -3600 # Node ID 108d30ed34ba6698409286e3a076218860b3918e # Parent 62545a8dec53632d792ddc73a838d66123b54e7a commit https://github.com/vim/vim/commit/187db50d0499aecf4cfd42fb4db0a1bebf61c8cd Author: Bram Moolenaar Date: Sat Feb 27 14:44:26 2016 +0100 patch 7.4.1426 Problem: The "out-io" option for jobs is not implemented yet. Solution: Implement the "buffer" value: append job output to a buffer. diff --git a/runtime/doc/channel.txt b/runtime/doc/channel.txt --- a/runtime/doc/channel.txt +++ b/runtime/doc/channel.txt @@ -130,6 +130,8 @@ Use |ch_status()| to see if the channel overwritten. Therefore set "mode" first and the part specific mode later. + Note: when writing to a file or buffer NL mode is always used. + *channel-callback* "callback" A function that is called when a message is received that is not handled otherwise. It gets two arguments: the channel @@ -198,6 +200,7 @@ Once done with the channel, disconnect i When a socket is used this will close the socket for both directions. When pipes are used (stdin/stdout/stderr) they are all closed. This might not be what you want! Stopping the job with job_stop() might be better. +All readahead is discarded, callbacks will no longer be invoked. When the channel can't be opened you will get an error message. There is a difference between MS-Windows and Unix: On Unix when the port doesn't exist @@ -327,7 +330,7 @@ It will send back the result of the expr [-2, "last line"] ~ The format is: [{number}, {result}] - *E915* + Here {number} is the same as what was in the request. Use a negative number to avoid confusion with message that Vim sends. Use a different number on every request to be able to match the request with the response. @@ -397,7 +400,7 @@ are: "closed" The channel was closed. TODO: -To objain the job associated with a channel: ch_getjob(channel) +To obtain the job associated with a channel: ch_getjob(channel) To read one message from a channel: > let output = ch_read(channel) @@ -448,10 +451,13 @@ You can send a message to the command wi JSON or JS mode you can use ch_sendexpr(). There are several options you can use, see |job-options|. +For example, to start a job and write its output in buffer "dummy": > + let logjob = job_start("tail -f /tmp/log", + \ {'out-io': 'buffer', 'out-name': 'dummy'}) + sbuf dummy TODO: To run a job and read its output once it is done: > - let job = job_start({command}, {'exit-cb': 'MyHandler'}) func MyHandler(job, status) let channel = job_getchannel() @@ -508,7 +514,7 @@ See |job_setoptions()| and |ch_setoption *job-err-cb* "err-cb": handler Callback for when there is something to read on stderr. -TODO: *job-close-cb* + *job-close-cb* "close-cb": handler Callback for when the channel is closed. Same as "close-cb" on ch_open(). *job-exit-cb* @@ -527,28 +533,44 @@ TODO: *job-term* "term": "open" Start a terminal and connect the job stdin/stdout/stderr to it. -TODO: *job-in-io* -"in-io": "null" disconnect stdin + *job-in-io* +"in-io": "null" disconnect stdin TODO "in-io": "pipe" stdin is connected to the channel (default) -"in-io": "file" stdin reads from a file -"in-file": "/path/file" the file to read from +"in-io": "file" stdin reads from a file TODO +"in-io": "buffer" stdin reads from a buffer TODO +"in-name": "/path/file" the name of he file or buffer to read from +"in-buf": number the number of the buffer to read from TODO -TODO: *job-out-io* -"out-io": "null" disconnect stdout + *job-out-io* +"out-io": "null" disconnect stdout TODO "out-io": "pipe" stdout is connected to the channel (default) -"out-io": "file" stdout writes to a file -"out-file": "/path/file" the file to write to +"out-io": "file" stdout writes to a file TODO "out-io": "buffer" stdout appends to a buffer -"out-buffer": "name" buffer to append to +"out-name": "/path/file" the name of the file or buffer to write to +"out-buf": number the number of the buffer to write to TODO + + *job-err-io* +"err-io": "out" same as stdout TODO +"err-io": "null" disconnect stderr TODO +"err-io": "pipe" stderr is connected to the channel (default) +"err-io": "file" stderr writes to a file TODO +"err-io": "buffer" stderr appends to a buffer TODO +"err-name": "/path/file" the name of the file or buffer to write to +"err-buf": number the number of the buffer to write to TODO -TODO: *job-err-io* -"err-io": "out" same type as stdout (default) -"err-io": "null" disconnect stderr -"err-io": "pipe" stderr is connected to the channel -"err-io": "file" stderr writes to a file -"err-file": "/path/file" the file to write to -"err-io": "buffer" stderr appends to a buffer -"err-buffer": "name" buffer to append to +When the IO mode is "buffer" and there is a callback, the text is appended to +the buffer before invoking the callback. + *E915* +The name of the buffer is compared the full name of existing buffers. If +there is a match that buffer is used. Otherwise a new buffer is created, +where 'buftype' is set to "nofile" and 'bufhidden' to "hide". If you prefer +other settings, create the buffer first and pass the buffer number. + +When the buffer written to is displayed in a window and the cursor is in the +first column of the last line, the cursor will be moved to the newly added +line and the window is scrolled up to show the cursor if needed. + +Undo is synced for every added line. ============================================================================== 11. Controlling a job *job-control* diff --git a/src/channel.c b/src/channel.c --- a/src/channel.c +++ b/src/channel.c @@ -52,6 +52,10 @@ # define fd_close(sd) close(sd) #endif +/* Whether a redraw is needed for appending a line to a buffer. */ +static int channel_need_redraw = FALSE; + + #ifdef WIN32 static int fd_read(sock_T fd, char *buf, size_t len) @@ -342,6 +346,7 @@ channel_may_free(channel_T *channel) channel_free(channel_T *channel) { channel_close(channel, TRUE); + channel_clear(channel); if (channel->ch_next != NULL) channel->ch_next->ch_prev = channel->ch_prev; if (channel->ch_prev == NULL) @@ -777,6 +782,35 @@ channel_set_job(channel_T *channel, job_ } /* + * Find a buffer matching "name" or create a new one. + */ + static buf_T * +find_buffer(char_u *name) +{ + buf_T *buf = buflist_findname(name); + buf_T *save_curbuf = curbuf; + + if (buf == NULL) + { + buf = buflist_new(name, NULL, (linenr_T)0, BLN_LISTED); + buf_copy_options(buf, BCO_ENTER); +#ifdef FEAT_QUICKFIX + clear_string_option(&buf->b_p_bt); + buf->b_p_bt = vim_strsave((char_u *)"nofile"); + clear_string_option(&buf->b_p_bh); + buf->b_p_bh = vim_strsave((char_u *)"hide"); +#endif + curbuf = buf; + ml_open(curbuf); + ml_replace(1, (char_u *)"Reading from channel output...", TRUE); + changed_bytes(1, 0); + curbuf = save_curbuf; + } + + return buf; +} + +/* * Set various properties from an "opt" argument. */ void @@ -839,6 +873,16 @@ channel_set_options(channel_T *channel, else *cbp = NULL; } + + if ((opt->jo_set & JO_OUT_IO) && opt->jo_io[PART_OUT] == JIO_BUFFER) + { + /* writing output to a buffer. Force mode to NL. */ + channel->ch_part[PART_OUT].ch_mode = MODE_NL; + channel->ch_part[PART_OUT].ch_buffer = + find_buffer(opt->jo_io_name[PART_OUT]); + ch_logs(channel, "writing to buffer %s", + (char *)channel->ch_part[PART_OUT].ch_buffer->b_ffname); + } } /* @@ -1303,6 +1347,7 @@ may_invoke_callback(channel_T *channel, int seq_nr = -1; ch_mode_T ch_mode = channel->ch_part[part].ch_mode; char_u *callback = NULL; + buf_T *buffer = NULL; if (channel->ch_nb_close_cb != NULL) /* this channel is handled elsewhere (netbeans) */ @@ -1312,6 +1357,7 @@ may_invoke_callback(channel_T *channel, callback = channel->ch_part[part].ch_callback; else callback = channel->ch_callback; + buffer = channel->ch_part[part].ch_buffer; if (ch_mode == MODE_JSON || ch_mode == MODE_JS) { @@ -1361,8 +1407,8 @@ may_invoke_callback(channel_T *channel, } else { - /* If there is no callback drop the message. */ - if (callback == NULL) + /* If there is no callback or buffer drop the message. */ + if (callback == NULL && buffer == NULL) { while ((msg = channel_get(channel, part)) != NULL) vim_free(msg); @@ -1386,8 +1432,11 @@ may_invoke_callback(channel_T *channel, return FALSE; /* incomplete message */ } if (nl[1] == NUL) - /* get the whole buffer */ + { + /* get the whole buffer, drop the NL */ msg = channel_get(channel, part); + *nl = NUL; + } else { /* Copy the message into allocated memory and remove it from @@ -1431,11 +1480,54 @@ may_invoke_callback(channel_T *channel, if (!done) ch_log(channel, "Dropping message without callback"); } - else if (callback != NULL) + else if (callback != NULL || buffer != NULL) { - /* invoke the channel callback */ - ch_log(channel, "Invoking channel callback"); - invoke_callback(channel, callback, argv); + if (buffer != NULL) + { + buf_T *save_curbuf = curbuf; + linenr_T lnum = buffer->b_ml.ml_line_count; + + /* Append to the buffer */ + ch_logn(channel, "appending line %d to buffer", (int)lnum + 1); + + curbuf = buffer; + u_sync(TRUE); + u_save(lnum, lnum + 1); + + ml_append(lnum, msg, 0, FALSE); + appended_lines_mark(lnum, 1L); + curbuf = save_curbuf; + + if (buffer->b_nwindows > 0) + { + win_T *wp; + win_T *save_curwin; + + FOR_ALL_WINDOWS(wp) + { + if (wp->w_buffer == buffer + && wp->w_cursor.lnum == lnum + && wp->w_cursor.col == 0) + { + ++wp->w_cursor.lnum; + save_curwin = curwin; + curwin = wp; + curbuf = curwin->w_buffer; + scroll_cursor_bot(0, FALSE); + curwin = save_curwin; + curbuf = curwin->w_buffer; + } + } + redraw_buf_later(buffer, VALID); + channel_need_redraw = TRUE; + } + } + if (callback != NULL) + { + /* invoke the channel callback */ + ch_log(channel, "Invoking channel callback"); + invoke_callback(channel, callback, argv); + } } else ch_log(channel, "Dropping message"); @@ -1493,6 +1585,7 @@ channel_status(channel_T *channel) /* * Close channel "channel". * Trigger the close callback if "invoke_close_cb" is TRUE. + * Does not clear the buffers. */ void channel_close(channel_T *channel, int invoke_close_cb) @@ -1548,7 +1641,6 @@ channel_close(channel_T *channel, int in } channel->ch_nb_close_cb = NULL; - channel_clear(channel); } /* @@ -2160,6 +2252,24 @@ channel_select_check(int ret_in, void *r # endif /* !WIN32 && HAVE_SELECT */ /* + * Return TRUE if "channel" has JSON or other typeahead. + */ + static int +channel_has_readahead(channel_T *channel, int part) +{ + ch_mode_T ch_mode = channel->ch_part[part].ch_mode; + + if (ch_mode == MODE_JSON || ch_mode == MODE_JS) + { + jsonq_T *head = &channel->ch_part[part].ch_json_head; + jsonq_T *item = head->jq_next; + + return item != NULL; + } + return channel_peek(channel, part) != NULL; +} + +/* * Execute queued up commands. * Invoked from the main loop when it's safe to execute received commands. * Return TRUE when something was done. @@ -2172,6 +2282,7 @@ channel_parse_messages(void) int r; int part = PART_SOCK; + ch_log(NULL, "looking for messages on channels"); while (channel != NULL) { if (channel->ch_refcount == 0 && !channel_still_useful(channel)) @@ -2182,7 +2293,8 @@ channel_parse_messages(void) part = PART_SOCK; continue; } - if (channel->ch_part[part].ch_fd != INVALID_FD) + if (channel->ch_part[part].ch_fd != INVALID_FD + || channel_has_readahead(channel, part)) { /* Increase the refcount, in case the handler causes the channel * to be unreferenced or closed. */ @@ -2208,6 +2320,16 @@ channel_parse_messages(void) part = PART_SOCK; } } + + if (channel_need_redraw && must_redraw) + { + channel_need_redraw = FALSE; + update_screen(0); + setcursor(); + cursor_on(); + out_flush(); + } + return ret; } diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -9976,6 +9976,30 @@ handle_mode(typval_T *item, jobopt_T *op 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) { @@ -9983,6 +10007,15 @@ clear_job_options(jobopt_T *opt) } /* + * 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". @@ -9996,6 +10029,7 @@ get_job_options(typval_T *tv, jobopt_T * dict_T *dict; int todo; hashitem_T *hi; + int part; opt->jo_set = 0; if (tv->v_type == VAR_UNKNOWN) @@ -10046,6 +10080,27 @@ get_job_options(typval_T *tv, jobopt_T * == 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, "callback") == 0) { if (!(supported & JO_CALLBACK)) @@ -10178,6 +10233,13 @@ get_job_options(typval_T *tv, jobopt_T * return FAIL; } + for (part = PART_OUT; part <= PART_IN; ++part) + if (opt->jo_io[part] == JIO_BUFFER && opt->jo_io_name[part] == NULL) + { + EMSG(_("E915: Missing name for buffer")); + return FAIL; + } + return OK; } #endif @@ -10216,7 +10278,10 @@ f_ch_close(typval_T *argvars, typval_T * channel_T *channel = get_channel_arg(&argvars[0]); if (channel != NULL) + { channel_close(channel, FALSE); + channel_clear(channel); + } } # ifdef FEAT_JOB @@ -14948,7 +15013,7 @@ f_job_start(typval_T *argvars UNUSED, ty 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) == FAIL) + + JO_STOPONEXIT + JO_EXIT_CB + JO_OUT_IO) == FAIL) return; job_set_options(job, &opt); diff --git a/src/netbeans.c b/src/netbeans.c --- a/src/netbeans.c +++ b/src/netbeans.c @@ -99,8 +99,11 @@ netbeans_close(void) { netbeans_send_disconnect(); if (nb_channel != NULL) + { /* Close the socket and remove the input handlers. */ channel_close(nb_channel, TRUE); + channel_clear(nb_channel); + } nb_channel = NULL; } diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -1347,6 +1347,7 @@ typedef struct { cbq_T ch_cb_head; /* dummy node for per-request callbacks */ char_u *ch_callback; /* call when a msg is not handled */ + buf_T *ch_buffer; /* buffer to read from or write to */ } chanpart_T; struct channel_S { @@ -1395,6 +1396,12 @@ struct channel_S { #define JO_ID 0x2000 /* "id" */ #define JO_STOPONEXIT 0x4000 /* "stoponexit" */ #define JO_EXIT_CB 0x8000 /* "exit-cb" */ +#define JO_OUT_IO 0x10000 /* "out-io" */ +#define JO_ERR_IO 0x20000 /* "err-io" (JO_OUT_IO << 1) */ +#define JO_IN_IO 0x40000 /* "in-io" (JO_OUT_IO << 2) */ +#define JO_OUT_NAME 0x80000 /* "out-name" */ +#define JO_ERR_NAME 0x100000 /* "err-name" (JO_OUT_NAME << 1) */ +#define JO_IN_NAME 0x200000 /* "in-name" (JO_OUT_NAME << 2) */ #define JO_ALL 0xffffff #define JO_MODE_ALL (JO_MODE + JO_IN_MODE + JO_OUT_MODE + JO_ERR_MODE) @@ -1402,6 +1409,14 @@ struct channel_S { (JO_CALLBACK + JO_OUT_CALLBACK + JO_ERR_CALLBACK + JO_CLOSE_CALLBACK) #define JO_TIMEOUT_ALL (JO_TIMEOUT + JO_OUT_TIMEOUT + JO_ERR_TIMEOUT) +typedef enum { + JIO_NULL, + JIO_PIPE, + JIO_FILE, + JIO_BUFFER, + JIO_OUT +} job_io_T; + /* * Options for job and channel commands. */ @@ -1413,6 +1428,11 @@ typedef struct ch_mode_T jo_in_mode; ch_mode_T jo_out_mode; ch_mode_T jo_err_mode; + + job_io_T jo_io[4]; /* PART_OUT, PART_ERR, PART_IN */ + char_u jo_io_name_buf[4][NUMBUFLEN]; + char_u *jo_io_name[4]; /* not allocated! */ + char_u *jo_callback; /* not allocated! */ char_u *jo_out_cb; /* not allocated! */ char_u *jo_err_cb; /* not allocated! */ diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -749,6 +749,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1426, +/**/ 1425, /**/ 1424,