0
0
mirror of https://github.com/vim/vim.git synced 2025-07-26 11:04:33 -04:00

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.
This commit is contained in:
Bram Moolenaar 2016-02-27 14:44:26 +01:00
parent 6e722e2f94
commit 187db50d04
6 changed files with 265 additions and 31 deletions

View File

@ -130,6 +130,8 @@ Use |ch_status()| to see if the channel could be opened.
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 it like this: >
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 expression:
[-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 with ch_sendraw(). If the channel is in
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_setoptions()|.
*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
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
*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
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*

View File

@ -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)
@ -776,6 +781,35 @@ channel_set_job(channel_T *channel, job_T *job)
channel->ch_job = 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.
*/
@ -839,6 +873,16 @@ channel_set_options(channel_T *channel, jobopt_T *opt)
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 part)
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, int part)
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, int part)
}
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, int part)
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, int part)
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 invoke_close_cb)
}
channel->ch_nb_close_cb = NULL;
channel_clear(channel);
}
/*
@ -2159,6 +2251,24 @@ channel_select_check(int ret_in, void *rfds_in)
}
# 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.
@ -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;
}

View File

@ -9976,12 +9976,45 @@ handle_mode(typval_T *item, jobopt_T *opt, ch_mode_T *modep, int jo)
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".
@ -9996,6 +10029,7 @@ get_job_options(typval_T *tv, jobopt_T *opt, int supported)
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 *opt, int supported)
== 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 *opt, int supported)
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 *rettv UNUSED)
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, typval_T *rettv)
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);

View File

@ -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;
}

View File

@ -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! */

View File

@ -748,6 +748,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
1426,
/**/
1425,
/**/