forked from aniani/vim
		
	patch 8.2.4758: when using an LSP channel want to get the message ID
Problem:    When using an LSP channel want to get the message ID.
Solution:   Have ch_sendexpr() return the ID. (Yegappan Lakshmanan,
            closes #10202)
			
			
This commit is contained in:
		
				
					committed by
					
						 Bram Moolenaar
						Bram Moolenaar
					
				
			
			
				
	
			
			
			
						parent
						
							b9e99e58bb
						
					
				
				
					commit
					3b470ae88f
				
			| @@ -25,6 +25,7 @@ The Netbeans interface also uses a channel. |netbeans| | ||||
| 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,6 +425,7 @@ To send a message, without expecting a response: > | ||||
| 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'}) | ||||
| @@ -696,6 +698,15 @@ ch_sendexpr({handle}, {expr} [, {options}])			*ch_sendexpr()* | ||||
| 		{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,7 +1394,7 @@ The same in |Vim9| script: > | ||||
| 	startinsert | ||||
|  | ||||
| ============================================================================== | ||||
| 14. Language Server Protocol			*language-server-protocol* | ||||
| 15. Language Server Protocol			*language-server-protocol* | ||||
|  | ||||
| The language server protocol specification is available at: | ||||
|  | ||||
| @@ -1410,10 +1421,11 @@ To open a channel using the 'lsp' mode with a job, set the 'in_mode' and | ||||
|  | ||||
|     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 | ||||
| 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. Example: > | ||||
| time.  If the request times out, then an empty string is returned.  Example: > | ||||
|  | ||||
|     let req = {} | ||||
|     let req.method = 'textDocument/definition' | ||||
| @@ -1425,20 +1437,45 @@ time. Example: > | ||||
| 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. Example: > | ||||
| 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,4 +1491,68 @@ from the server request message. Example: > | ||||
| 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": <number>, | ||||
| 	"method": <string>, | ||||
| 	"params": <list|dict> | ||||
|     } | ||||
|  | ||||
| A LSP reponse message has the following format (expressed as a Vim Dict).  The | ||||
| "result" and "error" fields are optional: > | ||||
|  | ||||
|     { | ||||
| 	"jsonrpc": "2.0", | ||||
| 	"id": <number>, | ||||
| 	"result": <vim type> | ||||
| 	"error": <dict> | ||||
|     } | ||||
|  | ||||
| A LSP notification message has the following format (expressed as a Vim Dict). | ||||
| The "params" field is optional: > | ||||
|  | ||||
|     { | ||||
| 	"jsonrpc": "2.0", | ||||
| 	"method": <string>, | ||||
| 	"params": <list|dict> | ||||
|     } | ||||
|  | ||||
| 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": <number>, | ||||
| 	"method": <string>, | ||||
| 	"params": <list|dict> | ||||
|     } | ||||
|  | ||||
| A LSP reponse message has the following format (expressed as a Vim Dict).  The | ||||
| "result" and "error" fields are optional: > | ||||
|  | ||||
|     { | ||||
| 	"jsonrpc": "2.0", | ||||
| 	"id": <number>, | ||||
| 	"result": <vim type> | ||||
| 	"error": <dict> | ||||
|     } | ||||
|  | ||||
| A LSP notification message has the following format (expressed as a Vim Dict). | ||||
| The "params" field is optional: > | ||||
|  | ||||
|     { | ||||
| 	"jsonrpc": "2.0", | ||||
| 	"method": <string>, | ||||
| 	"params": <list|dict> | ||||
|     } | ||||
|  | ||||
|  vim:tw=78:ts=8:noet:ft=help:norl: | ||||
|   | ||||
| @@ -4520,6 +4520,7 @@ ch_expr_common(typval_T *argvars, typval_T *rettv, int eval) | ||||
|     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,7 +4547,9 @@ ch_expr_common(typval_T *argvars, typval_T *rettv, int eval) | ||||
|     { | ||||
| 	dict_T		*d; | ||||
| 	dictitem_T	*di; | ||||
| 	int		callback_present = FALSE; | ||||
|  | ||||
| 	// return an empty dict by default | ||||
| 	rettv_dict_alloc(rettv); | ||||
|  | ||||
| 	if (argvars[1].v_type != VAR_DICT) | ||||
| 	{ | ||||
| @@ -4629,6 +4632,14 @@ ch_expr_common(typval_T *argvars, typval_T *rettv, int eval) | ||||
| 	} | ||||
|     } | ||||
|     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); | ||||
|     } | ||||
| } | ||||
|  | ||||
| /* | ||||
|   | ||||
| @@ -1660,7 +1660,7 @@ static funcentry_T global_functions[] = | ||||
|     {"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)}, | ||||
| 			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, | ||||
|   | ||||
| @@ -2494,9 +2494,10 @@ func LspTests(port) | ||||
|  | ||||
|   " Wrong payload notification test | ||||
|   let g:lspNotif = [] | ||||
|   call ch_sendexpr(ch, #{method: 'wrong-payload', params: {}}) | ||||
|   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 ch_evalexpr(ch, #{method: 'ping'}) | ||||
|   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,14 +2517,14 @@ func LspTests(port) | ||||
|   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('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 ch_evalexpr(ch, #{method: 'ping'}) | ||||
|   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,7 +2532,7 @@ func LspTests(port) | ||||
|   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('alive', ch_evalexpr(ch, #{method: 'ping'}).result) | ||||
|   call assert_equal([#{jsonrpc: '2.0', id: 93, result: 'msg-with-id'}], | ||||
|         \ g:lspNotif) | ||||
|  | ||||
| @@ -2541,16 +2542,17 @@ func LspTests(port) | ||||
|  | ||||
|   " Test for using a one time callback function to process a response | ||||
|   let g:lspOtMsgs = [] | ||||
|   call ch_sendexpr(ch, #{method: 'msg-specifc-cb', params: {}}, | ||||
|   let r = ch_sendexpr(ch, #{method: 'msg-specifc-cb', params: {}}, | ||||
|         \ #{callback: 'LspOtCb'}) | ||||
|   call ch_evalexpr(ch, #{method: 'ping'}) | ||||
|   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 ch_evalexpr(ch, #{method: 'ping'}) | ||||
|   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,7 +2561,7 @@ func LspTests(port) | ||||
|   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('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,7 +2570,7 @@ func LspTests(port) | ||||
|   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('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,46 +2583,41 @@ func LspTests(port) | ||||
|   " 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) | ||||
|   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) | ||||
|   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) | ||||
|   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) | ||||
|   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) | ||||
|   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) | ||||
|   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) | ||||
|   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) | ||||
|   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) | ||||
|   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) | ||||
|   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) | ||||
|   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,14 +2625,14 @@ func LspTests(port) | ||||
|   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('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 ch_evalexpr(ch, #{method: 'ping'}) | ||||
|   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) | ||||
|   | ||||
| @@ -746,6 +746,8 @@ static char *(features[]) = | ||||
|  | ||||
| static int included_patches[] = | ||||
| {   /* Add new patch number below this line */ | ||||
| /**/ | ||||
|     4758, | ||||
| /**/ | ||||
|     4757, | ||||
| /**/ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user