summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2016-11-29 21:54:44 +0100
committerBram Moolenaar <Bram@vim.org>2016-11-29 21:54:44 +0100
commit4b785f69c0616dba5d3f38e8ce4b5398cec89407 (patch)
treefea8b654714ff391135ce61da9c9d0792b7a5265
parentf422bcc7f9615fe91fa69b059cfe4785093d3d4a (diff)
downloadvim-4b785f69c0616dba5d3f38e8ce4b5398cec89407.zip
patch 8.0.0105
Problem: When using ch_read() with zero timeout, can't tell the difference between reading an empty line and nothing available. Solution: Add ch_canread().
-rw-r--r--runtime/doc/channel.txt11
-rw-r--r--runtime/doc/eval.txt31
-rw-r--r--src/channel.c2
-rw-r--r--src/evalfunc.c17
-rw-r--r--src/proto/channel.pro1
-rw-r--r--src/testdir/shared.vim2
-rw-r--r--src/testdir/test_channel.vim13
-rw-r--r--src/version.c2
8 files changed, 65 insertions, 14 deletions
diff --git a/runtime/doc/channel.txt b/runtime/doc/channel.txt
index 73105986f..2c3f83730 100644
--- a/runtime/doc/channel.txt
+++ b/runtime/doc/channel.txt
@@ -418,7 +418,11 @@ This uses the channel timeout. To read without a timeout, just get any
message that is available: >
let output = ch_read(channel, {'timeout': 0})
When no message was available then the result is v:none for a JSON or JS mode
-channels, an empty string for a RAW or NL channel.
+channels, an empty string for a RAW or NL channel. You can use |ch_canread()|
+to check if there is something to read.
+
+Note that when there is no callback message are dropped. To avoid that add a
+close callback to the channel.
To read all output from a RAW channel that is available: >
let output = ch_readraw(channel)
@@ -470,6 +474,11 @@ This depends on the system (on Unix this happens because closing the write end
of a pipe causes the read end to get EOF). To avoid this make the job sleep
for a short while before it exits.
+Note that if the job exits before you read the output, the output may be lost.
+This depends on the system (on Unix this happens because closing the write end
+of a pipe causes the read end to get EOF). To avoid this make the job sleep
+for a short while before it exits.
+
The handler defined for "out_cb" will not receive stderr. If you want to
handle that separately, add an "err_cb" handler: >
let job = job_start(command, {"out_cb": "MyHandler",
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index 1a11a4141..75c2ea113 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -2009,6 +2009,7 @@ byteidxcomp({expr}, {nr}) Number byte index of {nr}'th char in {expr}
call({func}, {arglist} [, {dict}])
any call {func} with arguments {arglist}
ceil({expr}) Float round {expr} up
+ch_canread({handle}) Number check if there is something to read
ch_close({handle}) none close {handle}
ch_close_in({handle}) none close in part of {handle}
ch_evalexpr({handle}, {expr} [, {options}])
@@ -2980,16 +2981,28 @@ confirm({msg} [, {choices} [, {default} [, {type}]]])
don't fit, a vertical layout is used anyway. For some systems
the horizontal layout is always used.
+ch_canread({handle}) *ch_canread()*
+ Return non-zero when there is something to read from {handle}.
+ {handle} can be a Channel or a Job that has a Channel.
+
+ This is useful to read from a channel at a convenient time,
+ e.g. from a timer.
+
+ Note that messages are dropped when the channel does not have
+ a callback. Add a close callback to avoid that.
+
+ {only available when compiled with the |+channel| feature}
+
ch_close({handle}) *ch_close()*
Close {handle}. See |channel-close|.
- {handle} can be Channel or a Job that has a Channel.
+ {handle} can be a Channel or a Job that has a Channel.
A close callback is not invoked.
{only available when compiled with the |+channel| feature}
ch_close_in({handle}) *ch_close_in()*
Close the "in" part of {handle}. See |channel-close-in|.
- {handle} can be Channel or a Job that has a Channel.
+ {handle} can be a Channel or a Job that has a Channel.
A close callback is not invoked.
{only available when compiled with the |+channel| feature}
@@ -2998,7 +3011,7 @@ ch_evalexpr({handle}, {expr} [, {options}]) *ch_evalexpr()*
Send {expr} over {handle}. The {expr} is encoded
according to the type of channel. The function cannot be used
with a raw channel. See |channel-use|.
- {handle} can be Channel or a Job that has a Channel.
+ {handle} can be a Channel or a Job that has a Channel.
*E917*
{options} must be a Dictionary. It must not have a "callback"
entry. It can have a "timeout" entry to specify the timeout
@@ -3012,7 +3025,7 @@ ch_evalexpr({handle}, {expr} [, {options}]) *ch_evalexpr()*
ch_evalraw({handle}, {string} [, {options}]) *ch_evalraw()*
Send {string} over {handle}.
- {handle} can be Channel or a Job that has a Channel.
+ {handle} can be a Channel or a Job that has a Channel.
Works like |ch_evalexpr()|, but does not encode the request or
decode the response. The caller is responsible for the
@@ -3025,7 +3038,7 @@ ch_evalraw({handle}, {string} [, {options}]) *ch_evalraw()*
ch_getbufnr({handle}, {what}) *ch_getbufnr()*
Get the buffer number that {handle} is using for {what}.
- {handle} can be Channel or a Job that has a Channel.
+ {handle} can be a Channel or a Job that has a Channel.
{what} can be "err" for stderr, "out" for stdout or empty for
socket output.
Returns -1 when there is no buffer.
@@ -3099,7 +3112,7 @@ ch_open({address} [, {options}]) *ch_open()*
ch_read({handle} [, {options}]) *ch_read()*
Read from {handle} and return the received message.
- {handle} can be Channel or a Job that has a Channel.
+ {handle} can be a Channel or a Job that has a Channel.
See |channel-more|.
{only available when compiled with the |+channel| feature}
@@ -3113,7 +3126,7 @@ ch_sendexpr({handle}, {expr} [, {options}]) *ch_sendexpr()*
according to the type of channel. The function cannot be used
with a raw channel.
See |channel-use|. *E912*
- {handle} can be Channel or a Job that has a Channel.
+ {handle} can be a Channel or a Job that has a Channel.
{only available when compiled with the |+channel| feature}
@@ -3134,7 +3147,7 @@ ch_setoptions({handle}, {options}) *ch_setoptions()*
"timeout" default read timeout in msec
"mode" mode for the whole channel
See |ch_open()| for more explanation.
- {handle} can be Channel or a Job that has a Channel.
+ {handle} can be a Channel or a Job that has a Channel.
Note that changing the mode may cause queued messages to be
lost.
@@ -3148,7 +3161,7 @@ ch_status({handle} [, {options}]) *ch_status()*
"open" channel can be used
"buffered" channel can be read, not written to
"closed" channel can not be used
- {handle} can be Channel or a Job that has a Channel.
+ {handle} can be a Channel or a Job that has a Channel.
"buffered" is used when the channel was closed but there is
still data that can be obtained with |ch_read()|.
diff --git a/src/channel.c b/src/channel.c
index 6c5a4ff1d..e5f28003e 100644
--- a/src/channel.c
+++ b/src/channel.c
@@ -2603,7 +2603,7 @@ channel_is_open(channel_T *channel)
/*
* Return TRUE if "channel" has JSON or other typeahead.
*/
- static int
+ int
channel_has_readahead(channel_T *channel, ch_part_T part)
{
ch_mode_T ch_mode = channel->ch_part[part].ch_mode;
diff --git a/src/evalfunc.c b/src/evalfunc.c
index 846a91416..88e4852fe 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -76,6 +76,7 @@ static void f_call(typval_T *argvars, typval_T *rettv);
static void f_ceil(typval_T *argvars, typval_T *rettv);
#endif
#ifdef FEAT_JOB_CHANNEL
+static void f_ch_canread(typval_T *argvars, typval_T *rettv);
static void f_ch_close(typval_T *argvars, typval_T *rettv);
static void f_ch_close_in(typval_T *argvars, typval_T *rettv);
static void f_ch_evalexpr(typval_T *argvars, typval_T *rettv);
@@ -499,6 +500,7 @@ static struct fst
{"ceil", 1, 1, f_ceil},
#endif
#ifdef FEAT_JOB_CHANNEL
+ {"ch_canread", 1, 1, f_ch_canread},
{"ch_close", 1, 1, f_ch_close},
{"ch_close_in", 1, 1, f_ch_close_in},
{"ch_evalexpr", 2, 3, f_ch_evalexpr},
@@ -1779,6 +1781,21 @@ f_ceil(typval_T *argvars, typval_T *rettv)
#ifdef FEAT_JOB_CHANNEL
/*
+ * "ch_canread()" function
+ */
+ static void
+f_ch_canread(typval_T *argvars, typval_T *rettv)
+{
+ channel_T *channel = get_channel_arg(&argvars[0], TRUE, TRUE, 0);
+
+ rettv->vval.v_number = 0;
+ if (channel != NULL)
+ rettv->vval.v_number = channel_has_readahead(channel, PART_SOCK)
+ || channel_has_readahead(channel, PART_OUT)
+ || channel_has_readahead(channel, PART_ERR);
+}
+
+/*
* "ch_close()" function
*/
static void
diff --git a/src/proto/channel.pro b/src/proto/channel.pro
index 8640fa7bb..f127268ed 100644
--- a/src/proto/channel.pro
+++ b/src/proto/channel.pro
@@ -25,6 +25,7 @@ void channel_consume(channel_T *channel, ch_part_T part, int len);
int channel_collapse(channel_T *channel, ch_part_T part, int want_nl);
int channel_can_write_to(channel_T *channel);
int channel_is_open(channel_T *channel);
+int channel_has_readahead(channel_T *channel, ch_part_T part);
char *channel_status(channel_T *channel, int req_part);
void channel_info(channel_T *channel, dict_T *dict);
void channel_close(channel_T *channel, int invoke_close_cb);
diff --git a/src/testdir/shared.vim b/src/testdir/shared.vim
index 45a2ea496..8160af385 100644
--- a/src/testdir/shared.vim
+++ b/src/testdir/shared.vim
@@ -88,7 +88,7 @@ func RunServer(cmd, testfunc, args)
call call(function(a:testfunc), [port])
catch
- call assert_false(1, "Caught exception: " . v:exception)
+ call assert_false(1, 'Caught exception: "' . v:exception . '" in ' . v:throwpoint)
finally
call s:kill_server(a:cmd)
endtry
diff --git a/src/testdir/test_channel.vim b/src/testdir/test_channel.vim
index 42e0e04b8..85f80e25c 100644
--- a/src/testdir/test_channel.vim
+++ b/src/testdir/test_channel.vim
@@ -58,6 +58,9 @@ func Ch_communicate(port)
" string with ][ should work
call assert_equal('this][that', ch_evalexpr(handle, 'echo this][that'))
+ " nothing to read now
+ call assert_equal(0, ch_canread(handle))
+
" sending three messages quickly then reading should work
for i in range(3)
call ch_sendexpr(handle, 'echo hello ' . i)
@@ -368,7 +371,7 @@ func Ch_raw_one_time_callback(port)
endif
call ch_setoptions(handle, {'mode': 'raw'})
- " The message are sent raw, we do our own JSON strings here.
+ " The messages are sent raw, we do our own JSON strings here.
call ch_sendraw(handle, "[1, \"hello!\"]\n", {'callback': 'Ch_handleRaw1'})
call WaitFor('g:Ch_reply1 != ""')
call assert_equal("[1, \"got it\"]", g:Ch_reply1)
@@ -431,7 +434,10 @@ func Test_raw_pipe()
return
endif
call ch_log('Test_raw_pipe()')
- let job = job_start(s:python . " test_channel_pipe.py", {'mode': 'raw'})
+ " Add a dummy close callback to avoid that messages are dropped when calling
+ " ch_canread().
+ let job = job_start(s:python . " test_channel_pipe.py",
+ \ {'mode': 'raw', 'close_cb': {chan -> 0}})
call assert_equal(v:t_job, type(job))
call assert_equal("run", job_status(job))
@@ -458,6 +464,9 @@ func Test_raw_pipe()
call assert_equal("something\n", substitute(msg, "\r", "", 'g'))
call ch_sendraw(job, "double this\n")
+ let g:handle = job_getchannel(job)
+ call WaitFor('ch_canread(g:handle)')
+ unlet g:handle
let msg = ch_readraw(job)
call assert_equal("this\nAND this\n", substitute(msg, "\r", "", 'g'))
diff --git a/src/version.c b/src/version.c
index 0e3554ddc..02d1be454 100644
--- a/src/version.c
+++ b/src/version.c
@@ -765,6 +765,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 105,
+/**/
104,
/**/
103,