forked from aniani/vim
patch 8.2.4780: parsing an LSP message fails when it is split
Problem: Parsing an LSP message fails when it is split. Solution: Collapse the received data before parsing. (Yegappan Lakshmanan, closes #10215)
This commit is contained in:
committed by
Bram Moolenaar
parent
53e8f3ffdf
commit
03cca297df
@@ -1433,11 +1433,17 @@ To open a channel using the 'lsp' mode with a job, set the 'in_mode' and
|
|||||||
let opts = {}
|
let opts = {}
|
||||||
let opts.in_mode = 'lsp'
|
let opts.in_mode = 'lsp'
|
||||||
let opts.out_mode = 'lsp'
|
let opts.out_mode = 'lsp'
|
||||||
|
let opts.err_mode = 'nl'
|
||||||
let opts.out_cb = function('LspOutCallback')
|
let opts.out_cb = function('LspOutCallback')
|
||||||
let opts.err_cb = function('LspErrCallback')
|
let opts.err_cb = function('LspErrCallback')
|
||||||
let opts.exit_cb = function('LspExitCallback')
|
let opts.exit_cb = function('LspExitCallback')
|
||||||
let job = job_start(cmd, opts)
|
let job = job_start(cmd, opts)
|
||||||
|
|
||||||
|
Note that if a job outputs LSP messages on stdout and non-LSP messages on
|
||||||
|
stderr, then the channel-callback function should handle both the message
|
||||||
|
formats appropriately or you should use a separate callback function for
|
||||||
|
"out_cb" and "err_cb" to handle them as shown above.
|
||||||
|
|
||||||
To synchronously send a JSON-RPC request to the server, use the
|
To synchronously send a JSON-RPC request to the server, use the
|
||||||
|ch_evalexpr()| function. This function will wait and return the decoded
|
|ch_evalexpr()| function. This function will wait and return the decoded
|
||||||
response message from the server. You can use either the |channel-timeout| or
|
response message from the server. You can use either the |channel-timeout| or
|
||||||
|
@@ -2035,22 +2035,24 @@ channel_consume(channel_T *channel, ch_part_T part, int len)
|
|||||||
int
|
int
|
||||||
channel_collapse(channel_T *channel, ch_part_T part, int want_nl)
|
channel_collapse(channel_T *channel, ch_part_T part, int want_nl)
|
||||||
{
|
{
|
||||||
readq_T *head = &channel->ch_part[part].ch_head;
|
ch_mode_T mode = channel->ch_part[part].ch_mode;
|
||||||
readq_T *node = head->rq_next;
|
readq_T *head = &channel->ch_part[part].ch_head;
|
||||||
readq_T *last_node;
|
readq_T *node = head->rq_next;
|
||||||
readq_T *n;
|
readq_T *last_node;
|
||||||
char_u *newbuf;
|
readq_T *n;
|
||||||
char_u *p;
|
char_u *newbuf;
|
||||||
long_u len;
|
char_u *p;
|
||||||
|
long_u len;
|
||||||
|
|
||||||
if (node == NULL || node->rq_next == NULL)
|
if (node == NULL || node->rq_next == NULL)
|
||||||
return FAIL;
|
return FAIL;
|
||||||
|
|
||||||
last_node = node->rq_next;
|
last_node = node->rq_next;
|
||||||
len = node->rq_buflen + last_node->rq_buflen;
|
len = node->rq_buflen + last_node->rq_buflen;
|
||||||
if (want_nl)
|
if (want_nl || mode == MODE_LSP)
|
||||||
while (last_node->rq_next != NULL
|
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;
|
last_node = last_node->rq_next;
|
||||||
len += last_node->rq_buflen;
|
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.
|
// Get any json message in the queue.
|
||||||
if (channel_get_json(channel, part, -1, FALSE, &listtv) == FAIL)
|
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.
|
// Parse readahead, return when there is still no message.
|
||||||
channel_parse_json(channel, part);
|
channel_parse_json(channel, part);
|
||||||
if (channel_get_json(channel, part, -1, FALSE, &listtv) == FAIL)
|
if (channel_get_json(channel, part, -1, FALSE, &listtv) == FAIL)
|
||||||
@@ -3974,6 +3982,7 @@ channel_read_json_block(
|
|||||||
sock_T fd;
|
sock_T fd;
|
||||||
int timeout;
|
int timeout;
|
||||||
chanpart_T *chanpart = &channel->ch_part[part];
|
chanpart_T *chanpart = &channel->ch_part[part];
|
||||||
|
ch_mode_T mode = channel->ch_part[part].ch_mode;
|
||||||
int retval = FAIL;
|
int retval = FAIL;
|
||||||
|
|
||||||
ch_log(channel, "Blocking read JSON for id %d", id);
|
ch_log(channel, "Blocking read JSON for id %d", id);
|
||||||
@@ -3984,6 +3993,12 @@ channel_read_json_block(
|
|||||||
|
|
||||||
for (;;)
|
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);
|
more = channel_parse_json(channel, part);
|
||||||
|
|
||||||
// search for message "id"
|
// search for message "id"
|
||||||
|
@@ -2580,6 +2580,11 @@ func LspTests(port)
|
|||||||
call assert_equal({'id': 14, 'jsonrpc': '2.0', 'result': 'extra-hdr-fields'},
|
call assert_equal({'id': 14, 'jsonrpc': '2.0', 'result': 'extra-hdr-fields'},
|
||||||
\ resp)
|
\ 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
|
" Test for processing a HTTP header without the Content-Length field
|
||||||
let resp = ch_evalexpr(ch, #{method: 'hdr-without-len', params: {}},
|
let resp = ch_evalexpr(ch, #{method: 'hdr-without-len', params: {}},
|
||||||
\ #{timeout: 200})
|
\ #{timeout: 200})
|
||||||
@@ -2629,13 +2634,6 @@ func LspTests(port)
|
|||||||
call assert_equal([], g:lspNotif)
|
call assert_equal([], g:lspNotif)
|
||||||
" Restore the callback function
|
" Restore the callback function
|
||||||
call ch_setoptions(ch, #{callback: 'LspCb'})
|
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
|
" " Test for sending a raw message
|
||||||
" let g:lspNotif = []
|
" let g:lspNotif = []
|
||||||
|
@@ -73,6 +73,18 @@ class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):
|
|||||||
resp += s
|
resp += s
|
||||||
self.request.sendall(resp.encode('utf-8'))
|
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):
|
def send_hdr_without_len(self, msgid, resp_dict):
|
||||||
# test for sending the http header without length
|
# test for sending the http header without length
|
||||||
v = {'jsonrpc': '2.0', 'id': msgid, 'result': resp_dict}
|
v = {'jsonrpc': '2.0', 'id': msgid, 'result': resp_dict}
|
||||||
@@ -152,6 +164,9 @@ class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):
|
|||||||
def do_extra_hdr_fields(self, payload):
|
def do_extra_hdr_fields(self, payload):
|
||||||
self.send_extra_hdr_fields(payload['id'], 'extra-hdr-fields')
|
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):
|
def do_hdr_without_len(self, payload):
|
||||||
self.send_hdr_without_len(payload['id'], 'hdr-without-len')
|
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,
|
'msg-specifc-cb': self.do_msg_specific_cb,
|
||||||
'server-req': self.do_server_req,
|
'server-req': self.do_server_req,
|
||||||
'extra-hdr-fields': self.do_extra_hdr_fields,
|
'extra-hdr-fields': self.do_extra_hdr_fields,
|
||||||
|
'delayed-payload': self.do_delayad_payload,
|
||||||
'hdr-without-len': self.do_hdr_without_len,
|
'hdr-without-len': self.do_hdr_without_len,
|
||||||
'hdr-with-wrong-len': self.do_hdr_with_wrong_len,
|
'hdr-with-wrong-len': self.do_hdr_with_wrong_len,
|
||||||
'hdr-with-negative-len': self.do_hdr_with_negative_len,
|
'hdr-with-negative-len': self.do_hdr_with_negative_len,
|
||||||
|
@@ -746,6 +746,8 @@ static char *(features[]) =
|
|||||||
|
|
||||||
static int included_patches[] =
|
static int included_patches[] =
|
||||||
{ /* Add new patch number below this line */
|
{ /* Add new patch number below this line */
|
||||||
|
/**/
|
||||||
|
4780,
|
||||||
/**/
|
/**/
|
||||||
4779,
|
4779,
|
||||||
/**/
|
/**/
|
||||||
|
Reference in New Issue
Block a user