Merge remote-tracking branch 'vim/master'

This commit is contained in:
Kazuki Sakamoto
2016-02-05 20:52:59 -08:00
10 changed files with 435 additions and 165 deletions
+26 -12
View File
@@ -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)
+16 -9
View File
@@ -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|.
+240 -81
View File
@@ -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);
}
+55 -37
View File
@@ -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);
}
+1 -1
View File
@@ -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 */
+25 -17
View File
@@ -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
+3 -2
View File
@@ -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);
+29 -4
View File
@@ -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.
+32 -2
View File
@@ -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!'))
+8
View File
@@ -757,6 +757,14 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
1265,
/**/
1264,
/**/
1263,
/**/
1262,
/**/
1261,
/**/