From 1926ae41845c3b6e2045b29225365c8a2e4eb1da Mon Sep 17 00:00:00 2001 From: Yegappan Lakshmanan Date: Thu, 21 Sep 2023 16:36:28 +0200 Subject: 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 --- src/channel.c | 19 ++++++------ src/optionstr.c | 2 +- src/testdir/test_channel.vim | 21 +++++++++++++ src/testdir/test_channel_lsp.py | 66 +++++++++++++++++++++++++++++------------ src/version.c | 2 ++ 5 files changed, 80 insertions(+), 30 deletions(-) (limited to 'src') diff --git a/src/channel.c b/src/channel.c index 4326ca7c11..1de376884a 100644 --- 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, ch_part_T part) 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 index aeeab27891..5fceb85737 100644 --- 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 index 5a35083248..edfd56e0ba 100644 --- 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 index eccb8937f0..887f6974e1 100644 --- a/src/testdir/test_channel_lsp.py +++ b/src/testdir/test_channel_lsp.py @@ -29,7 +29,20 @@ class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler): 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(socketserver.BaseRequestHandler): 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(socketserver.BaseRequestHandler): 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(socketserver.BaseRequestHandler): '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 index c088942889..b5a44982bb 100644 --- a/src/version.c +++ b/src/version.c @@ -699,6 +699,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1924, /**/ 1923, /**/ -- cgit v1.2.3