Skip to content

Commit 3b470ae

Browse files
yegappanbrammool
authored andcommitted
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)
1 parent b9e99e5 commit 3b470ae

File tree

5 files changed

+146
-35
lines changed

5 files changed

+146
-35
lines changed

runtime/doc/channel.txt

Lines changed: 106 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ The Netbeans interface also uses a channel. |netbeans|
2525
12. Job options |job-options|
2626
13. Controlling a job |job-control|
2727
14. Using a prompt buffer |prompt-buffer|
28+
15. Language Server Protocol |language-server-protocol|
2829

2930
{only when compiled with the |+channel| feature for channel stuff}
3031
You can check this with: `has('channel')`
@@ -424,6 +425,7 @@ To send a message, without expecting a response: >
424425
The process can send back a response, the channel handler will be called with
425426
it.
426427

428+
*channel-onetime-callback*
427429
To send a message and letting the response handled by a specific function,
428430
asynchronously: >
429431
call ch_sendraw(channel, {string}, {'callback': 'MyHandler'})
@@ -696,6 +698,15 @@ ch_sendexpr({handle}, {expr} [, {options}]) *ch_sendexpr()*
696698
{handle} can be a Channel or a Job that has a Channel.
697699
When using the "lsp" channel mode, {expr} must be a |Dict|.
698700

701+
If the channel mode is "lsp", then returns a Dict. Otherwise
702+
returns an empty String. If the "callback" item is present in
703+
{options}, then the returned Dict contains the ID of the
704+
request message. The ID can be used to send a cancellation
705+
request to the LSP server (if needed).
706+
707+
If a response message is not expected for {expr}, then don't
708+
specify the "callback" item in {options}.
709+
699710
Can also be used as a |method|: >
700711
GetChannel()->ch_sendexpr(expr)
701712
@@ -1383,7 +1394,7 @@ The same in |Vim9| script: >
13831394
startinsert
13841395
13851396
==============================================================================
1386-
14. Language Server Protocol *language-server-protocol*
1397+
15. Language Server Protocol *language-server-protocol*
13871398

13881399
The language server protocol specification is available at:
13891400

@@ -1410,10 +1421,11 @@ To open a channel using the 'lsp' mode with a job, set the 'in_mode' and
14101421
14111422
let job = job_start(...., #{in_mode: 'lsp', out_mode: 'lsp'})
14121423
1413-
To synchronously send a JSON-RPC request to the server, use the |ch_evalexpr()|
1414-
function. This function will return the response from the server. You can use
1424+
To synchronously send a JSON-RPC request to the server, use the
1425+
|ch_evalexpr()| function. This function will wait and return the decoded
1426+
response message from the server. You can use either the |channel-timeout| or
14151427
the 'timeout' field in the {options} argument to control the response wait
1416-
time. Example: >
1428+
time. If the request times out, then an empty string is returned. Example: >
14171429
14181430
let req = {}
14191431
let req.method = 'textDocument/definition'
@@ -1425,20 +1437,45 @@ time. Example: >
14251437
Note that in the request message the 'id' field should not be specified. If it
14261438
is specified, then Vim will overwrite the value with an internally generated
14271439
identifier. Vim currently supports only a number type for the 'id' field.
1440+
The callback function will be invoked for both a successful and a failed RPC
1441+
request. If the "id" field is present in the request message, then Vim will
1442+
overwrite it with an internally generated number. This function returns a
1443+
Dict with the identifier used for the message. This can be used to send
1444+
cancellation request to the LSP server (if needed).
14281445

14291446
To send a JSON-RPC request to the server and asynchronously process the
14301447
response, use the |ch_sendexpr()| function and supply a callback function.
1448+
14311449
Example: >
14321450
14331451
let req = {}
14341452
let req.method = 'textDocument/hover'
1453+
let req.id = 200
14351454
let req.params = {}
14361455
let req.params.textDocument = #{uri: 'a.c'}
14371456
let req.params.position = #{line: 10, character: 3}
14381457
let resp = ch_sendexpr(ch, req, #{callback: 'MyFn'})
14391458
1459+
To cancel an outstanding LSP request sent to the server using the
1460+
|ch_sendexpr()| function, send a cancelation message to the server using the
1461+
|ch_sendexpr()| function with the ID returned by |ch_sendexpr()|. Example: >
1462+
1463+
" send a completion request
1464+
let req = {}
1465+
let req.method = 'textDocument/completion'
1466+
let req.params = {}
1467+
let req.params.textDocument = #{uri: 'a.c'}
1468+
let req.params.position = #{line: 10, character: 3}
1469+
let reqstatus = ch_sendexpr(ch, req, #{callback: 'MyComplete'})
1470+
" send a cancellation notification
1471+
let notif = {}
1472+
let notif.method = '$/cancelRequest'
1473+
let notif.id = reqstatus.id
1474+
call ch_sendexpr(ch, notif)
1475+
14401476
To send a JSON-RPC notification message to the server, use the |ch_sendexpr()|
1441-
function. Example: >
1477+
function. As the server will not send a response message to the notification,
1478+
don't specify the "callback" item. Example: >
14421479
14431480
call ch_sendexpr(ch, #{method: 'initialized'})
14441481
@@ -1454,4 +1491,68 @@ from the server request message. Example: >
14541491
The JSON-RPC notification messages from the server are delivered through the
14551492
|channel-callback| function.
14561493

1494+
Depending on the use case, you can use the ch_evalexpr(), ch_sendexpr() and
1495+
ch_sendraw() functions on the same channel.
1496+
1497+
A LSP request message has the following format (expressed as a Vim Dict). The
1498+
"params" field is optional: >
1499+
1500+
{
1501+
"jsonrpc": "2.0",
1502+
"id": <number>,
1503+
"method": <string>,
1504+
"params": <list|dict>
1505+
}
1506+
1507+
A LSP reponse message has the following format (expressed as a Vim Dict). The
1508+
"result" and "error" fields are optional: >
1509+
1510+
{
1511+
"jsonrpc": "2.0",
1512+
"id": <number>,
1513+
"result": <vim type>
1514+
"error": <dict>
1515+
}
1516+
1517+
A LSP notification message has the following format (expressed as a Vim Dict).
1518+
The "params" field is optional: >
1519+
1520+
{
1521+
"jsonrpc": "2.0",
1522+
"method": <string>,
1523+
"params": <list|dict>
1524+
}
1525+
1526+
Depending on the use case, you can use the ch_evalexpr(), ch_sendexpr() and
1527+
ch_sendraw() functions on the same channel.
1528+
1529+
A LSP request message has the following format (expressed as a Vim Dict). The
1530+
"params" field is optional: >
1531+
1532+
{
1533+
"jsonrpc": "2.0",
1534+
"id": <number>,
1535+
"method": <string>,
1536+
"params": <list|dict>
1537+
}
1538+
1539+
A LSP reponse message has the following format (expressed as a Vim Dict). The
1540+
"result" and "error" fields are optional: >
1541+
1542+
{
1543+
"jsonrpc": "2.0",
1544+
"id": <number>,
1545+
"result": <vim type>
1546+
"error": <dict>
1547+
}
1548+
1549+
A LSP notification message has the following format (expressed as a Vim Dict).
1550+
The "params" field is optional: >
1551+
1552+
{
1553+
"jsonrpc": "2.0",
1554+
"method": <string>,
1555+
"params": <list|dict>
1556+
}
1557+
14571558
vim:tw=78:ts=8:noet:ft=help:norl:

src/channel.c

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4520,6 +4520,7 @@ ch_expr_common(typval_T *argvars, typval_T *rettv, int eval)
45204520
ch_part_T part_read;
45214521
jobopt_T opt;
45224522
int timeout;
4523+
int callback_present = FALSE;
45234524

45244525
// return an empty string by default
45254526
rettv->v_type = VAR_STRING;
@@ -4546,7 +4547,9 @@ ch_expr_common(typval_T *argvars, typval_T *rettv, int eval)
45464547
{
45474548
dict_T *d;
45484549
dictitem_T *di;
4549-
int callback_present = FALSE;
4550+
4551+
// return an empty dict by default
4552+
rettv_dict_alloc(rettv);
45504553

45514554
if (argvars[1].v_type != VAR_DICT)
45524555
{
@@ -4629,6 +4632,14 @@ ch_expr_common(typval_T *argvars, typval_T *rettv, int eval)
46294632
}
46304633
}
46314634
free_job_options(&opt);
4635+
if (ch_mode == MODE_LSP && !eval && callback_present)
4636+
{
4637+
// if ch_sendexpr() is used to send a LSP message and a callback
4638+
// function is specified, then return the generated identifier for the
4639+
// message. The user can use this to cancel the request (if needed).
4640+
if (rettv->vval.v_dict != NULL)
4641+
dict_add_number(rettv->vval.v_dict, "id", id);
4642+
}
46324643
}
46334644

46344645
/*

src/evalfunc.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1660,7 +1660,7 @@ static funcentry_T global_functions[] =
16601660
{"ch_readraw", 1, 2, FEARG_1, arg2_chan_or_job_dict,
16611661
ret_string, JOB_FUNC(f_ch_readraw)},
16621662
{"ch_sendexpr", 2, 3, FEARG_1, arg23_chanexpr,
1663-
ret_void, JOB_FUNC(f_ch_sendexpr)},
1663+
ret_any, JOB_FUNC(f_ch_sendexpr)},
16641664
{"ch_sendraw", 2, 3, FEARG_1, arg23_chanraw,
16651665
ret_void, JOB_FUNC(f_ch_sendraw)},
16661666
{"ch_setoptions", 2, 2, FEARG_1, arg2_chan_or_job_dict,

src/testdir/test_channel.vim

Lines changed: 25 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2494,9 +2494,10 @@ func LspTests(port)
24942494

24952495
" Wrong payload notification test
24962496
let g:lspNotif = []
2497-
call ch_sendexpr(ch, #{method: 'wrong-payload', params: {}})
2497+
let r = ch_sendexpr(ch, #{method: 'wrong-payload', params: {}})
2498+
call assert_equal({}, r)
24982499
" Send a ping to wait for all the notification messages to arrive
2499-
call ch_evalexpr(ch, #{method: 'ping'})
2500+
call assert_equal('alive', ch_evalexpr(ch, #{method: 'ping'}).result)
25002501
call assert_equal([#{jsonrpc: '2.0', result: 'wrong-payload'}], g:lspNotif)
25012502

25022503
" Test for receiving a response with incorrect 'id' and additional
@@ -2516,22 +2517,22 @@ func LspTests(port)
25162517
let g:lspNotif = []
25172518
call ch_sendexpr(ch, #{method: 'simple-notif', params: [#{a: 10, b: []}]})
25182519
" Send a ping to wait for all the notification messages to arrive
2519-
call ch_evalexpr(ch, #{method: 'ping'})
2520+
call assert_equal('alive', ch_evalexpr(ch, #{method: 'ping'}).result)
25202521
call assert_equal([#{jsonrpc: '2.0', result: 'simple-notif'}], g:lspNotif)
25212522

25222523
" multiple notifications test
25232524
let g:lspNotif = []
25242525
call ch_sendexpr(ch, #{method: 'multi-notif', params: [#{a: {}, b: {}}]})
25252526
" Send a ping to wait for all the notification messages to arrive
2526-
call ch_evalexpr(ch, #{method: 'ping'})
2527+
call assert_equal('alive', ch_evalexpr(ch, #{method: 'ping'}).result)
25272528
call assert_equal([#{jsonrpc: '2.0', result: 'multi-notif1'},
25282529
\ #{jsonrpc: '2.0', result: 'multi-notif2'}], g:lspNotif)
25292530

25302531
" Test for sending a message with an identifier.
25312532
let g:lspNotif = []
25322533
call ch_sendexpr(ch, #{method: 'msg-with-id', id: 93, params: #{s: 'str'}})
25332534
" Send a ping to wait for all the notification messages to arrive
2534-
call ch_evalexpr(ch, #{method: 'ping'})
2535+
call assert_equal('alive', ch_evalexpr(ch, #{method: 'ping'}).result)
25352536
call assert_equal([#{jsonrpc: '2.0', id: 93, result: 'msg-with-id'}],
25362537
\ g:lspNotif)
25372538

@@ -2541,16 +2542,17 @@ func LspTests(port)
25412542

25422543
" Test for using a one time callback function to process a response
25432544
let g:lspOtMsgs = []
2544-
call ch_sendexpr(ch, #{method: 'msg-specifc-cb', params: {}},
2545+
let r = ch_sendexpr(ch, #{method: 'msg-specifc-cb', params: {}},
25452546
\ #{callback: 'LspOtCb'})
2546-
call ch_evalexpr(ch, #{method: 'ping'})
2547+
call assert_equal(9, r.id)
2548+
call assert_equal('alive', ch_evalexpr(ch, #{method: 'ping'}).result)
25472549
call assert_equal([#{id: 9, jsonrpc: '2.0', result: 'msg-specifc-cb'}],
25482550
\ g:lspOtMsgs)
25492551

25502552
" Test for generating a request message from the other end (server)
25512553
let g:lspNotif = []
25522554
call ch_sendexpr(ch, #{method: 'server-req', params: #{}})
2553-
call ch_evalexpr(ch, #{method: 'ping'})
2555+
call assert_equal('alive', ch_evalexpr(ch, #{method: 'ping'}).result)
25542556
call assert_equal([{'id': 201, 'jsonrpc': '2.0',
25552557
\ 'result': {'method': 'checkhealth', 'params': {'a': 20}}}],
25562558
\ g:lspNotif)
@@ -2559,7 +2561,7 @@ func LspTests(port)
25592561
let g:lspNotif = []
25602562
call ch_sendexpr(ch, #{method: 'echo', params: #{s: 'msg-without-id'}})
25612563
" Send a ping to wait for all the notification messages to arrive
2562-
call ch_evalexpr(ch, #{method: 'ping'})
2564+
call assert_equal('alive', ch_evalexpr(ch, #{method: 'ping'}).result)
25632565
call assert_equal([#{jsonrpc: '2.0', result:
25642566
\ #{method: 'echo', jsonrpc: '2.0', params: #{s: 'msg-without-id'}}}],
25652567
\ g:lspNotif)
@@ -2568,7 +2570,7 @@ func LspTests(port)
25682570
let g:lspNotif = []
25692571
call ch_sendexpr(ch, #{method: 'echo', id: 110, params: #{s: 'msg-with-id'}})
25702572
" Send a ping to wait for all the notification messages to arrive
2571-
call ch_evalexpr(ch, #{method: 'ping'})
2573+
call assert_equal('alive', ch_evalexpr(ch, #{method: 'ping'}).result)
25722574
call assert_equal([#{jsonrpc: '2.0', result:
25732575
\ #{method: 'echo', jsonrpc: '2.0', id: 110,
25742576
\ params: #{s: 'msg-with-id'}}}], g:lspNotif)
@@ -2581,61 +2583,56 @@ func LspTests(port)
25812583
" Test for processing a HTTP header without the Content-Length field
25822584
let resp = ch_evalexpr(ch, #{method: 'hdr-without-len', params: {}},
25832585
\ #{timeout: 200})
2584-
call assert_equal('', resp)
2586+
call assert_equal({}, resp)
25852587
" send a ping to make sure communication still works
2586-
let resp = ch_evalexpr(ch, #{method: 'ping'})
2587-
call assert_equal({'id': 16, 'jsonrpc': '2.0', 'result': 'alive'}, resp)
2588+
call assert_equal('alive', ch_evalexpr(ch, #{method: 'ping'}).result)
25882589

25892590
" Test for processing a HTTP header with wrong length
25902591
let resp = ch_evalexpr(ch, #{method: 'hdr-with-wrong-len', params: {}},
25912592
\ #{timeout: 200})
2592-
call assert_equal('', resp)
2593+
call assert_equal({}, resp)
25932594
" send a ping to make sure communication still works
2594-
let resp = ch_evalexpr(ch, #{method: 'ping'})
2595-
call assert_equal({'id': 18, 'jsonrpc': '2.0', 'result': 'alive'}, resp)
2595+
call assert_equal('alive', ch_evalexpr(ch, #{method: 'ping'}).result)
25962596

25972597
" Test for processing a HTTP header with negative length
25982598
let resp = ch_evalexpr(ch, #{method: 'hdr-with-negative-len', params: {}},
25992599
\ #{timeout: 200})
2600-
call assert_equal('', resp)
2600+
call assert_equal({}, resp)
26012601
" send a ping to make sure communication still works
2602-
let resp = ch_evalexpr(ch, #{method: 'ping'})
2603-
call assert_equal({'id': 20, 'jsonrpc': '2.0', 'result': 'alive'}, resp)
2602+
call assert_equal('alive', ch_evalexpr(ch, #{method: 'ping'}).result)
26042603

26052604
" Test for an empty header
26062605
let resp = ch_evalexpr(ch, #{method: 'empty-header', params: {}},
26072606
\ #{timeout: 200})
2608-
call assert_equal('', resp)
2607+
call assert_equal({}, resp)
26092608
" send a ping to make sure communication still works
2610-
let resp = ch_evalexpr(ch, #{method: 'ping'})
2611-
call assert_equal({'id': 22, 'jsonrpc': '2.0', 'result': 'alive'}, resp)
2609+
call assert_equal('alive', ch_evalexpr(ch, #{method: 'ping'}).result)
26122610

26132611
" Test for an empty payload
26142612
let resp = ch_evalexpr(ch, #{method: 'empty-payload', params: {}},
26152613
\ #{timeout: 200})
2616-
call assert_equal('', resp)
2614+
call assert_equal({}, resp)
26172615
" send a ping to make sure communication still works
2618-
let resp = ch_evalexpr(ch, #{method: 'ping'})
2619-
call assert_equal({'id': 24, 'jsonrpc': '2.0', 'result': 'alive'}, resp)
2616+
call assert_equal('alive', ch_evalexpr(ch, #{method: 'ping'}).result)
26202617

26212618
" Test for invoking an unsupported method
26222619
let resp = ch_evalexpr(ch, #{method: 'xyz', params: {}}, #{timeout: 200})
2623-
call assert_equal('', resp)
2620+
call assert_equal({}, resp)
26242621

26252622
" Test for sending a message without a callback function. Notification
26262623
" message should be dropped but RPC response should not be dropped.
26272624
call ch_setoptions(ch, #{callback: ''})
26282625
let g:lspNotif = []
26292626
call ch_sendexpr(ch, #{method: 'echo', params: #{s: 'no-callback'}})
26302627
" Send a ping to wait for all the notification messages to arrive
2631-
call ch_evalexpr(ch, #{method: 'ping'})
2628+
call assert_equal('alive', ch_evalexpr(ch, #{method: 'ping'}).result)
26322629
call assert_equal([], g:lspNotif)
26332630
" Restore the callback function
26342631
call ch_setoptions(ch, #{callback: 'LspCb'})
26352632
let g:lspNotif = []
26362633
call ch_sendexpr(ch, #{method: 'echo', params: #{s: 'no-callback'}})
26372634
" Send a ping to wait for all the notification messages to arrive
2638-
call ch_evalexpr(ch, #{method: 'ping'})
2635+
call assert_equal('alive', ch_evalexpr(ch, #{method: 'ping'}).result)
26392636
call assert_equal([#{jsonrpc: '2.0', result:
26402637
\ #{method: 'echo', jsonrpc: '2.0', params: #{s: 'no-callback'}}}],
26412638
\ g:lspNotif)

src/version.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -746,6 +746,8 @@ static char *(features[]) =
746746

747747
static int included_patches[] =
748748
{ /* Add new patch number below this line */
749+
/**/
750+
4758,
749751
/**/
750752
4757,
751753
/**/

0 commit comments

Comments
 (0)