changeset 8267:108d30ed34ba v7.4.1426

commit https://github.com/vim/vim/commit/187db50d0499aecf4cfd42fb4db0a1bebf61c8cd Author: Bram Moolenaar <Bram@vim.org> 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.
author Christian Brabandt <cb@256bit.org>
date Sat, 27 Feb 2016 14:45:05 +0100
parents 62545a8dec53
children b9abd9942ec2
files runtime/doc/channel.txt src/channel.c src/eval.c src/netbeans.c src/structs.h src/version.c
diffstat 6 files changed, 265 insertions(+), 31 deletions(-) [+]
line wrap: on
line diff
--- 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*
--- 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;
 }
 
--- 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);
 
--- 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;
     }
 
--- 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! */
--- 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,