summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/channel.txt36
-rw-r--r--runtime/doc/eval.txt25
-rw-r--r--src/channel.c225
-rw-r--r--src/eval.c62
-rw-r--r--src/netbeans.c2
-rw-r--r--src/os_win32.c42
-rw-r--r--src/proto/channel.pro3
-rw-r--r--src/testdir/test_channel.vim4
-rw-r--r--src/version.c2
9 files changed, 287 insertions, 114 deletions
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,
+"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.
+
+"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 "raw" the "msg" argument is the whole message as a string.
-When {mode} is "json" the {callback} is optional. When omitted it is only
+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 49b7b0f989..c812d0a3d1 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 7f40860fd1..cab920c22b 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
@@ -119,6 +121,8 @@ typedef struct {
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;
/*
@@ -133,6 +137,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;
@@ -182,6 +228,8 @@ add_channel(void)
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++;
}
@@ -303,17 +351,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();
@@ -348,63 +398,121 @@ 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;
+ return -1;
+ }
+ }
- 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;
- }
- }
+ /* 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);
+ return -1;
}
- else
+ }
+
+ 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;
@@ -427,6 +535,15 @@ channel_set_json_mode(int idx, int 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".
*/
void
@@ -898,6 +1015,7 @@ 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));
@@ -1148,9 +1266,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);
}
@@ -1161,7 +1278,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)
@@ -1183,10 +1300,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 9549ed85b5..50b1b2aef9 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)
+ {
+ /* 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)
{
- EMSG2(_(e_invarg2), mode);
+ 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;
}
/*
diff --git a/src/netbeans.c b/src/netbeans.c
index 6f3c4e1a27..c25b7c6622 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 77040a50cc..f8e4a9b9f4 100644
--- a/src/proto/channel.pro
+++ b/src/proto/channel.pro
@@ -1,7 +1,8 @@
/* 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, int id);
char_u *channel_get(int idx);
diff --git a/src/testdir/test_channel.vim b/src/testdir/test_channel.vim
index e76c524ffa..0966101210 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
@@ -128,7 +128,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 f76e3c58eb..4f8f454e41 100644
--- a/src/version.c
+++ b/src/version.c
@@ -743,6 +743,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 1263,
+/**/
1262,
/**/
1261,