To: vim_dev@googlegroups.com Subject: Patch 8.2.4780 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.4780 Problem: Parsing an LSP message fails when it is split. Solution: Collapse the received data before parsing. (Yegappan Lakshmanan, closes #10215) Files: runtime/doc/channel.txt, src/channel.c, src/testdir/test_channel.vim, src/testdir/test_channel_lsp.py *** ../vim-8.2.4779/runtime/doc/channel.txt 2022-04-16 15:18:19.428831638 +0100 --- runtime/doc/channel.txt 2022-04-18 13:52:35.229980150 +0100 *************** *** 1433,1443 **** --- 1433,1449 ---- let opts = {} let opts.in_mode = 'lsp' let opts.out_mode = 'lsp' + let opts.err_mode = 'nl' let opts.out_cb = function('LspOutCallback') let opts.err_cb = function('LspErrCallback') let opts.exit_cb = function('LspExitCallback') 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 |ch_evalexpr()| function. This function will wait and return the decoded response message from the server. You can use either the |channel-timeout| or *** ../vim-8.2.4779/src/channel.c 2022-04-17 13:17:36.616862238 +0100 --- src/channel.c 2022-04-18 14:04:56.926239053 +0100 *************** *** 2035,2056 **** 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; 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) while (last_node->rq_next != NULL ! && channel_first_nl(last_node) == NULL) { last_node = last_node->rq_next; len += last_node->rq_buflen; --- 2035,2058 ---- int channel_collapse(channel_T *channel, ch_part_T part, int want_nl) { ! 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 || mode == MODE_LSP) while (last_node->rq_next != NULL ! && (mode == MODE_LSP ! || channel_first_nl(last_node) == NULL)) { last_node = last_node->rq_next; len += last_node->rq_buflen; *************** *** 3006,3011 **** --- 3008,3019 ---- // 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,3979 **** --- 3982,3988 ---- 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,3989 **** --- 3993,4004 ---- 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" *** ../vim-8.2.4779/src/testdir/test_channel.vim 2022-04-16 10:40:59.081370588 +0100 --- src/testdir/test_channel.vim 2022-04-18 13:52:35.229980150 +0100 *************** *** 2580,2585 **** --- 2580,2590 ---- 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,2641 **** 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 = [] --- 2634,2639 ---- *** ../vim-8.2.4779/src/testdir/test_channel_lsp.py 2022-03-30 10:14:41.489657276 +0100 --- src/testdir/test_channel_lsp.py 2022-04-18 13:52:35.229980150 +0100 *************** *** 73,78 **** --- 73,90 ---- 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,157 **** --- 164,172 ---- 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,191 **** --- 201,207 ---- '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, *** ../vim-8.2.4779/src/version.c 2022-04-17 21:36:31.516919459 +0100 --- src/version.c 2022-04-18 14:05:47.422248201 +0100 *************** *** 748,749 **** --- 748,751 ---- { /* Add new patch number below this line */ + /**/ + 4780, /**/ -- hundred-and-one symptoms of being an internet addict: 24. You realize there is not a sound in the house and you have no idea where your children are. /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// \\\ \\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///