summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/channel.c83
-rw-r--r--src/eval.c14
-rw-r--r--src/proto/channel.pro2
-rw-r--r--src/testdir/test_channel.vim13
-rw-r--r--src/version.c2
5 files changed, 88 insertions, 26 deletions
diff --git a/src/channel.c b/src/channel.c
index 0ae90a963..7f40860fd 100644
--- a/src/channel.c
+++ b/src/channel.c
@@ -84,6 +84,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() */
@@ -106,7 +115,7 @@ 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 */
@@ -168,6 +177,8 @@ 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;
@@ -426,15 +437,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;
+ }
}
/*
@@ -599,6 +618,19 @@ channel_parse_json(int ch_idx)
/*
* 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.
*/
static void
@@ -628,8 +660,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);
@@ -742,9 +773,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;
@@ -804,17 +836,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 */
@@ -844,6 +886,7 @@ channel_close(int idx)
{
channel_T *channel = &channels[idx];
jsonq_T *jhead;
+ cbq_T *cbhead;
if (channel->ch_fd >= 0)
{
@@ -859,6 +902,10 @@ channel_close(int idx)
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)
{
diff --git a/src/eval.c b/src/eval.c
index f13486f22..9549ed85b 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -9800,7 +9800,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 +9815,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,7 +9845,7 @@ 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)
{
@@ -9883,7 +9883,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/proto/channel.pro b/src/proto/channel.pro
index 197cddf86..77040a50c 100644
--- a/src/proto/channel.pro
+++ b/src/proto/channel.pro
@@ -3,7 +3,7 @@ void channel_gui_register_all(void);
int channel_open(char *hostname, int port_in, void (*close_cb)(void));
void channel_set_json_mode(int idx, int json_mode);
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.vim b/src/testdir/test_channel.vim
index a81996153..e76c524ff 100644
--- a/src/testdir/test_channel.vim
+++ b/src/testdir/test_channel.vim
@@ -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,12 @@ func Test_communicate()
call assert_equal('added1', getline(line('$') - 1))
call assert_equal('added2', 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
diff --git a/src/version.c b/src/version.c
index 1fbc721af..f76e3c58e 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 */
/**/
+ 1262,
+/**/
1261,
/**/
1260,