patch 9.2.0997: ch_sendexpr() cannot answer a request named with a string id
Problem: A request that a server named with a string cannot be
answered, since ch_sendexpr() rejects an "id" that is not a
Number.
Solution: Require a Number only when Vim has to match a reply, which
is with ch_evalexpr() or when a callback is given. Send the
message as it is otherwise (Hirohito Higashi).
fixes: #14091
fixes: #19003
closes: #21114
closes: #21126
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Hirohito Higashi <h.east.727@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
1 parent
4025cec645
commit
6308df41bf
5 files changed
+37
-17
No files matched your search
@@ -812,6 +812,13 @@ ch_sendexpr({handle}, {expr} [, {options}]) *ch_sendexpr()*
|
||||
If a response message is not expected for {expr}, then don't
|
||||
specify the "callback" item in {options}.
|
||||
|
||||
In "lsp" mode an "id" item in {expr} is sent as it is when no
|
||||
response is expected, which is how a request from the server
|
||||
is answered; it may be of any type the server used. When a
|
||||
response is expected, with |ch_evalexpr()| or with the
|
||||
"callback" item, Vim assigns the ID itself and the item must
|
||||
be a |Number|.
|
||||
|
||||
Can also be used as a |method|: >
|
||||
GetChannel()->ch_sendexpr(expr)
|
||||
<
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
*version9.txt* For Vim version 9.2. Last change: 2026 Aug 20
|
||||
*version9.txt* For Vim version 9.2. Last change: 2026 Aug 23
|
||||
|
||||
|
||||
VIM REFERENCE MANUAL by Bram Moolenaar
|
||||
@@ -52705,7 +52705,8 @@ Changed ~
|
||||
- |extend()| and |extendnew()| also accept |Blobs|.
|
||||
- events like |CursorMoved| are no longer triggered for what happened while
|
||||
they were ignored with 'eventignore'.
|
||||
|
||||
- |ch_sendexpr()| accepts a non-Number "id" in "lsp" mode when no response is
|
||||
expected, so a request from the server can be answered.
|
||||
|
||||
*added-9.3*
|
||||
Added ~
|
||||
|
||||
+12
-13
@@ -5120,6 +5120,7 @@ ch_expr_common(typval_T *argvars, typval_T *rettv, int eval)
|
||||
{
|
||||
dict_T *d;
|
||||
dictitem_T *di;
|
||||
char *key = ch_mode == CH_MODE_LSP ? "id" : "seq";
|
||||
|
||||
// return an empty dict by default
|
||||
if (rettv_dict_alloc(rettv) == FAIL)
|
||||
@@ -5129,22 +5130,20 @@ ch_expr_common(typval_T *argvars, typval_T *rettv, int eval)
|
||||
return;
|
||||
|
||||
d = argvars[1].vval.v_dict;
|
||||
if (ch_mode == CH_MODE_LSP)
|
||||
di = dict_find(d, (char_u *)"id", -1);
|
||||
else
|
||||
di = dict_find(d, (char_u *)"seq", -1);
|
||||
if (di != NULL && di->di_tv.v_type != VAR_NUMBER)
|
||||
di = dict_find(d, (char_u *)key, -1);
|
||||
if (argvars[2].v_type == VAR_DICT
|
||||
&& dict_has_key(argvars[2].vval.v_dict, "callback"))
|
||||
callback_present = TRUE;
|
||||
|
||||
// The id is what a reply is matched by, so it must be a number when
|
||||
// one is waited for.
|
||||
if (di != NULL && di->di_tv.v_type != VAR_NUMBER
|
||||
&& (ch_mode == CH_MODE_DAP || eval || callback_present))
|
||||
{
|
||||
// only number type is supported for the 'id' or 'seq' item
|
||||
semsg(_(e_invalid_value_for_argument_str),
|
||||
ch_mode == CH_MODE_LSP ? "id" : "seq");
|
||||
semsg(_(e_invalid_value_for_argument_str), key);
|
||||
return;
|
||||
}
|
||||
|
||||
if (argvars[2].v_type == VAR_DICT)
|
||||
if (dict_has_key(argvars[2].vval.v_dict, "callback"))
|
||||
callback_present = TRUE;
|
||||
|
||||
if (ch_mode == CH_MODE_DAP)
|
||||
{
|
||||
// DAP message always has a sequence number (id)
|
||||
@@ -5169,7 +5168,7 @@ ch_expr_common(typval_T *argvars, typval_T *rettv, int eval)
|
||||
// When sending an expression, if the message has an 'id' item,
|
||||
// then use it.
|
||||
id = 0;
|
||||
if (di != NULL)
|
||||
if (di != NULL && di->di_tv.v_type == VAR_NUMBER)
|
||||
id = di->di_tv.vval.v_number;
|
||||
}
|
||||
if (ch_mode == CH_MODE_LSP && !dict_has_key(d, "jsonrpc"))
|
||||
|
||||
@@ -2809,9 +2809,20 @@ func LspTests(port)
|
||||
" \ 'result': {'method': 'echo', 'jsonrpc': '2.0',
|
||||
" \ 'params': {'m': 'raw-message'}}}], g:lspNotif)
|
||||
|
||||
" Test for sending a message with a String id, which is what answering a
|
||||
" request from a server that named it with one takes
|
||||
let g:lspNotif = []
|
||||
call ch_sendexpr(ch, #{method: 'echo', id: 'e8a1-4c2f',
|
||||
\ params: #{s: 'msg-with-string-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: 'e8a1-4c2f',
|
||||
\ params: #{s: 'msg-with-string-id'}}}], g:lspNotif)
|
||||
|
||||
" Invalid arguments to ch_evalexpr() and ch_sendexpr()
|
||||
call assert_fails('call ch_sendexpr(ch, #{method: "cookie", id: "cookie"})',
|
||||
\ 'E475:')
|
||||
call assert_fails('call ch_sendexpr(ch, #{method: "cookie", id: "cookie"},'
|
||||
\ .. ' #{callback: "LspOtCb"})', 'E475:')
|
||||
call assert_fails('call ch_evalexpr(ch, #{method: "ping", id: [{}]})', 'E475:')
|
||||
call assert_fails('call ch_evalexpr(ch, [1, 2, 3])', 'E1206:')
|
||||
call assert_fails('call ch_sendexpr(ch, "abc")', 'E1206:')
|
||||
|
||||
@@ -763,6 +763,8 @@ static char *(features[]) =
|
||||
|
||||
static int included_patches[] =
|
||||
{ /* Add new patch number below this line */
|
||||
/**/
|
||||
997,
|
||||
/**/
|
||||
996,
|
||||
/**/
|
||||
|
||||
Reference in new issue
Block a user