diff --git a/runtime/doc/channel.txt b/runtime/doc/channel.txt index 31b55cdf3a..0bc98f191e 100644 --- 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 04 +*channel.txt* For Vim version 7.4. Last change: 2016 Feb 05 VIM REFERENCE MANUAL by Bram Moolenaar @@ -32,7 +32,7 @@ $VIMRUNTIME/tools/demoserver.py Run it in one terminal. We will call this T1. Run Vim in another terminal. Connect to the demo server with: > - let handle = ch_open('localhost:8765', 'json') + let handle = ch_open('localhost:8765') In T1 you should see: === socket opened === ~ @@ -62,23 +62,25 @@ To handle asynchronous communication a callback needs to be used: > Instead of giving a callback with every send call, it can also be specified when opening the channel: > call ch_close(handle) - let handle = ch_open('localhost:8765', 'json', "MyHandler") + let handle = ch_open('localhost:8765', {'callback': "MyHandler"}) call ch_sendexpr(handle, 'hello!', 0) ============================================================================== 2. Opening a channel *channel-open* To open a channel: > - let handle = ch_open({address}, {mode}, {callback}) + let handle = ch_open({address} [, {argdict}]) {address} has the form "hostname:port". E.g., "localhost:8765". -{mode} can be: *channel-mode* - "json" - Use JSON, see below; most convenient way +{argdict} is a dictionary with optional entries: + +"mode" can be: *channel-mode* + "json" - Use JSON, see below; most convenient way. Default. "raw" - Use raw messages *channel-callback* -{callback} is a function that is called when a message is received that is not +"callback" is a function that is called when a message is received that is not handled otherwise. It gets two arguments: the channel handle and the received message. Example: > func Handle(handle, msg) @@ -86,16 +88,28 @@ message. Example: > endfunc let handle = ch_open("localhost:8765", 'json', "Handle") -When {mode} is "json" the "msg" argument is the body of the received message, -converted to Vim types. -When {mode} is "raw" the "msg" argument is the whole message as a string. +"waittime" is the time to wait for the connection to be made in milliseconds. +The default is zero, don't wait, which is useful if the server is supposed to +be running already. A negative number waits forever. -When {mode} is "json" the {callback} is optional. When omitted it is only +"timeout" is the time to wait for a request when blocking, using +ch_sendexpr(). Again in millisecons. The default si 2000 (2 seconds). + +When "mode" is "json" the "msg" argument is the body of the received message, +converted to Vim types. +When "mode" is "raw" the "msg" argument is the whole message as a string. + +When "mode" is "json" the "callback" is optional. When omitted it is only possible to receive a message after sending one. The handler can be added or changed later: > call ch_setcallback(handle, {callback}) -When {callback} is empty (zero or an empty string) the handler is removed. +When "callback is empty (zero or an empty string) the handler is removed. +NOT IMPLEMENTED YET + +The timeout can be changed later: > + call ch_settimeout(handle, {msec}) +NOT IMPLEMENTED YET Once done with the channel, disconnect it like this: > call ch_close(handle) diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 17706e2995..25905341f9 100644 --- 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 04 +*eval.txt* For Vim version 7.4. Last change: 2016 Feb 05 VIM REFERENCE MANUAL by Bram Moolenaar @@ -1811,8 +1811,7 @@ call( {func}, {arglist} [, {dict}]) any call {func} with arguments {arglist} ceil( {expr}) Float round {expr} up ch_close( {handle}) none close a channel -ch_open( {address}, {mode} [, {callback}]) - Number open a channel +ch_open( {address} [, {argdict})] Number open a channel to {address} ch_sendexpr( {handle}, {expr} [, {callback}]) any send {expr} over JSON channel {handle} ch_sendraw( {handle}, {string} [, {callback}]) @@ -2670,7 +2669,7 @@ confirm({msg} [, {choices} [, {default} [, {type}]]]) ch_close({handle}) *ch_close()* Close channel {handle}. See |channel|. -ch_open({address}, {mode} [, {callback}]) *ch_open()* +ch_open({address} [, {argdict}]) *ch_open()* Open a channel to {address}. See |channel|. Returns the channel handle on success. Returns a negative number for failure. @@ -2678,11 +2677,19 @@ ch_open({address}, {mode} [, {callback}]) *ch_open()* {address} has the form "hostname:port", e.g., "localhost:8765". - {mode} is either "json" or "raw". See |channel-mode| for the - meaning. - - {callback} is a function that handles received messages on the - channel. See |channel-callback|. + If {argdict} is given it must be a |Directory|. The optional + items are: + mode "raw" or "json". + Default "json". + callback function to call for requests with a zero + sequence number. See |channel-callback|. + Default: none. + waittime Specify connect timeout as milliseconds. + Negative means forever. + Default: 0. + timeout Specify response read timeout value as + milliseconds. + Default: 2000. ch_sendexpr({handle}, {expr} [, {callback}]) *ch_sendexpr()* Send {expr} over JSON channel {handle}. See |channel-use|. diff --git a/src/channel.c b/src/channel.c index 59e8a9c964..74c71466ee 100644 --- a/src/channel.c +++ b/src/channel.c @@ -42,6 +42,8 @@ static void chlog(int send, char_u *buf); # define SOCK_ERRNO errno = WSAGetLastError() # undef ECONNREFUSED # define ECONNREFUSED WSAECONNREFUSED +# undef EWOULDBLOCK +# define EWOULDBLOCK WSAEWOULDBLOCK # ifdef EINTR # undef EINTR # endif @@ -84,6 +86,15 @@ struct jsonqueue }; typedef struct jsonqueue jsonq_T; +struct cbqueue +{ + char_u *callback; + int seq_nr; + struct cbqueue *next; + struct cbqueue *prev; +}; +typedef struct cbqueue cbq_T; + typedef struct { sock_T ch_fd; /* the socket, -1 for a closed channel */ int ch_idx; /* used by channel_poll_setup() */ @@ -109,10 +120,12 @@ typedef struct { void (*ch_close_cb)(void); /* callback for when channel is closed */ char_u *ch_callback; /* function to call when a msg is not handled */ - char_u *ch_req_callback; /* function to call for current request */ + cbq_T ch_cb_head; /* dummy node for pre-request callbacks */ int ch_json_mode; /* TRUE for a json channel */ jsonq_T ch_json_head; /* dummy node, header for circular queue */ + + int ch_timeout; /* request timeout in msec */ } channel_T; /* @@ -127,6 +140,48 @@ static int channel_count = 0; */ FILE *debugfd = NULL; +#ifdef _WIN32 +# undef PERROR +# define PERROR(msg) (void)emsg3((char_u *)"%s: %s", \ + (char_u *)msg, (char_u *)strerror_win32(errno)) + + static char * +strerror_win32(int eno) +{ + static LPVOID msgbuf = NULL; + char_u *ptr; + + if (msgbuf) + LocalFree(msgbuf); + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + eno, + MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT), + (LPTSTR) &msgbuf, + 0, + NULL); + /* chomp \r or \n */ + for (ptr = (char_u *)msgbuf; *ptr; ptr++) + switch (*ptr) + { + case '\r': + STRMOVE(ptr, ptr + 1); + ptr--; + break; + case '\n': + if (*(ptr + 1) == '\0') + *ptr = '\0'; + else + *ptr = ' '; + break; + } + return msgbuf; +} +#endif + /* * Add a new channel slot, return the index. * The channel isn't actually used into ch_fd is set >= 0; @@ -174,9 +229,13 @@ add_channel(void) /* initialize circular queues */ ch->ch_head.next = &ch->ch_head; ch->ch_head.prev = &ch->ch_head; + ch->ch_cb_head.next = &ch->ch_cb_head; + ch->ch_cb_head.prev = &ch->ch_cb_head; ch->ch_json_head.next = &ch->ch_json_head; ch->ch_json_head.prev = &ch->ch_json_head; + ch->ch_timeout = 2000; + return channel_count++; } @@ -317,17 +376,19 @@ channel_gui_unregister(int idx) * Returns a negative number for failure. */ int -channel_open(char *hostname, int port_in, void (*close_cb)(void)) +channel_open(char *hostname, int port_in, int waittime, void (*close_cb)(void)) { int sd; struct sockaddr_in server; struct hostent * host; #ifdef WIN32 u_short port = port_in; + u_long val = 1; #else int port = port_in; #endif int idx; + int ret; #ifdef WIN32 channel_init_winsock(); @@ -362,58 +423,32 @@ channel_open(char *hostname, int port_in, void (*close_cb)(void)) } memcpy((char *)&server.sin_addr, host->h_addr, host->h_length); - /* Connect to server */ - if (connect(sd, (struct sockaddr *)&server, sizeof(server))) + if (waittime >= 0) { - SOCK_ERRNO; - CHERROR("channel_open: Connect failed with errno %d\n", errno); - if (errno == ECONNREFUSED) + /* Make connect non-blocking. */ + if ( +#ifdef _WIN32 + ioctlsocket(sd, FIONBIO, &val) < 0 +#else + fcntl(sd, F_SETFL, O_NONBLOCK) < 0 +#endif + ) { + SOCK_ERRNO; + CHERROR("channel_open: Connect failed with errno %d\n", errno); sock_close(sd); - if ((sd = (sock_T)socket(AF_INET, SOCK_STREAM, 0)) == (sock_T)-1) - { - SOCK_ERRNO; - CHERROR("socket() retry in channel_open()\n", ""); - PERROR("E900: socket() retry in channel_open()"); - return -1; - } - if (connect(sd, (struct sockaddr *)&server, sizeof(server))) - { - int retries = 36; - int success = FALSE; - - SOCK_ERRNO; - while (retries-- && ((errno == ECONNREFUSED) - || (errno == EINTR))) - { - CHERROR("retrying...\n", ""); - mch_delay(3000L, TRUE); - ui_breakcheck(); - if (got_int) - { - errno = EINTR; - break; - } - if (connect(sd, (struct sockaddr *)&server, - sizeof(server)) == 0) - { - success = TRUE; - break; - } - SOCK_ERRNO; - } - if (!success) - { - /* Get here when the server can't be found. */ - CHERROR("Cannot connect to port after retry\n", ""); - PERROR(_("E899: Cannot connect to port after retry2")); - sock_close(sd); - return -1; - } - } + return -1; } - else + } + + /* Try connecting to the server. */ + ret = connect(sd, (struct sockaddr *)&server, sizeof(server)); + SOCK_ERRNO; + if (ret < 0) + { + if (errno != EWOULDBLOCK && errno != EINPROGRESS) { + CHERROR("channel_open: Connect failed with errno %d\n", errno); CHERROR("Cannot connect to port\n", ""); PERROR(_("E902: Cannot connect to port")); sock_close(sd); @@ -421,6 +456,90 @@ channel_open(char *hostname, int port_in, void (*close_cb)(void)) } } + if (waittime >= 0) + { + struct timeval tv; + fd_set rfds, wfds; + + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_SET(sd, &rfds); + FD_SET(sd, &wfds); + tv.tv_sec = waittime; + tv.tv_usec = 0; + ret = select((int)sd+1, &rfds, &wfds, NULL, &tv); + if (ret < 0) + { + SOCK_ERRNO; + CHERROR("channel_open: Connect failed with errno %d\n", errno); + CHERROR("Cannot connect to port\n", ""); + PERROR(_("E902: Cannot connect to port")); + sock_close(sd); + return -1; + } + if (!FD_ISSET(sd, &rfds) && !FD_ISSET(sd, &wfds)) + { + errno = ECONNREFUSED; + CHERROR("Cannot connect to port\n", ""); + PERROR(_("E902: Cannot connect to port")); + sock_close(sd); + return -1; + } + +#ifdef _WIN32 + val = 0; + ioctlsocket(sd, FIONBIO, &val); +#else + fcntl(sd, F_SETFL, 0); +#endif + } + + if (errno == ECONNREFUSED) + { + sock_close(sd); + if ((sd = (sock_T)socket(AF_INET, SOCK_STREAM, 0)) == (sock_T)-1) + { + SOCK_ERRNO; + CHERROR("socket() retry in channel_open()\n", ""); + PERROR("E900: socket() retry in channel_open()"); + return -1; + } + if (connect(sd, (struct sockaddr *)&server, sizeof(server))) + { + int retries = 36; + int success = FALSE; + + SOCK_ERRNO; + while (retries-- && ((errno == ECONNREFUSED) + || (errno == EINTR))) + { + CHERROR("retrying...\n", ""); + mch_delay(3000L, TRUE); + ui_breakcheck(); + if (got_int) + { + errno = EINTR; + break; + } + if (connect(sd, (struct sockaddr *)&server, + sizeof(server)) == 0) + { + success = TRUE; + break; + } + SOCK_ERRNO; + } + if (!success) + { + /* Get here when the server can't be found. */ + CHERROR("Cannot connect to port after retry\n", ""); + PERROR(_("E899: Cannot connect to port after retry2")); + sock_close(sd); + return -1; + } + } + } + channels[idx].ch_fd = sd; channels[idx].ch_close_cb = close_cb; @@ -440,6 +559,15 @@ channel_set_json_mode(int idx, int json_mode) channels[idx].ch_json_mode = json_mode; } +/* + * Set the read timeout of channel "idx". + */ + void +channel_set_timeout(int idx, int timeout) +{ + channels[idx].ch_timeout = timeout; +} + /* * Set the callback for channel "idx". */ @@ -451,15 +579,23 @@ channel_set_callback(int idx, char_u *callback) } /* - * Set the callback for channel "idx" for the next response. + * Set the callback for channel "idx" for the response with "id". */ void -channel_set_req_callback(int idx, char_u *callback) +channel_set_req_callback(int idx, char_u *callback, int id) { - /* TODO: make a list of callbacks */ - vim_free(channels[idx].ch_req_callback); - channels[idx].ch_req_callback = callback == NULL - ? NULL : vim_strsave(callback); + cbq_T *cbhead = &channels[idx].ch_cb_head; + cbq_T *item = (cbq_T *)alloc((int)sizeof(cbq_T)); + + if (item != NULL) + { + item->callback = vim_strsave(callback); + item->seq_nr = id; + item->prev = cbhead->prev; + cbhead->prev = item; + item->next = cbhead; + item->prev->next = item; + } } /* @@ -577,7 +713,9 @@ channel_parse_json(int ch_idx) ret = json_decode(&reader, &listtv); if (ret == OK) { - if (listtv.v_type != VAR_LIST) + /* Only accept the response when it is a list with at least two + * items. */ + if (listtv.v_type != VAR_LIST || listtv.vval.v_list->lv_len < 2) { /* TODO: give error */ clear_tv(&listtv); @@ -622,6 +760,19 @@ channel_parse_json(int ch_idx) return ret; } +/* + * Remove "node" from the queue that it is in and free it. + * Also frees the contained callback name. + */ + static void +remove_cb_node(cbq_T *node) +{ + node->prev->next = node->next; + node->next->prev = node->prev; + vim_free(node->callback); + vim_free(node); +} + /* * Remove "node" from the queue that it is in and free it. * Caller should have freed or used node->value. @@ -653,8 +804,7 @@ channel_get_json(int ch_idx, int id, typval_T **rettv) typval_T *tv = &l->lv_first->li_tv; if ((id > 0 && tv->v_type == VAR_NUMBER && tv->vval.v_number == id) - || (id <= 0 - && (tv->v_type != VAR_NUMBER || tv->vval.v_number < 0))) + || id <= 0) { *rettv = item->value; remove_json_node(item); @@ -767,9 +917,10 @@ may_invoke_callback(int idx) typval_T *typetv; typval_T argv[3]; int seq_nr = -1; - int json_mode = channels[idx].ch_json_mode; + channel_T *channel = &channels[idx]; + int json_mode = channel->ch_json_mode; - if (channels[idx].ch_close_cb != NULL) + if (channel->ch_close_cb != NULL) /* this channel is handled elsewhere (netbeans) */ return FALSE; @@ -785,13 +936,6 @@ may_invoke_callback(int idx) } list = listtv->vval.v_list; - if (list->lv_len < 2) - { - /* TODO: give error */ - clear_tv(listtv); - return FALSE; - } - argv[1] = list->lv_first->li_next->li_tv; typetv = &list->lv_first->li_tv; if (typetv->v_type == VAR_STRING) @@ -829,17 +973,27 @@ may_invoke_callback(int idx) argv[1].vval.v_string = msg; } - if (channels[idx].ch_req_callback != NULL && seq_nr != 0) + if (seq_nr > 0) { - /* TODO: check the sequence number */ - /* invoke the one-time callback */ - invoke_callback(idx, channels[idx].ch_req_callback, argv); - channels[idx].ch_req_callback = NULL; + cbq_T *cbhead = &channel->ch_cb_head; + cbq_T *cbitem = cbhead->next; + + /* invoke the one-time callback with the matching nr */ + while (cbitem != cbhead) + { + if (cbitem->seq_nr == seq_nr) + { + invoke_callback(idx, cbitem->callback, argv); + remove_cb_node(cbitem); + break; + } + cbitem = cbitem->next; + } } - else if (channels[idx].ch_callback != NULL) + else if (channel->ch_callback != NULL) { /* invoke the channel callback */ - invoke_callback(idx, channels[idx].ch_callback, argv); + invoke_callback(idx, channel->ch_callback, argv); } /* else: drop the message TODO: give error */ @@ -869,6 +1023,7 @@ channel_close(int idx) { channel_T *channel = &channels[idx]; jsonq_T *jhead; + cbq_T *cbhead; if (channel->ch_fd >= 0) { @@ -880,10 +1035,15 @@ channel_close(int idx) #endif vim_free(channel->ch_callback); channel->ch_callback = NULL; + channel->ch_timeout = 2000; while (channel_peek(idx) != NULL) vim_free(channel_get(idx)); + cbhead = &channel->ch_cb_head; + while (cbhead->next != cbhead) + remove_cb_node(cbhead->next); + jhead = &channel->ch_json_head; while (jhead->next != jhead) { @@ -1126,9 +1286,8 @@ channel_read_block(int idx) { if (channel_peek(idx) == NULL) { - /* Wait for up to 2 seconds. - * TODO: use timeout set on the channel. */ - if (channel_wait(channels[idx].ch_fd, 2000) == FAIL) + /* Wait for up to the channel timeout. */ + if (channel_wait(channels[idx].ch_fd, channels[idx].ch_timeout) == FAIL) return NULL; channel_read(idx); } @@ -1139,7 +1298,7 @@ channel_read_block(int idx) /* * Read one JSON message from channel "ch_idx" with ID "id" and store the * result in "rettv". - * Blocks until the message is received. + * Blocks until the message is received or the timeout is reached. */ int channel_read_json_block(int ch_idx, int id, typval_T **rettv) @@ -1161,10 +1320,10 @@ channel_read_json_block(int ch_idx, int id, typval_T **rettv) if (channel_parse_messages()) continue; - /* Wait for up to 2 seconds. - * TODO: use timeout set on the channel. */ + /* Wait for up to the channel timeout. */ if (channels[ch_idx].ch_fd < 0 - || channel_wait(channels[ch_idx].ch_fd, 2000) == FAIL) + || channel_wait(channels[ch_idx].ch_fd, + channels[ch_idx].ch_timeout) == FAIL) break; channel_read(ch_idx); } diff --git a/src/eval.c b/src/eval.c index db7ac6e5cd..80f3faf09e 100644 --- a/src/eval.c +++ b/src/eval.c @@ -8005,7 +8005,7 @@ static struct fst #endif #ifdef FEAT_CHANNEL {"ch_close", 1, 1, f_ch_close}, - {"ch_open", 2, 3, f_ch_open}, + {"ch_open", 1, 2, f_ch_open}, {"ch_sendexpr", 2, 3, f_ch_sendexpr}, {"ch_sendraw", 2, 3, f_ch_sendraw}, #endif @@ -9743,21 +9743,23 @@ f_ch_open(typval_T *argvars, typval_T *rettv) char_u *address; char_u *mode; char_u *callback = NULL; - char_u buf1[NUMBUFLEN]; char_u *p; + char *rest; int port; - int json_mode = FALSE; + int waittime = 0; + int timeout = 2000; + int json_mode = TRUE; + int ch_idx; /* default: fail */ rettv->vval.v_number = -1; address = get_tv_string(&argvars[0]); - mode = get_tv_string_buf(&argvars[1], buf1); - if (argvars[2].v_type != VAR_UNKNOWN) + if (argvars[1].v_type != VAR_UNKNOWN + && (argvars[1].v_type != VAR_DICT || argvars[1].vval.v_dict == NULL)) { - callback = get_callback(&argvars[2]); - if (callback == NULL) - return; + EMSG(_(e_invarg)); + return; } /* parse address */ @@ -9768,30 +9770,52 @@ f_ch_open(typval_T *argvars, typval_T *rettv) return; } *p++ = NUL; - port = atoi((char *)p); - if (*address == NUL || port <= 0) + port = strtol((char *)p, &rest, 10); + if (*address == NUL || port <= 0 || *rest != NUL) { p[-1] = ':'; EMSG2(_(e_invarg2), address); return; } - /* parse mode */ - if (STRCMP(mode, "json") == 0) - json_mode = TRUE; - else if (STRCMP(mode, "raw") != 0) + if (argvars[1].v_type == VAR_DICT) { - EMSG2(_(e_invarg2), mode); + /* parse argdict */ + dict_T *dict = argvars[1].vval.v_dict; + + if (dict_find(dict, (char_u *)"mode", -1) != NULL) + { + mode = get_dict_string(dict, (char_u *)"mode", FALSE); + if (STRCMP(mode, "raw") == 0) + json_mode = FALSE; + else if (STRCMP(mode, "json") != 0) + { + EMSG2(_(e_invarg2), mode); + return; + } + } + if (dict_find(dict, (char_u *)"waittime", -1) != NULL) + waittime = get_dict_number(dict, (char_u *)"waittime"); + if (dict_find(dict, (char_u *)"timeout", -1) != NULL) + timeout = get_dict_number(dict, (char_u *)"timeout"); + if (dict_find(dict, (char_u *)"callback", -1) != NULL) + callback = get_dict_string(dict, (char_u *)"callback", FALSE); + } + if (waittime < 0 || timeout < 0) + { + EMSG(_(e_invarg)); return; } - rettv->vval.v_number = channel_open((char *)address, port, NULL); - if (rettv->vval.v_number >= 0) + ch_idx = channel_open((char *)address, port, waittime, NULL); + if (ch_idx >= 0) { - channel_set_json_mode(rettv->vval.v_number, json_mode); + channel_set_json_mode(ch_idx, json_mode); + channel_set_timeout(ch_idx, timeout); if (callback != NULL && *callback != NUL) - channel_set_callback(rettv->vval.v_number, callback); + channel_set_callback(ch_idx, callback); } + rettv->vval.v_number = ch_idx; } /* @@ -9800,7 +9824,7 @@ f_ch_open(typval_T *argvars, typval_T *rettv) * Otherwise returns -1. */ static int -send_common(typval_T *argvars, char_u *text, char *fun) +send_common(typval_T *argvars, char_u *text, int id, char *fun) { int ch_idx; char_u *callback = NULL; @@ -9815,10 +9839,10 @@ send_common(typval_T *argvars, char_u *text, char *fun) if (callback == NULL) return -1; } - /* Set the callback or clear it. An empty callback means no callback and - * not reading the response. */ - channel_set_req_callback(ch_idx, - callback != NULL && *callback == NUL ? NULL : callback); + /* Set the callback. An empty callback means no callback and not reading + * the response. */ + if (callback != NULL && *callback != NUL) + channel_set_req_callback(ch_idx, callback, id); if (channel_send(ch_idx, text, fun) == OK && callback == NULL) return ch_idx; @@ -9845,24 +9869,18 @@ f_ch_sendexpr(typval_T *argvars, typval_T *rettv) if (text == NULL) return; - ch_idx = send_common(argvars, text, "sendexpr"); + ch_idx = send_common(argvars, text, id, "sendexpr"); vim_free(text); if (ch_idx >= 0) { if (channel_read_json_block(ch_idx, id, &listtv) == OK) { - if (listtv->v_type == VAR_LIST) - { - list_T *list = listtv->vval.v_list; + list_T *list = listtv->vval.v_list; - if (list->lv_len == 2) - { - /* Move the item from the list and then change the type to - * avoid the value being freed. */ - *rettv = list->lv_last->li_tv; - list->lv_last->li_tv.v_type = VAR_NUMBER; - } - } + /* Move the item from the list and then change the type to + * avoid the value being freed. */ + *rettv = list->lv_last->li_tv; + list->lv_last->li_tv.v_type = VAR_NUMBER; clear_tv(listtv); } } @@ -9883,7 +9901,7 @@ f_ch_sendraw(typval_T *argvars, typval_T *rettv) rettv->vval.v_string = NULL; text = get_tv_string_buf(&argvars[1], buf); - ch_idx = send_common(argvars, text, "sendraw"); + ch_idx = send_common(argvars, text, 0, "sendraw"); if (ch_idx >= 0) rettv->vval.v_string = channel_read_block(ch_idx); } diff --git a/src/netbeans.c b/src/netbeans.c index c38d48988f..a3049c11d7 100644 --- a/src/netbeans.c +++ b/src/netbeans.c @@ -213,7 +213,7 @@ netbeans_connect(char *params, int doabort) if (hostname != NULL && address != NULL && password != NULL) { port = atoi(address); - nb_channel_idx = channel_open(hostname, port, nb_channel_closed); + nb_channel_idx = channel_open(hostname, port, 0, nb_channel_closed); if (nb_channel_idx >= 0) { /* success */ diff --git a/src/os_win32.c b/src/os_win32.c index fb13671884..db080e44ec 100644 --- a/src/os_win32.c +++ b/src/os_win32.c @@ -1123,6 +1123,29 @@ mch_setmouse(int on) SetConsoleMode(g_hConIn, cmodein); } +#ifdef FEAT_CHANNEL + static int +handle_channel_event(void) +{ + int ret; + fd_set rfds; + int maxfd; + + FD_ZERO(&rfds); + maxfd = channel_select_setup(-1, &rfds); + if (maxfd >= 0) + { + struct timeval tv; + + tv.tv_sec = 0; + tv.tv_usec = 0; + ret = select(maxfd + 1, &rfds, NULL, NULL, &tv); + if (ret > 0 && channel_select_check(ret, &rfds) > 0) + return TRUE; + } + return FALSE; +} +#endif /* * Decode a MOUSE_EVENT. If it's a valid event, return MOUSE_LEFT, @@ -1443,11 +1466,6 @@ WaitForChar(long msec) INPUT_RECORD ir; DWORD cRecords; WCHAR ch, ch2; -#ifdef FEAT_CHANNEL - int ret; - fd_set rfds; - int maxfd; -#endif if (msec > 0) /* Wait until the specified time has elapsed. */ @@ -1472,18 +1490,8 @@ WaitForChar(long msec) #endif #ifdef FEAT_CHANNEL - FD_ZERO(&rfds); - maxfd = channel_select_setup(-1, &rfds); - if (maxfd >= 0) - { - struct timeval tv; - - tv.tv_sec = 0; - tv.tv_usec = 0; - ret = select(maxfd + 1, &rfds, NULL, NULL, &tv); - if (ret > 0 && channel_select_check(ret, &rfds) > 0) - return TRUE; - } + if (handle_channel_event()) + return TRUE; #endif if (0 diff --git a/src/proto/channel.pro b/src/proto/channel.pro index 197cddf868..f8e4a9b9f4 100644 --- a/src/proto/channel.pro +++ b/src/proto/channel.pro @@ -1,9 +1,10 @@ /* channel.c */ void channel_gui_register_all(void); -int channel_open(char *hostname, int port_in, void (*close_cb)(void)); +int channel_open(char *hostname, int port_in, int waittime, void (*close_cb)(void)); void channel_set_json_mode(int idx, int json_mode); +void channel_set_timeout(int idx, int timeout); void channel_set_callback(int idx, char_u *callback); -void channel_set_req_callback(int idx, char_u *callback); +void channel_set_req_callback(int idx, char_u *callback, int id); char_u *channel_get(int idx); int channel_collapse(int idx); int channel_is_open(int idx); diff --git a/src/testdir/test_channel.py b/src/testdir/test_channel.py index fb75938c1f..d8830c519f 100644 --- a/src/testdir/test_channel.py +++ b/src/testdir/test_channel.py @@ -75,6 +75,12 @@ class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler): print("sending: {}".format(cmd)) self.request.sendall(cmd.encode('utf-8')) response = "ok" + elif decoded[1] == 'do normal': + # Send a normal command. + cmd = '["normal","G$s more\u001b"]' + print("sending: {}".format(cmd)) + self.request.sendall(cmd.encode('utf-8')) + response = "ok" elif decoded[1] == 'eval-works': # Send an eval request. We ignore the response. cmd = '["eval","\\"foo\\" . 123", -1]' @@ -93,6 +99,27 @@ class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler): print("sending: {}".format(cmd)) self.request.sendall(cmd.encode('utf-8')) response = "ok" + elif decoded[1] == 'an expr': + # Send an expr request. + cmd = '["expr","setline(\\"$\\", [\\"one\\",\\"two\\",\\"three\\"])"]' + print("sending: {}".format(cmd)) + self.request.sendall(cmd.encode('utf-8')) + response = "ok" + elif decoded[1] == 'redraw': + cmd = '["redraw",""]' + print("sending: {}".format(cmd)) + self.request.sendall(cmd.encode('utf-8')) + response = "ok" + elif decoded[1] == 'redraw!': + cmd = '["redraw","force"]' + print("sending: {}".format(cmd)) + self.request.sendall(cmd.encode('utf-8')) + response = "ok" + elif decoded[1] == 'empty-request': + cmd = '[]' + print("sending: {}".format(cmd)) + self.request.sendall(cmd.encode('utf-8')) + response = "ok" elif decoded[1] == 'eval-result': # Send back the last received eval result. response = last_eval @@ -123,11 +150,9 @@ if __name__ == "__main__": server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler) ip, port = server.server_address - # Start a thread with the server -- that thread will then start one - # more thread for each request + # Start a thread with the server. That thread will then start a new thread + # for each connection. server_thread = threading.Thread(target=server.serve_forever) - - # Exit the server thread when the main thread terminates server_thread.start() # Write the port number in Xportnr, so that the test knows it. diff --git a/src/testdir/test_channel.vim b/src/testdir/test_channel.vim index a819961536..46bd364311 100644 --- a/src/testdir/test_channel.vim +++ b/src/testdir/test_channel.vim @@ -57,7 +57,7 @@ func s:start_server() endif let s:port = l[0] - let handle = ch_open('localhost:' . s:port, 'json') + let handle = ch_open('localhost:' . s:port) return handle endfunc @@ -69,6 +69,13 @@ func s:kill_server() endif endfunc +let s:responseHandle = -1 +let s:responseMsg = '' +func s:RequestHandler(handle, msg) + let s:responseHandle = a:handle + let s:responseMsg = a:msg +endfunc + func Test_communicate() let handle = s:start_server() if handle < 0 @@ -86,6 +93,16 @@ func Test_communicate() call assert_equal('added1', getline(line('$') - 1)) call assert_equal('added2', getline('$')) + call assert_equal('ok', ch_sendexpr(handle, 'do normal')) + sleep 10m + call assert_equal('added more', getline('$')) + + " Send a request with a specific handler. + call ch_sendexpr(handle, 'hello!', 's:RequestHandler') + sleep 10m + call assert_equal(handle, s:responseHandle) + call assert_equal('got it', s:responseMsg) + " Send an eval request that works. call assert_equal('ok', ch_sendexpr(handle, 'eval-works')) sleep 10m @@ -101,6 +118,19 @@ func Test_communicate() sleep 10m call assert_equal([-2, 'ERROR'], ch_sendexpr(handle, 'eval-result')) + " Send an expr request + call assert_equal('ok', ch_sendexpr(handle, 'an expr')) + sleep 10m + call assert_equal('one', getline(line('$') - 2)) + call assert_equal('two', getline(line('$') - 1)) + call assert_equal('three', getline('$')) + + " Request a redraw, we don't check for the effect. + call assert_equal('ok', ch_sendexpr(handle, 'redraw')) + call assert_equal('ok', ch_sendexpr(handle, 'redraw!')) + + 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) @@ -115,7 +145,7 @@ func Test_two_channels() endif call assert_equal('got it', ch_sendexpr(handle, 'hello!')) - let newhandle = ch_open('localhost:' . s:port, 'json') + let newhandle = ch_open('localhost:' . s:port) call assert_equal('got it', ch_sendexpr(newhandle, 'hello!')) call assert_equal('got it', ch_sendexpr(handle, 'hello!')) diff --git a/src/version.c b/src/version.c index b75d5f2623..5ecf8509e6 100644 --- a/src/version.c +++ b/src/version.c @@ -757,6 +757,14 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1265, +/**/ + 1264, +/**/ + 1263, +/**/ + 1262, /**/ 1261, /**/