# HG changeset patch # User Christian Brabandt # Date 1455653705 -3600 # Node ID 18a3f0f05244300314afe91c14a883f2b00f98a1 # Parent 4ffd0f8167f8e8e56360b3f2d963e961d7f57a19 commit https://github.com/vim/vim/commit/910b8aac5dc4693c4508b7acd2cef0bbfac04242 Author: Bram Moolenaar Date: Tue Feb 16 21:03:07 2016 +0100 patch 7.4.1341 Problem: It's difficult to add more arguments to ch_sendraw() and ch_sendexpr(). Solution: Make the third option a dictionary. diff --git a/runtime/doc/channel.txt b/runtime/doc/channel.txt --- a/runtime/doc/channel.txt +++ b/runtime/doc/channel.txt @@ -1,4 +1,4 @@ -*channel.txt* For Vim version 7.4. Last change: 2016 Feb 15 +*channel.txt* For Vim version 7.4. Last change: 2016 Feb 16 VIM REFERENCE MANUAL by Bram Moolenaar @@ -117,7 +117,7 @@ Use |ch_status()| to see if the channel "mode" can be: *channel-mode* "json" - Use JSON, see below; most convenient way. Default. - "js" - Use JavaScript encoding, more efficient than JSON. + "js" - Use JS (JavaScript) encoding, more efficient than JSON. "nl" - Use messages that end in a NL character "raw" - Use raw messages @@ -188,11 +188,11 @@ If there is an error reading or writing ============================================================================== 4. Using a JSON or JS channel *channel-use* -If {mode} is "json" then a message can be sent synchronously like this: > +If mode is JSON then a message can be sent synchronously like this: > let response = ch_sendexpr(channel, {expr}) This awaits a response from the other side. -When {mode} is "js" this works the same, except that the messages use +When mode is JS this works the same, except that the messages use JavaScript encoding. See |js_encode()| for the difference. To send a message, without handling a response: > @@ -242,7 +242,7 @@ is then completely responsible for corre ============================================================================== 5. Channel commands *channel-commands* -With a "json" channel the process can send commands to Vim that will be +With a JSON channel the process can send commands to Vim that will be handled by Vim internally, it does not require a handler for the channel. Possible commands are: *E903* *E904* *E905* @@ -316,14 +316,15 @@ Example: ============================================================================== 6. Using a RAW or NL channel *channel-raw* -If {mode} is "raw" then a message can be send like this: > +If mode is RAW or NL then a message can be send like this: > let response = ch_sendraw(channel, {string}) + The {string} is sent as-is. The response will be what can be read from the channel right away. Since Vim doesn't know how to recognize the end of the message you need to take care of it yourself. The timeout applies for reading the first byte, after that it will not wait for anything more. -If {mode} is "nl" you can send a message in a similar way. You are expected +If mode is "nl" you can send a message in a similar way. You are expected to put in the NL after each message. Thus you can also send several messages ending in a NL at once. The response will be the text up to and including the first NL. This can also be just the NL for an empty response. @@ -450,6 +451,7 @@ The {options} argument in job_start() is optional. The same options can be used with job_setoptions(job, {options}). TODO: *job-out-cb* +"callback": handler "out-cb": handler Callback for when there is something to read on stdout. TODO: *job-err-cb* @@ -484,7 +486,7 @@ TODO: *job-out-io* "out-buffer": "name" buffer to append to TODO: *job-err-io* -"err-io": "out" same as stdout (default) +"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 diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -1,4 +1,4 @@ -*eval.txt* For Vim version 7.4. Last change: 2016 Feb 13 +*eval.txt* For Vim version 7.4. Last change: 2016 Feb 16 VIM REFERENCE MANUAL by Bram Moolenaar @@ -1821,9 +1821,9 @@ ch_close( {handle}) none close a channe ch_logfile( {fname} [, {mode}]) none start logging channel activity ch_open( {address} [, {argdict})] Number open a channel to {address} ch_readraw( {handle}) String read from channel {handle} -ch_sendexpr( {handle}, {expr} [, {callback}]) +ch_sendexpr( {handle}, {expr} [, {options}]) any send {expr} over JSON channel {handle} -ch_sendraw( {handle}, {string} [, {callback}]) +ch_sendraw( {handle}, {string} [, {options}]) any send {string} over raw channel {handle} ch_status( {handle}) String status of channel {handle} changenr() Number current change number @@ -2725,28 +2725,32 @@ ch_readraw({handle}) *ch_readraw()* within that time an empty string is returned. TODO: depends on channel mode. -ch_sendexpr({handle}, {expr} [, {callback}]) *ch_sendexpr()* +ch_sendexpr({handle}, {expr} [, {options}]) *ch_sendexpr()* Send {expr} over channel {handle}. The {expr} is encoded according to the type of channel. The function cannot be used with a raw channel. See |channel-use|. *E912* - When {callback} is given returns immediately. Without - {callback} waits for a response and returns the decoded - expression. When there is an error or timeout returns an - empty string. - - When {callback} is zero no response is expected. - Otherwise {callback} must be a Funcref or the name of a - function. It is called when the response is received. See - |channel-callback|. + {options} must be a Dictionary. + When "callback" is a Funcref or the name of a function, + ch_sendexpr() returns immediately. The callback is invoked + when the response is received. See |channel-callback|. + + Without "callback" ch_sendexpr() waits for a response and + returns the decoded expression. When there is an error or + timeout it returns an empty string. + + When "callback" is zero no response is expected. {only available when compiled with the |+channel| feature} -ch_sendraw({handle}, {string} [, {callback}]) *ch_sendraw()* +ch_sendraw({handle}, {string} [, {options}]) *ch_sendraw()* Send {string} over channel {handle}. Works like |ch_sendexpr()|, but does not encode the request or decode the response. The caller is responsible for the - correct contents. See |channel-use|. + correct contents. Also does not add a newline for a channel + in NL mode, the caller must do that. The NL in the response + is removed. + See |channel-use|. {only available when compiled with the |+channel| feature} @@ -7274,7 +7278,7 @@ listcmds Compiled with commands for the and the argument list |arglist|. localmap Compiled with local mappings and abbr. |:map-local| lua Compiled with Lua interface |Lua|. -mac Macintosh version of Vim. +mac Any Macintosh version of Vim. macunix Compiled for OS X, with darwin osx Compiled for OS X, with or without darwin menu Compiled with support for |:menu|. diff --git a/src/channel.c b/src/channel.c --- a/src/channel.c +++ b/src/channel.c @@ -697,6 +697,18 @@ channel_set_callback(channel_T *channel, } /* + * Set various properties from an "options" argument. + */ + void +channel_set_options(channel_T *channel, jobopt_T *options) +{ + channel_set_mode(channel, options->jo_mode); + + if (options->jo_callback != NULL && *options->jo_callback != NUL) + channel_set_callback(channel, options->jo_callback); +} + +/* * Set the callback for channel "channel" for the response with "id". */ void diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -9930,15 +9930,18 @@ f_ch_logfile(typval_T *argvars, typval_T } /* - * Get the "mode" entry from "dict", if it exists, and parse the mode name. - * If the mode is invalide return FAIL. - */ - static int -get_mode_arg(dict_T *dict, jobopt_T *opt) + * Get the option entries from "dict", and parse them. + * If an option value is invalid return FAIL. + */ + static int +get_job_options(dict_T *dict, jobopt_T *opt) { dictitem_T *item; char_u *mode; + if (dict == NULL) + return OK; + if ((item = dict_find(dict, (char_u *)"mode", -1)) != NULL) { mode = get_tv_string(&item->di_tv); @@ -9956,6 +9959,17 @@ get_mode_arg(dict_T *dict, jobopt_T *opt return FAIL; } } + + if ((item = dict_find(dict, (char_u *)"callback", -1)) != NULL) + { + opt->jo_callback = get_callback(&item->di_tv); + if (opt->jo_callback == NULL) + { + EMSG2(_(e_invarg2), "callback"); + return FAIL; + } + } + return OK; } @@ -9966,7 +9980,6 @@ get_mode_arg(dict_T *dict, jobopt_T *opt f_ch_open(typval_T *argvars, typval_T *rettv) { char_u *address; - char_u *callback = NULL; char_u *p; char *rest; int port; @@ -10004,20 +10017,19 @@ f_ch_open(typval_T *argvars, typval_T *r } options.jo_mode = MODE_JSON; + options.jo_callback = NULL; if (argvars[1].v_type == VAR_DICT) { dict_T *dict = argvars[1].vval.v_dict; dictitem_T *item; /* parse argdict */ - if (get_mode_arg(dict, &options) == FAIL) + if (get_job_options(dict, &options) == FAIL) return; if ((item = dict_find(dict, (char_u *)"waittime", -1)) != NULL) waittime = get_tv_number(&item->di_tv); if ((item = dict_find(dict, (char_u *)"timeout", -1)) != NULL) timeout = get_tv_number(&item->di_tv); - if ((item = dict_find(dict, (char_u *)"callback", -1)) != NULL) - callback = get_callback(&item->di_tv); } if (waittime < 0 || timeout < 0) { @@ -10029,10 +10041,8 @@ f_ch_open(typval_T *argvars, typval_T *r if (channel != NULL) { rettv->vval.v_channel = channel; - channel_set_mode(channel, options.jo_mode); + channel_set_options(channel, &options); channel_set_timeout(channel, timeout); - if (callback != NULL && *callback != NUL) - channel_set_callback(channel, callback); } } @@ -10082,6 +10092,7 @@ send_common(typval_T *argvars, char_u *t { channel_T *channel; char_u *callback = NULL; + jobopt_T options; channel = get_channel_arg(&argvars[0]); if (channel == NULL) @@ -10089,9 +10100,15 @@ send_common(typval_T *argvars, char_u *t if (argvars[2].v_type != VAR_UNKNOWN) { - callback = get_callback(&argvars[2]); - if (callback == NULL) + if (argvars[2].v_type != VAR_DICT) + { + EMSG(_(e_invarg)); return NULL; + } + options.jo_callback = NULL; + if (get_job_options(argvars[2].vval.v_dict, &options) == FAIL) + return NULL; + callback = options.jo_callback; } /* Set the callback. An empty callback means no callback and not reading * the response. */ @@ -14511,17 +14528,15 @@ f_job_start(typval_T *argvars UNUSED, ty /* Default mode is NL. */ options.jo_mode = MODE_NL; + options.jo_callback = NULL; if (argvars[1].v_type != VAR_UNKNOWN) { - dict_T *dict; - if (argvars[1].v_type != VAR_DICT) { EMSG(_(e_invarg)); return; } - dict = argvars[1].vval.v_dict; - if (get_mode_arg(dict, &options) == FAIL) + if (get_job_options(argvars[1].vval.v_dict, &options) == FAIL) return; } diff --git a/src/os_unix.c b/src/os_unix.c --- a/src/os_unix.c +++ b/src/os_unix.c @@ -5127,7 +5127,7 @@ mch_start_job(char **argv, job_T *job, j # ifdef FEAT_CHANNEL channel_set_pipes(channel, fd_in[1], fd_out[0], fd_err[0]); channel_set_job(channel, job); - channel_set_mode(channel, options->jo_mode); + channel_set_options(channel, options); # ifdef FEAT_GUI channel_gui_register(channel); # endif diff --git a/src/os_win32.c b/src/os_win32.c --- a/src/os_win32.c +++ b/src/os_win32.c @@ -5125,7 +5125,7 @@ mch_start_job(char *cmd, job_T *job, job job->jv_channel = channel; channel_set_pipes(channel, (sock_T)ifd[1], (sock_T)ofd[0], (sock_T)efd[0]); channel_set_job(channel, job); - channel_set_mode(channel, options->jo_mode); + channel_set_options(channel, options); # ifdef FEAT_GUI channel_gui_register(channel); diff --git a/src/proto/channel.pro b/src/proto/channel.pro --- a/src/proto/channel.pro +++ b/src/proto/channel.pro @@ -7,9 +7,10 @@ void channel_gui_register_all(void); channel_T *channel_open(char *hostname, int port_in, int waittime, void (*close_cb)(void)); void channel_set_pipes(channel_T *channel, sock_T in, sock_T out, sock_T err); void channel_set_job(channel_T *channel, job_T *job); -void channel_set_mode(channel_T *channel, ch_mode_T ch_mode); +void channel_set_mode(channel_T *channel, ch_mode_T mode); void channel_set_timeout(channel_T *channel, int timeout); void channel_set_callback(channel_T *channel, char_u *callback); +void channel_set_options(channel_T *channel, jobopt_T *options); void channel_set_req_callback(channel_T *channel, char_u *callback, int id); char_u *channel_get(channel_T *channel); int channel_collapse(channel_T *channel); diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -1373,11 +1373,12 @@ struct channel_S { }; /* - * Options for job commands. + * Options for job and channel commands. */ typedef struct { - ch_mode_T jo_mode; + ch_mode_T jo_mode; /* "mode" */ + char_u *jo_callback; /* "callback", not allocated! */ } jobopt_T; 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 @@ -117,7 +117,7 @@ func s:communicate(port) call assert_equal('added more', getline('$')) " Send a request with a specific handler. - call ch_sendexpr(handle, 'hello!', 's:RequestHandler') + call ch_sendexpr(handle, 'hello!', {'callback': 's:RequestHandler'}) sleep 10m if !exists('s:responseHandle') call assert_false(1, 's:responseHandle was not set') @@ -128,7 +128,7 @@ func s:communicate(port) unlet s:responseHandle let s:responseMsg = '' - call ch_sendexpr(handle, 'hello!', function('s:RequestHandler')) + call ch_sendexpr(handle, 'hello!', {'callback': function('s:RequestHandler')}) sleep 10m if !exists('s:responseHandle') call assert_false(1, 's:responseHandle was not set') @@ -171,7 +171,7 @@ func s:communicate(port) call assert_equal('ok', ch_sendexpr(handle, 'empty-request')) " make the server quit, can't check if this works, should not hang. - call ch_sendexpr(handle, '!quit!', 0) + call ch_sendexpr(handle, '!quit!', {'callback': 0}) endfunc func Test_communicate() @@ -242,7 +242,7 @@ func s:channel_handler(port) call assert_equal('we called you', s:reply) " Test that it works while not waiting on a numbered message. - call ch_sendexpr(handle, 'call me again', 0) + call ch_sendexpr(handle, 'call me again', {'callback': 0}) sleep 10m call assert_equal('we did call you', s:reply) endfunc @@ -292,11 +292,11 @@ func Test_raw_pipe() call assert_equal("run", job_status(job)) try let handle = job_getchannel(job) - call ch_sendraw(handle, "echo something\n", 0) + call ch_sendraw(handle, "echo something\n", {'callback': 0}) let msg = ch_readraw(handle) call assert_equal("something\n", substitute(msg, "\r", "", 'g')) - call ch_sendraw(handle, "double this\n", 0) + call ch_sendraw(handle, "double this\n", {'callback': 0}) let msg = ch_readraw(handle) call assert_equal("this\nAND this\n", substitute(msg, "\r", "", 'g')) @@ -315,10 +315,10 @@ func Test_nl_pipe() call assert_equal("run", job_status(job)) try let handle = job_getchannel(job) - call ch_sendraw(handle, "echo something\n", 0) + call ch_sendraw(handle, "echo something\n", {'callback': 0}) call assert_equal("something", ch_readraw(handle)) - call ch_sendraw(handle, "double this\n", 0) + call ch_sendraw(handle, "double this\n", {'callback': 0}) call assert_equal("this", ch_readraw(handle)) call assert_equal("AND this", ch_readraw(handle)) @@ -340,7 +340,7 @@ endfunc " Test that "unlet handle" in a handler doesn't crash Vim. func s:unlet_handle(port) let s:channelfd = ch_open('localhost:' . a:port, s:chopt) - call ch_sendexpr(s:channelfd, "test", function('s:UnletHandler')) + call ch_sendexpr(s:channelfd, "test", {'callback': function('s:UnletHandler')}) sleep 10m call assert_equal('what?', s:unletResponse) endfunc @@ -360,7 +360,7 @@ endfunc " Test that "unlet handle" in a handler doesn't crash Vim. func s:close_handle(port) let s:channelfd = ch_open('localhost:' . a:port, s:chopt) - call ch_sendexpr(s:channelfd, "test", function('s:CloseHandler')) + call ch_sendexpr(s:channelfd, "test", {'callback': function('s:CloseHandler')}) sleep 10m call assert_equal('what?', s:unletResponse) endfunc diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -748,6 +748,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1341, +/**/ 1340, /**/ 1339,