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 -- [email protected] -- http://www.Moolenaar.net   \\\
///                                                                      \\\
\\\        sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ ///
 \\\            help me help AIDS victims -- http://ICCF-Holland.org    ///

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_dev/20220418130826.A7D4B1C2577%40moolenaar.net.

Raspunde prin e-mail lui