mirror of
https://github.com/macvim-dev/macvim.git
synced 2026-06-07 15:37:14 +02:00
Merge remote-tracking branch 'vim/master'
This commit is contained in:
+26
-12
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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!'))
|
||||
|
||||
|
||||
@@ -757,6 +757,14 @@ static char *(features[]) =
|
||||
|
||||
static int included_patches[] =
|
||||
{ /* Add new patch number below this line */
|
||||
/**/
|
||||
1265,
|
||||
/**/
|
||||
1264,
|
||||
/**/
|
||||
1263,
|
||||
/**/
|
||||
1262,
|
||||
/**/
|
||||
1261,
|
||||
/**/
|
||||
|
||||
Reference in New Issue
Block a user