# HG changeset patch # User Christian Brabandt # Date 1480453205 -3600 # Node ID e664ee056a84cd0004a91788dcdd1ee8aa782d87 # Parent 474ff13f3ec3b93c10a74a47c3cdbf88757738b2 commit https://github.com/vim/vim/commit/4b785f69c0616dba5d3f38e8ce4b5398cec89407 Author: Bram Moolenaar Date: Tue Nov 29 21:54:44 2016 +0100 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(). diff --git a/runtime/doc/channel.txt b/runtime/doc/channel.txt --- a/runtime/doc/channel.txt +++ b/runtime/doc/channel.txt @@ -418,7 +418,11 @@ This uses the channel timeout. To read 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 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 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -2009,6 +2009,7 @@ byteidxcomp({expr}, {nr}) Number byte in 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} 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 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_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} [, {option 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_ 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 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 "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 "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 --- 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 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -76,6 +76,7 @@ static void f_call(typval_T *argvars, ty 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 *rett #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 --- a/src/proto/channel.pro +++ b/src/proto/channel.pro @@ -25,6 +25,7 @@ void channel_consume(channel_T *channel, 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 --- 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 --- 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 --- 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,