To: vim_dev@googlegroups.com Subject: Patch 8.2.4758 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.4758 Problem: When using an LSP channel want to get the message ID. Solution: Have ch_sendexpr() return the ID. (Yegappan Lakshmanan, closes #10202) Files: runtime/doc/channel.txt, src/channel.c, src/evalfunc.c, src/testdir/test_channel.vim *** ../vim-8.2.4757/runtime/doc/channel.txt 2022-04-12 15:08:36.669207637 +0100 --- runtime/doc/channel.txt 2022-04-16 10:38:22.033520606 +0100 *************** *** 25,30 **** --- 25,31 ---- 12. Job options |job-options| 13. Controlling a job |job-control| 14. Using a prompt buffer |prompt-buffer| + 15. Language Server Protocol |language-server-protocol| {only when compiled with the |+channel| feature for channel stuff} You can check this with: `has('channel')` *************** *** 424,429 **** --- 425,431 ---- The process can send back a response, the channel handler will be called with it. + *channel-onetime-callback* To send a message and letting the response handled by a specific function, asynchronously: > call ch_sendraw(channel, {string}, {'callback': 'MyHandler'}) *************** *** 588,594 **** "sock_io" "socket" "sock_timeout" timeout in msec ! Note that "pair" is only present for Unix-domain sockets, for regular ones "hostname" and "port" are present instead. When opened with job_start(): --- 590,596 ---- "sock_io" "socket" "sock_timeout" timeout in msec ! Note that "path" is only present for Unix-domain sockets, for regular ones "hostname" and "port" are present instead. When opened with job_start(): *************** *** 696,701 **** --- 698,712 ---- {handle} can be a Channel or a Job that has a Channel. When using the "lsp" channel mode, {expr} must be a |Dict|. + If the channel mode is "lsp", then returns a Dict. Otherwise + returns an empty String. If the "callback" item is present in + {options}, then the returned Dict contains the ID of the + request message. The ID can be used to send a cancellation + request to the LSP server (if needed). + + If a response message is not expected for {expr}, then don't + specify the "callback" item in {options}. + Can also be used as a |method|: > GetChannel()->ch_sendexpr(expr) *************** *** 1383,1389 **** startinsert ============================================================================== ! 14. Language Server Protocol *language-server-protocol* The language server protocol specification is available at: --- 1394,1400 ---- startinsert ============================================================================== ! 15. Language Server Protocol *language-server-protocol* The language server protocol specification is available at: *************** *** 1410,1419 **** let job = job_start(...., #{in_mode: 'lsp', out_mode: 'lsp'}) ! To synchronously send a JSON-RPC request to the server, use the |ch_evalexpr()| ! function. This function will return the response from the server. You can use the 'timeout' field in the {options} argument to control the response wait ! time. Example: > let req = {} let req.method = 'textDocument/definition' --- 1421,1431 ---- let job = job_start(...., #{in_mode: 'lsp', out_mode: 'lsp'}) ! 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 the 'timeout' field in the {options} argument to control the response wait ! time. If the request times out, then an empty string is returned. Example: > let req = {} let req.method = 'textDocument/definition' *************** *** 1425,1444 **** Note that in the request message the 'id' field should not be specified. If it is specified, then Vim will overwrite the value with an internally generated identifier. Vim currently supports only a number type for the 'id' field. To send a JSON-RPC request to the server and asynchronously process the response, use the |ch_sendexpr()| function and supply a callback function. Example: > let req = {} let req.method = 'textDocument/hover' let req.params = {} let req.params.textDocument = #{uri: 'a.c'} let req.params.position = #{line: 10, character: 3} let resp = ch_sendexpr(ch, req, #{callback: 'MyFn'}) To send a JSON-RPC notification message to the server, use the |ch_sendexpr()| ! function. Example: > call ch_sendexpr(ch, #{method: 'initialized'}) --- 1437,1481 ---- Note that in the request message the 'id' field should not be specified. If it is specified, then Vim will overwrite the value with an internally generated identifier. Vim currently supports only a number type for the 'id' field. + The callback function will be invoked for both a successful and a failed RPC + request. If the "id" field is present in the request message, then Vim will + overwrite it with an internally generated number. This function returns a + Dict with the identifier used for the message. This can be used to send + cancellation request to the LSP server (if needed). To send a JSON-RPC request to the server and asynchronously process the response, use the |ch_sendexpr()| function and supply a callback function. + Example: > let req = {} let req.method = 'textDocument/hover' + let req.id = 200 let req.params = {} let req.params.textDocument = #{uri: 'a.c'} let req.params.position = #{line: 10, character: 3} let resp = ch_sendexpr(ch, req, #{callback: 'MyFn'}) + To cancel an outstanding LSP request sent to the server using the + |ch_sendexpr()| function, send a cancelation message to the server using the + |ch_sendexpr()| function with the ID returned by |ch_sendexpr()|. Example: > + + " send a completion request + let req = {} + let req.method = 'textDocument/completion' + let req.params = {} + let req.params.textDocument = #{uri: 'a.c'} + let req.params.position = #{line: 10, character: 3} + let reqstatus = ch_sendexpr(ch, req, #{callback: 'MyComplete'}) + " send a cancellation notification + let notif = {} + let notif.method = '$/cancelRequest' + let notif.id = reqstatus.id + call ch_sendexpr(ch, notif) + To send a JSON-RPC notification message to the server, use the |ch_sendexpr()| ! function. As the server will not send a response message to the notification, ! don't specify the "callback" item. Example: > call ch_sendexpr(ch, #{method: 'initialized'}) *************** *** 1454,1457 **** --- 1491,1558 ---- The JSON-RPC notification messages from the server are delivered through the |channel-callback| function. + Depending on the use case, you can use the ch_evalexpr(), ch_sendexpr() and + ch_sendraw() functions on the same channel. + + A LSP request message has the following format (expressed as a Vim Dict). The + "params" field is optional: > + + { + "jsonrpc": "2.0", + "id": , + "method": , + "params": + } + + A LSP reponse message has the following format (expressed as a Vim Dict). The + "result" and "error" fields are optional: > + + { + "jsonrpc": "2.0", + "id": , + "result": + "error": + } + + A LSP notification message has the following format (expressed as a Vim Dict). + The "params" field is optional: > + + { + "jsonrpc": "2.0", + "method": , + "params": + } + + Depending on the use case, you can use the ch_evalexpr(), ch_sendexpr() and + ch_sendraw() functions on the same channel. + + A LSP request message has the following format (expressed as a Vim Dict). The + "params" field is optional: > + + { + "jsonrpc": "2.0", + "id": , + "method": , + "params": + } + + A LSP reponse message has the following format (expressed as a Vim Dict). The + "result" and "error" fields are optional: > + + { + "jsonrpc": "2.0", + "id": , + "result": + "error": + } + + A LSP notification message has the following format (expressed as a Vim Dict). + The "params" field is optional: > + + { + "jsonrpc": "2.0", + "method": , + "params": + } + vim:tw=78:ts=8:noet:ft=help:norl: *** ../vim-8.2.4757/src/channel.c 2022-04-15 13:53:30.044708701 +0100 --- src/channel.c 2022-04-16 10:32:40.785508863 +0100 *************** *** 4520,4525 **** --- 4520,4526 ---- ch_part_T part_read; jobopt_T opt; int timeout; + int callback_present = FALSE; // return an empty string by default rettv->v_type = VAR_STRING; *************** *** 4546,4552 **** { dict_T *d; dictitem_T *di; ! int callback_present = FALSE; if (argvars[1].v_type != VAR_DICT) { --- 4547,4555 ---- { dict_T *d; dictitem_T *di; ! ! // return an empty dict by default ! rettv_dict_alloc(rettv); if (argvars[1].v_type != VAR_DICT) { *************** *** 4629,4634 **** --- 4632,4645 ---- } } free_job_options(&opt); + if (ch_mode == MODE_LSP && !eval && callback_present) + { + // if ch_sendexpr() is used to send a LSP message and a callback + // function is specified, then return the generated identifier for the + // message. The user can use this to cancel the request (if needed). + if (rettv->vval.v_dict != NULL) + dict_add_number(rettv->vval.v_dict, "id", id); + } } /* *** ../vim-8.2.4757/src/evalfunc.c 2022-04-15 20:50:13.459235826 +0100 --- src/evalfunc.c 2022-04-16 10:32:40.785508863 +0100 *************** *** 1660,1666 **** {"ch_readraw", 1, 2, FEARG_1, arg2_chan_or_job_dict, ret_string, JOB_FUNC(f_ch_readraw)}, {"ch_sendexpr", 2, 3, FEARG_1, arg23_chanexpr, ! ret_void, JOB_FUNC(f_ch_sendexpr)}, {"ch_sendraw", 2, 3, FEARG_1, arg23_chanraw, ret_void, JOB_FUNC(f_ch_sendraw)}, {"ch_setoptions", 2, 2, FEARG_1, arg2_chan_or_job_dict, --- 1660,1666 ---- {"ch_readraw", 1, 2, FEARG_1, arg2_chan_or_job_dict, ret_string, JOB_FUNC(f_ch_readraw)}, {"ch_sendexpr", 2, 3, FEARG_1, arg23_chanexpr, ! ret_any, JOB_FUNC(f_ch_sendexpr)}, {"ch_sendraw", 2, 3, FEARG_1, arg23_chanraw, ret_void, JOB_FUNC(f_ch_sendraw)}, {"ch_setoptions", 2, 2, FEARG_1, arg2_chan_or_job_dict, *** ../vim-8.2.4757/src/testdir/test_channel.vim 2022-04-04 15:46:37.606126838 +0100 --- src/testdir/test_channel.vim 2022-04-16 10:32:40.785508863 +0100 *************** *** 2494,2502 **** " Wrong payload notification test let g:lspNotif = [] ! call ch_sendexpr(ch, #{method: 'wrong-payload', params: {}}) " Send a ping to wait for all the notification messages to arrive ! call ch_evalexpr(ch, #{method: 'ping'}) call assert_equal([#{jsonrpc: '2.0', result: 'wrong-payload'}], g:lspNotif) " Test for receiving a response with incorrect 'id' and additional --- 2494,2503 ---- " Wrong payload notification test let g:lspNotif = [] ! let r = ch_sendexpr(ch, #{method: 'wrong-payload', params: {}}) ! call assert_equal({}, r) " 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: 'wrong-payload'}], g:lspNotif) " Test for receiving a response with incorrect 'id' and additional *************** *** 2516,2529 **** let g:lspNotif = [] call ch_sendexpr(ch, #{method: 'simple-notif', params: [#{a: 10, b: []}]}) " Send a ping to wait for all the notification messages to arrive ! call ch_evalexpr(ch, #{method: 'ping'}) call assert_equal([#{jsonrpc: '2.0', result: 'simple-notif'}], g:lspNotif) " multiple notifications test let g:lspNotif = [] call ch_sendexpr(ch, #{method: 'multi-notif', params: [#{a: {}, b: {}}]}) " Send a ping to wait for all the notification messages to arrive ! call ch_evalexpr(ch, #{method: 'ping'}) call assert_equal([#{jsonrpc: '2.0', result: 'multi-notif1'}, \ #{jsonrpc: '2.0', result: 'multi-notif2'}], g:lspNotif) --- 2517,2530 ---- let g:lspNotif = [] call ch_sendexpr(ch, #{method: 'simple-notif', params: [#{a: 10, b: []}]}) " 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: 'simple-notif'}], g:lspNotif) " multiple notifications test let g:lspNotif = [] call ch_sendexpr(ch, #{method: 'multi-notif', params: [#{a: {}, b: {}}]}) " 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: 'multi-notif1'}, \ #{jsonrpc: '2.0', result: 'multi-notif2'}], g:lspNotif) *************** *** 2531,2537 **** let g:lspNotif = [] call ch_sendexpr(ch, #{method: 'msg-with-id', id: 93, params: #{s: 'str'}}) " Send a ping to wait for all the notification messages to arrive ! call ch_evalexpr(ch, #{method: 'ping'}) call assert_equal([#{jsonrpc: '2.0', id: 93, result: 'msg-with-id'}], \ g:lspNotif) --- 2532,2538 ---- let g:lspNotif = [] call ch_sendexpr(ch, #{method: 'msg-with-id', id: 93, params: #{s: 'str'}}) " 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', id: 93, result: 'msg-with-id'}], \ g:lspNotif) *************** *** 2541,2556 **** " Test for using a one time callback function to process a response let g:lspOtMsgs = [] ! call ch_sendexpr(ch, #{method: 'msg-specifc-cb', params: {}}, \ #{callback: 'LspOtCb'}) ! call ch_evalexpr(ch, #{method: 'ping'}) call assert_equal([#{id: 9, jsonrpc: '2.0', result: 'msg-specifc-cb'}], \ g:lspOtMsgs) " Test for generating a request message from the other end (server) let g:lspNotif = [] call ch_sendexpr(ch, #{method: 'server-req', params: #{}}) ! call ch_evalexpr(ch, #{method: 'ping'}) call assert_equal([{'id': 201, 'jsonrpc': '2.0', \ 'result': {'method': 'checkhealth', 'params': {'a': 20}}}], \ g:lspNotif) --- 2542,2558 ---- " Test for using a one time callback function to process a response let g:lspOtMsgs = [] ! let r = ch_sendexpr(ch, #{method: 'msg-specifc-cb', params: {}}, \ #{callback: 'LspOtCb'}) ! call assert_equal(9, r.id) ! call assert_equal('alive', ch_evalexpr(ch, #{method: 'ping'}).result) call assert_equal([#{id: 9, jsonrpc: '2.0', result: 'msg-specifc-cb'}], \ g:lspOtMsgs) " Test for generating a request message from the other end (server) let g:lspNotif = [] call ch_sendexpr(ch, #{method: 'server-req', params: #{}}) ! call assert_equal('alive', ch_evalexpr(ch, #{method: 'ping'}).result) call assert_equal([{'id': 201, 'jsonrpc': '2.0', \ 'result': {'method': 'checkhealth', 'params': {'a': 20}}}], \ g:lspNotif) *************** *** 2559,2565 **** let g:lspNotif = [] call ch_sendexpr(ch, #{method: 'echo', params: #{s: 'msg-without-id'}}) " Send a ping to wait for all the notification messages to arrive ! call ch_evalexpr(ch, #{method: 'ping'}) call assert_equal([#{jsonrpc: '2.0', result: \ #{method: 'echo', jsonrpc: '2.0', params: #{s: 'msg-without-id'}}}], \ g:lspNotif) --- 2561,2567 ---- let g:lspNotif = [] call ch_sendexpr(ch, #{method: 'echo', params: #{s: 'msg-without-id'}}) " 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: 'msg-without-id'}}}], \ g:lspNotif) *************** *** 2568,2574 **** let g:lspNotif = [] call ch_sendexpr(ch, #{method: 'echo', id: 110, params: #{s: 'msg-with-id'}}) " Send a ping to wait for all the notification messages to arrive ! call ch_evalexpr(ch, #{method: 'ping'}) call assert_equal([#{jsonrpc: '2.0', result: \ #{method: 'echo', jsonrpc: '2.0', id: 110, \ params: #{s: 'msg-with-id'}}}], g:lspNotif) --- 2570,2576 ---- let g:lspNotif = [] call ch_sendexpr(ch, #{method: 'echo', id: 110, params: #{s: 'msg-with-id'}}) " 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', id: 110, \ params: #{s: 'msg-with-id'}}}], g:lspNotif) *************** *** 2581,2626 **** " Test for processing a HTTP header without the Content-Length field let resp = ch_evalexpr(ch, #{method: 'hdr-without-len', params: {}}, \ #{timeout: 200}) ! call assert_equal('', resp) " send a ping to make sure communication still works ! let resp = ch_evalexpr(ch, #{method: 'ping'}) ! call assert_equal({'id': 16, 'jsonrpc': '2.0', 'result': 'alive'}, resp) " Test for processing a HTTP header with wrong length let resp = ch_evalexpr(ch, #{method: 'hdr-with-wrong-len', params: {}}, \ #{timeout: 200}) ! call assert_equal('', resp) " send a ping to make sure communication still works ! let resp = ch_evalexpr(ch, #{method: 'ping'}) ! call assert_equal({'id': 18, 'jsonrpc': '2.0', 'result': 'alive'}, resp) " Test for processing a HTTP header with negative length let resp = ch_evalexpr(ch, #{method: 'hdr-with-negative-len', params: {}}, \ #{timeout: 200}) ! call assert_equal('', resp) " send a ping to make sure communication still works ! let resp = ch_evalexpr(ch, #{method: 'ping'}) ! call assert_equal({'id': 20, 'jsonrpc': '2.0', 'result': 'alive'}, resp) " Test for an empty header let resp = ch_evalexpr(ch, #{method: 'empty-header', params: {}}, \ #{timeout: 200}) ! call assert_equal('', resp) " send a ping to make sure communication still works ! let resp = ch_evalexpr(ch, #{method: 'ping'}) ! call assert_equal({'id': 22, 'jsonrpc': '2.0', 'result': 'alive'}, resp) " Test for an empty payload let resp = ch_evalexpr(ch, #{method: 'empty-payload', params: {}}, \ #{timeout: 200}) ! call assert_equal('', resp) " send a ping to make sure communication still works ! let resp = ch_evalexpr(ch, #{method: 'ping'}) ! call assert_equal({'id': 24, 'jsonrpc': '2.0', 'result': 'alive'}, resp) " Test for invoking an unsupported method let resp = ch_evalexpr(ch, #{method: 'xyz', params: {}}, #{timeout: 200}) ! call assert_equal('', resp) " Test for sending a message without a callback function. Notification " message should be dropped but RPC response should not be dropped. --- 2583,2623 ---- " Test for processing a HTTP header without the Content-Length field let resp = ch_evalexpr(ch, #{method: 'hdr-without-len', params: {}}, \ #{timeout: 200}) ! call assert_equal({}, resp) " send a ping to make sure communication still works ! call assert_equal('alive', ch_evalexpr(ch, #{method: 'ping'}).result) " Test for processing a HTTP header with wrong length let resp = ch_evalexpr(ch, #{method: 'hdr-with-wrong-len', params: {}}, \ #{timeout: 200}) ! call assert_equal({}, resp) " send a ping to make sure communication still works ! call assert_equal('alive', ch_evalexpr(ch, #{method: 'ping'}).result) " Test for processing a HTTP header with negative length let resp = ch_evalexpr(ch, #{method: 'hdr-with-negative-len', params: {}}, \ #{timeout: 200}) ! call assert_equal({}, resp) " send a ping to make sure communication still works ! call assert_equal('alive', ch_evalexpr(ch, #{method: 'ping'}).result) " Test for an empty header let resp = ch_evalexpr(ch, #{method: 'empty-header', params: {}}, \ #{timeout: 200}) ! call assert_equal({}, resp) " send a ping to make sure communication still works ! call assert_equal('alive', ch_evalexpr(ch, #{method: 'ping'}).result) " Test for an empty payload let resp = ch_evalexpr(ch, #{method: 'empty-payload', params: {}}, \ #{timeout: 200}) ! call assert_equal({}, resp) " send a ping to make sure communication still works ! call assert_equal('alive', ch_evalexpr(ch, #{method: 'ping'}).result) " Test for invoking an unsupported method let resp = ch_evalexpr(ch, #{method: 'xyz', params: {}}, #{timeout: 200}) ! call assert_equal({}, resp) " Test for sending a message without a callback function. Notification " message should be dropped but RPC response should not be dropped. *************** *** 2628,2641 **** 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 ch_evalexpr(ch, #{method: 'ping'}) 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 ch_evalexpr(ch, #{method: 'ping'}) call assert_equal([#{jsonrpc: '2.0', result: \ #{method: 'echo', jsonrpc: '2.0', params: #{s: 'no-callback'}}}], \ g:lspNotif) --- 2625,2638 ---- 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([], 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) *** ../vim-8.2.4757/src/version.c 2022-04-16 10:08:47.858603269 +0100 --- src/version.c 2022-04-16 10:34:59.849522055 +0100 *************** *** 748,749 **** --- 748,751 ---- { /* Add new patch number below this line */ + /**/ + 4758, /**/ -- If someone questions your market projections, simply point out that your target market is "People who are nuts" and "People who will buy any damn thing". Nobody is going to tell you there aren't enough of those people to go around. (Scott Adams - The Dilbert principle) /// 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 ///