0
0
mirror of https://github.com/vim/vim.git synced 2025-07-26 11:04:33 -04:00

patch 7.4.1373

Problem:    Calling a Vim function over a channel requires turning the
            arguments into a string.
Solution:   Add the "call" command. (Damien)  Also merge "expr" and "eval"
            into one.
This commit is contained in:
Bram Moolenaar 2016-02-20 21:39:05 +01:00
parent 6f3a544228
commit ece61b06ef
4 changed files with 102 additions and 46 deletions

View File

@ -1080,28 +1080,28 @@ channel_get_json(channel_T *channel, int part, int id, typval_T **rettv)
return FAIL; return FAIL;
} }
#define CH_JSON_MAX_ARGS 4
/* /*
* Execute a command received over "channel"/"part" * Execute a command received over "channel"/"part"
* "cmd" is the command string, "arg2" the second argument. * "argv[0]" is the command string.
* "arg3" is the third argument, NULL if missing. * "argv[1]" etc. have further arguments, type is VAR_UNKNOWN if missing.
*/ */
static void static void
channel_exe_cmd( channel_exe_cmd(channel_T *channel, int part, typval_T *argv)
channel_T *channel,
int part,
char_u *cmd,
typval_T *arg2,
typval_T *arg3)
{ {
char_u *cmd = argv[0].vval.v_string;
char_u *arg; char_u *arg;
int options = channel->ch_part[part].ch_mode == MODE_JS ? JSON_JS : 0;
if (arg2->v_type != VAR_STRING) if (argv[1].v_type != VAR_STRING)
{ {
ch_error(channel, "received command with non-string argument");
if (p_verbose > 2) if (p_verbose > 2)
EMSG("E903: received ex command with non-string argument"); EMSG("E903: received command with non-string argument");
return; return;
} }
arg = arg2->vval.v_string; arg = argv[1].vval.v_string;
if (arg == NULL) if (arg == NULL)
arg = (char_u *)""; arg = (char_u *)"";
@ -1135,31 +1135,46 @@ channel_exe_cmd(
} }
#endif #endif
} }
else if (STRCMP(cmd, "expr") == 0 || STRCMP(cmd, "eval") == 0) else if (STRCMP(cmd, "expr") == 0 || STRCMP(cmd, "call") == 0)
{ {
int is_eval = cmd[1] == 'v'; int is_call = cmd[0] == 'c';
int id_idx = is_call ? 3 : 2;
if (is_eval && (arg3 == NULL || arg3->v_type != VAR_NUMBER)) if (argv[id_idx].v_type != VAR_UNKNOWN
&& argv[id_idx].v_type != VAR_NUMBER)
{ {
ch_error(channel, "last argument for expr/call must be a number");
if (p_verbose > 2) if (p_verbose > 2)
EMSG("E904: third argument for eval must be a number"); EMSG("E904: last argument for expr/call must be a number");
}
else if (is_call && argv[2].v_type != VAR_LIST)
{
ch_error(channel, "third argument for call must be a list");
if (p_verbose > 2)
EMSG("E904: third argument for call must be a list");
} }
else else
{ {
typval_T *tv; typval_T *tv;
typval_T res_tv;
typval_T err_tv; typval_T err_tv;
char_u *json = NULL; char_u *json = NULL;
int options = channel->ch_part[part].ch_mode == MODE_JS
? JSON_JS : 0;
/* Don't pollute the display with errors. */ /* Don't pollute the display with errors. */
++emsg_skip; ++emsg_skip;
if (!is_call)
tv = eval_expr(arg, NULL); tv = eval_expr(arg, NULL);
if (is_eval) else if (func_call(arg, &argv[2], NULL, &res_tv) == OK)
tv = &res_tv;
else
tv = NULL;
if (argv[id_idx].v_type == VAR_NUMBER)
{ {
int id = argv[id_idx].vval.v_number;
if (tv != NULL) if (tv != NULL)
json = json_encode_nr_expr(arg3->vval.v_number, tv, json = json_encode_nr_expr(id, tv, options);
options);
if (tv == NULL || (json != NULL && *json == NUL)) if (tv == NULL || (json != NULL && *json == NUL))
{ {
/* If evaluation failed or the result can't be encoded /* If evaluation failed or the result can't be encoded
@ -1169,23 +1184,29 @@ channel_exe_cmd(
err_tv.v_type = VAR_STRING; err_tv.v_type = VAR_STRING;
err_tv.vval.v_string = (char_u *)"ERROR"; err_tv.vval.v_string = (char_u *)"ERROR";
tv = &err_tv; tv = &err_tv;
json = json_encode_nr_expr(arg3->vval.v_number, tv, json = json_encode_nr_expr(id, tv, options);
options);
} }
if (json != NULL) if (json != NULL)
{ {
channel_send(channel, part, json, "eval"); channel_send(channel,
part == PART_SOCK ? PART_SOCK : PART_IN,
json, (char *)cmd);
vim_free(json); vim_free(json);
} }
} }
--emsg_skip; --emsg_skip;
if (tv != &err_tv) if (tv == &res_tv)
clear_tv(tv);
else if (tv != &err_tv)
free_tv(tv); free_tv(tv);
} }
} }
else if (p_verbose > 2) else if (p_verbose > 2)
{
ch_errors(channel, "Receved unknown command: %s", (char *)cmd);
EMSG2("E905: received unknown command: %s", cmd); EMSG2("E905: received unknown command: %s", cmd);
} }
}
/* /*
* Invoke a callback for "channel"/"part" if needed. * Invoke a callback for "channel"/"part" if needed.
@ -1196,9 +1217,7 @@ may_invoke_callback(channel_T *channel, int part)
{ {
char_u *msg = NULL; char_u *msg = NULL;
typval_T *listtv = NULL; typval_T *listtv = NULL;
list_T *list; typval_T argv[CH_JSON_MAX_ARGS];
typval_T *typetv;
typval_T argv[3];
int seq_nr = -1; int seq_nr = -1;
ch_mode_T ch_mode = channel->ch_part[part].ch_mode; ch_mode_T ch_mode = channel->ch_part[part].ch_mode;
char_u *callback = NULL; char_u *callback = NULL;
@ -1214,6 +1233,9 @@ may_invoke_callback(channel_T *channel, int part)
if (ch_mode == MODE_JSON || ch_mode == MODE_JS) if (ch_mode == MODE_JSON || ch_mode == MODE_JS)
{ {
listitem_T *item;
int argc = 0;
/* Get any json message in the queue. */ /* Get any json message in the queue. */
if (channel_get_json(channel, part, -1, &listtv) == FAIL) if (channel_get_json(channel, part, -1, &listtv) == FAIL)
{ {
@ -1223,31 +1245,32 @@ may_invoke_callback(channel_T *channel, int part)
return FALSE; return FALSE;
} }
list = listtv->vval.v_list; for (item = listtv->vval.v_list->lv_first;
argv[1] = list->lv_first->li_next->li_tv; item != NULL && argc < CH_JSON_MAX_ARGS;
typetv = &list->lv_first->li_tv; item = item->li_next)
if (typetv->v_type == VAR_STRING) argv[argc++] = item->li_tv;
{ while (argc < CH_JSON_MAX_ARGS)
typval_T *arg3 = NULL; argv[argc++].v_type = VAR_UNKNOWN;
char_u *cmd = typetv->vval.v_string;
/* ["cmd", arg] or ["cmd", arg, arg] */ if (argv[0].v_type == VAR_STRING)
if (list->lv_len == 3) {
arg3 = &list->lv_last->li_tv; char_u *cmd = argv[0].vval.v_string;
/* ["cmd", arg] or ["cmd", arg, arg] or ["cmd", arg, arg, arg] */
ch_logs(channel, "Executing %s command", (char *)cmd); ch_logs(channel, "Executing %s command", (char *)cmd);
channel_exe_cmd(channel, part, cmd, &argv[1], arg3); channel_exe_cmd(channel, part, argv);
free_tv(listtv); free_tv(listtv);
return TRUE; return TRUE;
} }
if (typetv->v_type != VAR_NUMBER) if (argv[0].v_type != VAR_NUMBER)
{ {
ch_error(channel, ch_error(channel,
"Dropping message with invalid sequence number type"); "Dropping message with invalid sequence number type");
free_tv(listtv); free_tv(listtv);
return FALSE; return FALSE;
} }
seq_nr = typetv->vval.v_number; seq_nr = argv[0].vval.v_number;
} }
else if (channel_peek(channel, part) == NULL) else if (channel_peek(channel, part) == NULL)
{ {

View File

@ -78,26 +78,26 @@ class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):
response = "ok" response = "ok"
elif decoded[1] == 'eval-works': elif decoded[1] == 'eval-works':
# Send an eval request. We ignore the response. # Send an eval request. We ignore the response.
cmd = '["eval","\\"foo\\" . 123", -1]' cmd = '["expr","\\"foo\\" . 123", -1]'
print("sending: {}".format(cmd)) print("sending: {}".format(cmd))
self.request.sendall(cmd.encode('utf-8')) self.request.sendall(cmd.encode('utf-8'))
response = "ok" response = "ok"
elif decoded[1] == 'eval-fails': elif decoded[1] == 'eval-fails':
# Send an eval request that will fail. # Send an eval request that will fail.
cmd = '["eval","xxx", -2]' cmd = '["expr","xxx", -2]'
print("sending: {}".format(cmd)) print("sending: {}".format(cmd))
self.request.sendall(cmd.encode('utf-8')) self.request.sendall(cmd.encode('utf-8'))
response = "ok" response = "ok"
elif decoded[1] == 'eval-error': elif decoded[1] == 'eval-error':
# Send an eval request that works but the result can't # Send an eval request that works but the result can't
# be encoded. # be encoded.
cmd = '["eval","function(\\"tr\\")", -3]' cmd = '["expr","function(\\"tr\\")", -3]'
print("sending: {}".format(cmd)) print("sending: {}".format(cmd))
self.request.sendall(cmd.encode('utf-8')) self.request.sendall(cmd.encode('utf-8'))
response = "ok" response = "ok"
elif decoded[1] == 'eval-bad': elif decoded[1] == 'eval-bad':
# Send an eval request missing the third argument. # Send an eval request missing the third argument.
cmd = '["eval","xxx"]' cmd = '["expr","xxx"]'
print("sending: {}".format(cmd)) print("sending: {}".format(cmd))
self.request.sendall(cmd.encode('utf-8')) self.request.sendall(cmd.encode('utf-8'))
response = "ok" response = "ok"
@ -107,6 +107,11 @@ class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):
print("sending: {}".format(cmd)) print("sending: {}".format(cmd))
self.request.sendall(cmd.encode('utf-8')) self.request.sendall(cmd.encode('utf-8'))
response = "ok" response = "ok"
elif decoded[1] == 'call-func':
cmd = '["call","MyFunction",[1,2,3], 0]'
print("sending: {}".format(cmd))
self.request.sendall(cmd.encode('utf-8'))
response = "ok"
elif decoded[1] == 'redraw': elif decoded[1] == 'redraw':
cmd = '["redraw",""]' cmd = '["redraw",""]'
print("sending: {}".format(cmd)) print("sending: {}".format(cmd))
@ -135,6 +140,9 @@ class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):
print("sending: {}".format(cmd)) print("sending: {}".format(cmd))
self.request.sendall(cmd.encode('utf-8')) self.request.sendall(cmd.encode('utf-8'))
response = "" response = ""
elif decoded[1] == 'wait a bit':
time.sleep(0.2)
response = "waited"
elif decoded[1] == '!quit!': elif decoded[1] == '!quit!':
# we're done # we're done
self.server.shutdown() self.server.shutdown()

View File

@ -431,3 +431,26 @@ func Test_open_delay()
" The server will wait half a second before creating the port. " The server will wait half a second before creating the port.
call s:run_server('s:open_delay', 'delay') call s:run_server('s:open_delay', 'delay')
endfunc endfunc
"""""""""
function MyFunction(a,b,c)
let s:call_ret = [a:a, a:b, a:c]
endfunc
function s:test_call(port)
let handle = ch_open('localhost:' . a:port, s:chopt)
if ch_status(handle) == "fail"
call assert_false(1, "Can't open channel")
return
endif
call assert_equal('ok', ch_sendexpr(handle, 'call-func'))
sleep 20m
call assert_equal([1, 2, 3], s:call_ret)
endfunc
func Test_call()
call ch_log('Test_call()')
call s:run_server('s:test_call')
endfunc

View File

@ -747,6 +747,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
1373,
/**/ /**/
1372, 1372,
/**/ /**/