summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorYegappan Lakshmanan <yegappan@yahoo.com>2022-04-18 14:07:46 +0100
committerBram Moolenaar <Bram@vim.org>2022-04-18 14:07:46 +0100
commit03cca297df5210f94be2246cfdb1ee9a30454bea (patch)
treecc2f525d9f41ad11e2d0520c0c7b0d8d557455f8 /src
parent53e8f3ffdf80dbd24a60adb51f8f21982fd41c57 (diff)
downloadvim-git-03cca297df5210f94be2246cfdb1ee9a30454bea.tar.gz
patch 8.2.4780: parsing an LSP message fails when it is splitv8.2.4780
Problem: Parsing an LSP message fails when it is split. Solution: Collapse the received data before parsing. (Yegappan Lakshmanan, closes #10215)
Diffstat (limited to 'src')
-rw-r--r--src/channel.c33
-rw-r--r--src/testdir/test_channel.vim12
-rw-r--r--src/testdir/test_channel_lsp.py16
-rw-r--r--src/version.c2
4 files changed, 47 insertions, 16 deletions
diff --git a/src/channel.c b/src/channel.c
index 79f4dbc29..b981af400 100644
--- a/src/channel.c
+++ b/src/channel.c
@@ -2035,22 +2035,24 @@ channel_consume(channel_T *channel, ch_part_T part, int len)
int
channel_collapse(channel_T *channel, ch_part_T part, int want_nl)
{
- readq_T *head = &channel->ch_part[part].ch_head;
- readq_T *node = head->rq_next;
- readq_T *last_node;
- readq_T *n;
- char_u *newbuf;
- char_u *p;
- long_u len;
+ ch_mode_T mode = channel->ch_part[part].ch_mode;
+ readq_T *head = &channel->ch_part[part].ch_head;
+ readq_T *node = head->rq_next;
+ readq_T *last_node;
+ readq_T *n;
+ char_u *newbuf;
+ char_u *p;
+ long_u len;
if (node == NULL || node->rq_next == NULL)
return FAIL;
last_node = node->rq_next;
len = node->rq_buflen + last_node->rq_buflen;
- if (want_nl)
+ if (want_nl || mode == MODE_LSP)
while (last_node->rq_next != NULL
- && channel_first_nl(last_node) == NULL)
+ && (mode == MODE_LSP
+ || channel_first_nl(last_node) == NULL))
{
last_node = last_node->rq_next;
len += last_node->rq_buflen;
@@ -3006,6 +3008,12 @@ may_invoke_callback(channel_T *channel, ch_part_T part)
// Get any json message in the queue.
if (channel_get_json(channel, part, -1, FALSE, &listtv) == FAIL)
{
+ if (ch_mode == MODE_LSP)
+ // In the "lsp" mode, the http header and the json payload may
+ // be received in multiple messages. So concatenate all the
+ // received messages.
+ (void)channel_collapse(channel, part, FALSE);
+
// Parse readahead, return when there is still no message.
channel_parse_json(channel, part);
if (channel_get_json(channel, part, -1, FALSE, &listtv) == FAIL)
@@ -3974,6 +3982,7 @@ channel_read_json_block(
sock_T fd;
int timeout;
chanpart_T *chanpart = &channel->ch_part[part];
+ ch_mode_T mode = channel->ch_part[part].ch_mode;
int retval = FAIL;
ch_log(channel, "Blocking read JSON for id %d", id);
@@ -3984,6 +3993,12 @@ channel_read_json_block(
for (;;)
{
+ if (mode == MODE_LSP)
+ // In the "lsp" mode, the http header and the json payload may be
+ // received in multiple messages. So concatenate all the received
+ // messages.
+ (void)channel_collapse(channel, part, FALSE);
+
more = channel_parse_json(channel, part);
// search for message "id"
diff --git a/src/testdir/test_channel.vim b/src/testdir/test_channel.vim
index e270a4be7..6fef421e5 100644
--- a/src/testdir/test_channel.vim
+++ b/src/testdir/test_channel.vim
@@ -2580,6 +2580,11 @@ func LspTests(port)
call assert_equal({'id': 14, 'jsonrpc': '2.0', 'result': 'extra-hdr-fields'},
\ resp)
+ " Test for processing delayed payload
+ let resp = ch_evalexpr(ch, #{method: 'delayed-payload', params: {}})
+ call assert_equal({'id': 15, 'jsonrpc': '2.0', 'result': 'delayed-payload'},
+ \ resp)
+
" Test for processing a HTTP header without the Content-Length field
let resp = ch_evalexpr(ch, #{method: 'hdr-without-len', params: {}},
\ #{timeout: 200})
@@ -2629,13 +2634,6 @@ func LspTests(port)
call assert_equal([], g:lspNotif)
" Restore the callback function
call ch_setoptions(ch, #{callback: 'LspCb'})
- let g:lspNotif = []
- call ch_sendexpr(ch, #{method: 'echo', params: #{s: 'no-callback'}})
- " Send a ping to wait for all the notification messages to arrive
- call assert_equal('alive', ch_evalexpr(ch, #{method: 'ping'}).result)
- call assert_equal([#{jsonrpc: '2.0', result:
- \ #{method: 'echo', jsonrpc: '2.0', params: #{s: 'no-callback'}}}],
- \ g:lspNotif)
" " Test for sending a raw message
" let g:lspNotif = []
diff --git a/src/testdir/test_channel_lsp.py b/src/testdir/test_channel_lsp.py
index 530258d84..fb8ed2243 100644
--- a/src/testdir/test_channel_lsp.py
+++ b/src/testdir/test_channel_lsp.py
@@ -73,6 +73,18 @@ class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):
resp += s
self.request.sendall(resp.encode('utf-8'))
+ def send_delayed_payload(self, msgid, resp_dict):
+ # test for sending the hdr first and then after some delay, send the
+ # payload
+ v = {'jsonrpc': '2.0', 'id': msgid, 'result': resp_dict}
+ s = json.dumps(v)
+ resp = "Content-Length: " + str(len(s)) + "\r\n"
+ resp += "\r\n"
+ self.request.sendall(resp.encode('utf-8'))
+ time.sleep(0.05)
+ resp = s
+ self.request.sendall(resp.encode('utf-8'))
+
def send_hdr_without_len(self, msgid, resp_dict):
# test for sending the http header without length
v = {'jsonrpc': '2.0', 'id': msgid, 'result': resp_dict}
@@ -152,6 +164,9 @@ class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):
def do_extra_hdr_fields(self, payload):
self.send_extra_hdr_fields(payload['id'], 'extra-hdr-fields')
+ def do_delayad_payload(self, payload):
+ self.send_delayed_payload(payload['id'], 'delayed-payload')
+
def do_hdr_without_len(self, payload):
self.send_hdr_without_len(payload['id'], 'hdr-without-len')
@@ -186,6 +201,7 @@ class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):
'msg-specifc-cb': self.do_msg_specific_cb,
'server-req': self.do_server_req,
'extra-hdr-fields': self.do_extra_hdr_fields,
+ 'delayed-payload': self.do_delayad_payload,
'hdr-without-len': self.do_hdr_without_len,
'hdr-with-wrong-len': self.do_hdr_with_wrong_len,
'hdr-with-negative-len': self.do_hdr_with_negative_len,
diff --git a/src/version.c b/src/version.c
index efc010432..fd461229f 100644
--- a/src/version.c
+++ b/src/version.c
@@ -747,6 +747,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 4780,
+/**/
4779,
/**/
4778,