# HG changeset patch # User Christian Brabandt # Date 1695307506 -7200 # Node ID 41e2414d2886b0f8c19d283f180646ab378a11eb # Parent e81313a3aacb1b64b85c8ca23b4ab52d99549767 patch 9.0.1924: LSP server message still wrongly handled (after 9.0.1922) Commit: https://github.com/vim/vim/commit/1926ae41845c3b6e2045b29225365c8a2e4eb1da Author: Yegappan Lakshmanan Date: Thu Sep 21 16:36:28 2023 +0200 patch 9.0.1924: LSP server message still wrongly handled (after 9.0.1922) Problem: LSP server message still wrongly handled (after 9.0.1922) Solution: Handle 'method' messages properly, don't discard them, add tests. closes: #13141 Signed-off-by: Christian Brabandt Co-authored-by: Yegappan Lakshmanan diff --git a/src/channel.c b/src/channel.c --- a/src/channel.c +++ b/src/channel.c @@ -2466,6 +2466,12 @@ channel_get_json( d = item->jq_value->vval.v_dict; if (d == NULL) goto nextitem; + // When looking for a response message from the LSP server, + // ignore new LSP request and notification messages.  LSP + // request and notification messages have the "method" field in + // the header and the response messages do not have this field. + if (dict_has_key(d, "method")) + goto nextitem; di = dict_find(d, (char_u *)"id", -1); if (di == NULL) goto nextitem; @@ -2927,16 +2933,9 @@ may_invoke_callback(channel_T *channel, seq_nr = 0; if (d != NULL) { - // When looking for a response message from the LSP server, - // ignore new LSP request and notification messages. LSP - // request and notification messages have the "method" field in - // the header and the response messages do not have this field. - if (!dict_has_key(d, "method")) - { - di = dict_find(d, (char_u *)"id", -1); - if (di != NULL && di->di_tv.v_type == VAR_NUMBER) - seq_nr = di->di_tv.vval.v_number; - } + di = dict_find(d, (char_u *)"id", -1); + if (di != NULL && di->di_tv.v_type == VAR_NUMBER) + seq_nr = di->di_tv.vval.v_number; } argv[1] = *listtv; diff --git a/src/optionstr.c b/src/optionstr.c --- a/src/optionstr.c +++ b/src/optionstr.c @@ -1858,7 +1858,7 @@ did_set_isopt(optset_T *args) * The 'jumpoptions' option is changed. */ char * -did_set_jumpoptions(optset_T *args) +did_set_jumpoptions(optset_T *args UNUSED) { if (opt_strings_flags(p_jop, p_jop_values, &jop_flags, TRUE) != OK) return e_invalid_argument; diff --git a/src/testdir/test_channel.vim b/src/testdir/test_channel.vim --- a/src/testdir/test_channel.vim +++ b/src/testdir/test_channel.vim @@ -2481,6 +2481,14 @@ endfunc " Test for the 'lsp' channel mode func LspCb(chan, msg) call add(g:lspNotif, a:msg) + if a:msg->has_key('method') + " Requests received from the LSP server + if a:msg['method'] == 'server-req-in-middle' + \ && a:msg['params']['text'] == 'server-req' + call ch_sendexpr(a:chan, #{method: 'server-req-in-middle-resp', + \ id: a:msg['id'], params: #{text: 'client-resp'}}) + endif + endif endfunc func LspOtCb(chan, msg) @@ -2652,6 +2660,19 @@ func LspTests(port) " send a ping to make sure communication still works call assert_equal('alive', ch_evalexpr(ch, #{method: 'ping'}).result) + " Test for processing a request message from the server while the client + " is waiting for a response with the same identifier. + let g:lspNotif = [] + let resp = ch_evalexpr(ch, #{method: 'server-req-in-middle', + \ params: #{text: 'client-req'}}) + call assert_equal(#{jsonrpc: '2.0', id: 28, + \ result: #{text: 'server-resp'}}, resp) + call assert_equal([ + \ #{id: -1, jsonrpc: '2.0', method: 'server-req-in-middle', + \ params: #{text: 'server-notif'}}, + \ #{id: 28, jsonrpc: '2.0', method: 'server-req-in-middle', + \ params: #{text: 'server-req'}}], g:lspNotif) + " Test for invoking an unsupported method let resp = ch_evalexpr(ch, #{method: 'xyz', params: {}}, #{timeout: 200}) call assert_equal({}, resp) diff --git a/src/testdir/test_channel_lsp.py b/src/testdir/test_channel_lsp.py --- a/src/testdir/test_channel_lsp.py +++ b/src/testdir/test_channel_lsp.py @@ -29,7 +29,20 @@ class ThreadedTCPRequestHandler(socketse with open("Xlspserver.log", "a") as myfile: myfile.write(msg) - def send_lsp_msg(self, msgid, resp_dict): + def send_lsp_req(self, msgid, method, params): + v = {'jsonrpc': '2.0', 'id': msgid, 'method': method} + if len(params) != 0: + v['params'] = params + s = json.dumps(v) + req = "Content-Length: " + str(len(s)) + "\r\n" + req += "Content-Type: application/vscode-jsonrpc; charset=utf-8\r\n" + req += "\r\n" + req += s + if self.debug: + self.debuglog("SEND: ({0} bytes) '{1}'\n".format(len(req), req)) + self.request.sendall(req.encode('utf-8')) + + def send_lsp_resp(self, msgid, resp_dict): v = {'jsonrpc': '2.0', 'result': resp_dict} if msgid != -1: v['id'] = msgid @@ -118,56 +131,56 @@ class ThreadedTCPRequestHandler(socketse def do_ping(self, payload): time.sleep(0.2) - self.send_lsp_msg(payload['id'], 'alive') + self.send_lsp_resp(payload['id'], 'alive') def do_echo(self, payload): - self.send_lsp_msg(-1, payload) + self.send_lsp_resp(-1, payload) def do_simple_rpc(self, payload): # test for a simple RPC request - self.send_lsp_msg(payload['id'], 'simple-rpc') + self.send_lsp_resp(payload['id'], 'simple-rpc') def do_rpc_with_notif(self, payload): # test for sending a notification before replying to a request message - self.send_lsp_msg(-1, 'rpc-with-notif-notif') + self.send_lsp_resp(-1, 'rpc-with-notif-notif') # sleep for some time to make sure the notification is delivered time.sleep(0.2) - self.send_lsp_msg(payload['id'], 'rpc-with-notif-resp') + self.send_lsp_resp(payload['id'], 'rpc-with-notif-resp') def do_wrong_payload(self, payload): # test for sending a non dict payload self.send_wrong_payload() time.sleep(0.2) - self.send_lsp_msg(-1, 'wrong-payload') + self.send_lsp_resp(-1, 'wrong-payload') def do_large_payload(self, payload): # test for sending a large (> 64K) payload - self.send_lsp_msg(payload['id'], payload) + self.send_lsp_resp(payload['id'], payload) def do_rpc_resp_incorrect_id(self, payload): - self.send_lsp_msg(-1, 'rpc-resp-incorrect-id-1') - self.send_lsp_msg(-1, 'rpc-resp-incorrect-id-2') - self.send_lsp_msg(1, 'rpc-resp-incorrect-id-3') + self.send_lsp_resp(-1, 'rpc-resp-incorrect-id-1') + self.send_lsp_resp(-1, 'rpc-resp-incorrect-id-2') + self.send_lsp_resp(1, 'rpc-resp-incorrect-id-3') time.sleep(0.2) - self.send_lsp_msg(payload['id'], 'rpc-resp-incorrect-id-4') + self.send_lsp_resp(payload['id'], 'rpc-resp-incorrect-id-4') def do_simple_notif(self, payload): # notification message test - self.send_lsp_msg(-1, 'simple-notif') + self.send_lsp_resp(-1, 'simple-notif') def do_multi_notif(self, payload): # send multiple notifications - self.send_lsp_msg(-1, 'multi-notif1') - self.send_lsp_msg(-1, 'multi-notif2') + self.send_lsp_resp(-1, 'multi-notif1') + self.send_lsp_resp(-1, 'multi-notif2') def do_msg_with_id(self, payload): - self.send_lsp_msg(payload['id'], 'msg-with-id') + self.send_lsp_resp(payload['id'], 'msg-with-id') def do_msg_specific_cb(self, payload): - self.send_lsp_msg(payload['id'], 'msg-specific-cb') + self.send_lsp_resp(payload['id'], 'msg-specific-cb') def do_server_req(self, payload): - self.send_lsp_msg(201, {'method': 'checkhealth', 'params': {'a': 20}}) + self.send_lsp_resp(201, {'method': 'checkhealth', 'params': {'a': 20}}) def do_extra_hdr_fields(self, payload): self.send_extra_hdr_fields(payload['id'], 'extra-hdr-fields') @@ -190,6 +203,19 @@ class ThreadedTCPRequestHandler(socketse def do_empty_payload(self, payload): self.send_empty_payload() + def do_server_req_in_middle(self, payload): + # Send a notification message to the client in the middle of processing + # a request message from the client + self.send_lsp_req(-1, 'server-req-in-middle', {'text': 'server-notif'}) + # Send a request message to the client in the middle of processing a + # request message from the client. + self.send_lsp_req(payload['id'], 'server-req-in-middle', {'text': 'server-req'}) + + def do_server_req_in_middle_resp(self, payload): + # After receiving a response from the client send the response to the + # client request. + self.send_lsp_resp(payload['id'], {'text': 'server-resp'}) + def process_msg(self, msg): try: decoded = json.loads(msg) @@ -213,7 +239,9 @@ class ThreadedTCPRequestHandler(socketse 'hdr-with-wrong-len': self.do_hdr_with_wrong_len, 'hdr-with-negative-len': self.do_hdr_with_negative_len, 'empty-header': self.do_empty_header, - 'empty-payload': self.do_empty_payload + 'empty-payload': self.do_empty_payload, + 'server-req-in-middle': self.do_server_req_in_middle, + 'server-req-in-middle-resp': self.do_server_req_in_middle_resp, } if decoded['method'] in test_map: test_map[decoded['method']](decoded) diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -700,6 +700,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1924, +/**/ 1923, /**/ 1922,