0
0
mirror of https://github.com/vim/vim.git synced 2025-09-23 03:43:49 -04:00

patch 7.4.1229

Problem:    "eval" and "expr" channel commands don't work yet.
Solution:   Implement them.  Update the error numbers.  Also add "redraw".
This commit is contained in:
Bram Moolenaar
2016-01-31 20:24:32 +01:00
parent 155500077c
commit fb1f62691e
9 changed files with 209 additions and 80 deletions

View File

@@ -1,4 +1,4 @@
*channel.txt* For Vim version 7.4. Last change: 2016 Jan 28
*channel.txt* For Vim version 7.4. Last change: 2016 Jan 31
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -48,10 +48,10 @@ And the response is:
The number will increase every time you send a message.
The server can send a command to Vim. Type this on T1 (literally, including
the quotes): >
NOT IMPLEMENTED YET
["ex","echo 'hi there'"]
And you should see the message in Vim.
the quotes):
["ex","echo 'hi there'"] ~
And you should see the message in Vim. You can move the cursor a word forward:
["normal","w"] ~
To handle asynchronous communication a callback needs to be used: >
func MyHandler(handle, msg)
@@ -100,6 +100,14 @@ When {callback} is empty (zero or an empty string) the handler is removed.
Once done with the channel, disconnect it like this: >
call disconnect(handle)
Currently up to 10 channels can be in use at the same time. *E897*
When the channel can't be opened you will get an error message.
*E898* *E899* *E900* *E901* *E902*
If there is an error reading or writing a channel it will be closed.
*E896* *E630* *E631*
==============================================================================
3. Using a JSON channel *channel-use*
@@ -146,36 +154,77 @@ The channel will then be inactive.
==============================================================================
4. Vim commands *channel-commands*
NOT IMPLEMENTED YET
PARTLY IMPLEMENTED: only "ex" and "normal" work
With a "json" channel the process can send commands to Vim that will be
handled by Vim internally, it does not require a handler for the channel.
Possible commands are:
Possible commands are: *E903* *E904* *E905*
["redraw" {forced}]
["ex", {Ex command}]
["normal", {Normal mode command}]
["eval", {number}, {expression}]
["eval", {expression}, {number}]
["expr", {expression}]
With all of these: Be careful what these commands do! You can easily
interfere with what the user is doing. To avoid trouble use |mode()| to check
that the editor is in the expected state. E.g., to send keys that must be
inserted as text, not executed as a command: >
["ex","if mode() == 'i' | call feedkeys('ClassName') | endif"]
inserted as text, not executed as a command:
["ex","if mode() == 'i' | call feedkeys('ClassName') | endif"] ~
Errors in these commands are normally not reported to avoid them messing up
the display. If you do want to see them, set the 'verbose' option to 3 or
higher.
Command "redraw" ~
The other commands do not update the screen, so that you can send a sequence
of commands without the cursor moving around. You must end with the "redraw"
command to show any changed text and show the cursor where it belongs.
The argument is normally an empty string:
["redraw", ""] ~
To first clear the screen pass "force":
["redraw", "force"] ~
Command "ex" ~
The "ex" command is executed as any Ex command. There is no response for
completion or error. You could use functions in an |autoload| script.
You can also invoke |feedkeys()| to insert anything.
completion or error. You could use functions in an |autoload| script:
["ex","call myscript#MyFunc(arg)"]
The "normal" command is executed like with |:normal|.
You can also use "call |feedkeys()|" to insert any key sequence.
The "eval" command will result in sending back the result of the expression:
Command "normal" ~
The "normal" command is executed like with |:normal!|, commands are not
mapped. Example to open the folds under the cursor:
["normal" "zO"]
Command "eval" ~
The "eval" command an be used to get the result of an expression. For
example, to get the number of lines in the current buffer:
["eval","line('$')"] ~
it will send back the result of the expression:
[{number}, {result}]
Here {number} is the same as what was in the request.
Here {number} is the same as what was in the request. Use a negative number
to avoid confusion with message that Vim sends.
The "expr" command is similar, but does not send back any response.
{result} is the result of the evaluation and is JSON encoded. If the
evaluation fails it is the string "ERROR".
Command "expr" ~
The "expr" command is similar to "eval", but does not send back any response.
Example:
["expr","setline('$', ['one', 'two', 'three'])"]
["expr","setline('$', ['one', 'two', 'three'])"] ~
==============================================================================
5. Using a raw channel *channel-raw*

View File

@@ -293,14 +293,14 @@ channel_open(char *hostname, int port_in, void (*close_cb)(void))
if (idx < 0)
{
CHERROR("All channels are in use\n", "");
EMSG(_("E999: All channels are in use"));
EMSG(_("E897: All channels are in use"));
return -1;
}
if ((sd = (sock_T)socket(AF_INET, SOCK_STREAM, 0)) == (sock_T)-1)
{
CHERROR("error in socket() in channel_open()\n", "");
PERROR("E999: socket() in channel_open()");
PERROR("E898: socket() in channel_open()");
return -1;
}
@@ -312,7 +312,7 @@ channel_open(char *hostname, int port_in, void (*close_cb)(void))
if ((host = gethostbyname(hostname)) == NULL)
{
CHERROR("error in gethostbyname() in channel_open()\n", "");
PERROR("E999: gethostbyname() in channel_open()");
PERROR("E901: gethostbyname() in channel_open()");
sock_close(sd);
return -1;
}
@@ -330,7 +330,7 @@ channel_open(char *hostname, int port_in, void (*close_cb)(void))
{
SOCK_ERRNO;
CHERROR("socket() retry in channel_open()\n", "");
PERROR("E999: socket() retry in channel_open()");
PERROR("E900: socket() retry in channel_open()");
return -1;
}
if (connect(sd, (struct sockaddr *)&server, sizeof(server)))
@@ -362,7 +362,7 @@ channel_open(char *hostname, int port_in, void (*close_cb)(void))
{
/* Get here when the server can't be found. */
CHERROR("Cannot connect to port after retry\n", "");
PERROR(_("E999: Cannot connect to port after retry2"));
PERROR(_("E899: Cannot connect to port after retry2"));
sock_close(sd);
return -1;
}
@@ -371,7 +371,7 @@ channel_open(char *hostname, int port_in, void (*close_cb)(void))
else
{
CHERROR("Cannot connect to port\n", "");
PERROR(_("E999: Cannot connect to port"));
PERROR(_("E902: Cannot connect to port"));
sock_close(sd);
return -1;
}
@@ -418,13 +418,15 @@ channel_set_req_callback(int idx, char_u *callback)
}
/*
* Decode JSON "msg", which must have the form "[expr1, expr2]".
* Decode JSON "msg", which must have the form "[expr1, expr2, expr3]".
* Put "expr1" in "tv1".
* Put "expr2" in "tv2".
* Put "expr3" in "tv3". If "tv3" is NULL there is no "expr3".
*
* Return OK or FAIL.
*/
int
channel_decode_json(char_u *msg, typval_T *tv1, typval_T *tv2)
channel_decode_json(char_u *msg, typval_T *tv1, typval_T *tv2, typval_T *tv3)
{
js_read_T reader;
typval_T listtv;
@@ -434,16 +436,31 @@ channel_decode_json(char_u *msg, typval_T *tv1, typval_T *tv2)
reader.js_used = 0;
json_decode(&reader, &listtv);
if (listtv.v_type == VAR_LIST && listtv.vval.v_list->lv_len == 2)
if (listtv.v_type == VAR_LIST)
{
/* Move the item from the list and then change the type to avoid the
* item being freed. */
*tv1 = listtv.vval.v_list->lv_first->li_tv;
listtv.vval.v_list->lv_first->li_tv.v_type = VAR_NUMBER;
*tv2 = listtv.vval.v_list->lv_last->li_tv;
listtv.vval.v_list->lv_last->li_tv.v_type = VAR_NUMBER;
list_unref(listtv.vval.v_list);
return OK;
list_T *list = listtv.vval.v_list;
if (list->lv_len == 2 || (tv3 != NULL && list->lv_len == 3))
{
/* Move the item from the list and then change the type to avoid the
* item being freed. */
*tv1 = list->lv_first->li_tv;
list->lv_first->li_tv.v_type = VAR_NUMBER;
*tv2 = list->lv_first->li_next->li_tv;
list->lv_first->li_next->li_tv.v_type = VAR_NUMBER;
if (tv3 != NULL)
{
if (list->lv_len == 3)
{
*tv3 = list->lv_last->li_tv;
list->lv_last->li_tv.v_type = VAR_NUMBER;
}
else
tv3->v_type = VAR_UNKNOWN;
}
list_unref(list);
return OK;
}
}
/* give error message? */
@@ -472,44 +489,86 @@ invoke_callback(int idx, char_u *callback, typval_T *argv)
out_flush();
}
/*
* Execute a command received over channel "idx".
* "cmd" is the command string, "arg2" the second argument.
* "arg3" is the third argument, NULL if missing.
*/
static void
channel_exe_cmd(char_u *cmd, typval_T *arg)
channel_exe_cmd(int idx, char_u *cmd, typval_T *arg2, typval_T *arg3)
{
char_u *arg;
if (arg2->v_type != VAR_STRING)
{
if (p_verbose > 2)
EMSG("E903: received ex command with non-string argument");
return;
}
arg = arg2->vval.v_string;
if (STRCMP(cmd, "ex") == 0)
{
if (arg->v_type == VAR_STRING)
do_cmdline_cmd(arg->vval.v_string);
else if (p_verbose > 2)
EMSG("E999: received ex command with non-string argument");
do_cmdline_cmd(arg);
}
else if (STRCMP(cmd, "normal") == 0)
{
if (arg->v_type == VAR_STRING)
{
exarg_T ea;
exarg_T ea;
ea.arg = arg->vval.v_string;
ea.addr_count = 0;
ea.forceit = TRUE; /* no mapping */
ex_normal(&ea);
ea.arg = arg;
ea.addr_count = 0;
ea.forceit = TRUE; /* no mapping */
ex_normal(&ea);
}
else if (STRCMP(cmd, "redraw") == 0)
{
exarg_T ea;
update_screen(0);
showruler(FALSE);
setcursor();
out_flush();
ea.forceit = *arg != NUL;
ex_redraw(&ea);
showruler(FALSE);
setcursor();
out_flush();
#ifdef FEAT_GUI
if (gui.in_use)
{
gui_update_cursor(FALSE, FALSE);
gui_mch_flush();
}
#endif
if (gui.in_use)
{
gui_update_cursor(FALSE, FALSE);
gui_mch_flush();
}
#endif
}
else if (STRCMP(cmd, "expr") == 0 || STRCMP(cmd, "eval") == 0)
{
int is_eval = cmd[1] == 'v';
if (is_eval && arg3->v_type != VAR_NUMBER)
{
if (p_verbose > 2)
EMSG("E904: third argument for eval must be a number");
}
else
{
typval_T *tv = eval_expr(arg, NULL);
typval_T err_tv;
char_u *json;
if (is_eval)
{
if (tv == NULL)
{
err_tv.v_type = VAR_STRING;
err_tv.vval.v_string = (char_u *)"ERROR";
tv = &err_tv;
}
json = json_encode_nr_expr(arg3->vval.v_number, tv);
channel_send(idx, json, "eval");
vim_free(json);
}
free_tv(tv);
}
else if (p_verbose > 2)
EMSG("E999: received normal command with non-string argument");
}
else if (p_verbose > 2)
EMSG2("E999: received unknown command: %s", cmd);
EMSG2("E905: received unknown command: %s", cmd);
}
/*
@@ -521,6 +580,7 @@ may_invoke_callback(int idx)
char_u *msg;
typval_T typetv;
typval_T argv[3];
typval_T arg3;
char_u *cmd = NULL;
int seq_nr = -1;
int ret = OK;
@@ -537,9 +597,10 @@ may_invoke_callback(int idx)
if (channels[idx].ch_json_mode)
{
ret = channel_decode_json(msg, &typetv, &argv[1]);
ret = channel_decode_json(msg, &typetv, &argv[1], &arg3);
if (ret == OK)
{
/* TODO: error if arg3 is set when it shouldn't? */
if (typetv.v_type == VAR_STRING)
cmd = typetv.vval.v_string;
else if (typetv.v_type == VAR_NUMBER)
@@ -556,7 +617,7 @@ may_invoke_callback(int idx)
{
if (cmd != NULL)
{
channel_exe_cmd(cmd, &argv[1]);
channel_exe_cmd(idx, cmd, &argv[1], &arg3);
}
else if (channels[idx].ch_req_callback != NULL && seq_nr != 0)
{
@@ -576,6 +637,7 @@ may_invoke_callback(int idx)
{
clear_tv(&typetv);
clear_tv(&argv[1]);
clear_tv(&arg3);
}
}
@@ -874,7 +936,7 @@ channel_read(int idx)
{
/* Todo: which channel? */
CHERROR("%s(): cannot from channel\n", "channel_read");
PERROR(_("E999: read from channel"));
PERROR(_("E896: read from channel"));
}
}

View File

@@ -16897,8 +16897,6 @@ f_sendexpr(typval_T *argvars, typval_T *rettv)
{
char_u *text;
char_u *resp;
typval_T nrtv;
typval_T listtv;
typval_T typetv;
int ch_idx;
@@ -16906,19 +16904,9 @@ f_sendexpr(typval_T *argvars, typval_T *rettv)
rettv->v_type = VAR_STRING;
rettv->vval.v_string = NULL;
nrtv.v_type = VAR_NUMBER;
nrtv.vval.v_number = channel_get_id();
if (rettv_list_alloc(&listtv) == FAIL)
text = json_encode_nr_expr(channel_get_id(), &argvars[1]);
if (text == NULL)
return;
if (list_append_tv(listtv.vval.v_list, &nrtv) == FAIL
|| list_append_tv(listtv.vval.v_list, &argvars[1]) == FAIL)
{
list_unref(listtv.vval.v_list);
return;
}
text = json_encode(&listtv);
list_unref(listtv.vval.v_list);
ch_idx = send_common(argvars, text, "sendexpr");
if (ch_idx >= 0)
@@ -16929,7 +16917,7 @@ f_sendexpr(typval_T *argvars, typval_T *rettv)
resp = channel_read_block(ch_idx);
if (resp != NULL)
{
channel_decode_json(resp, &typetv, rettv);
channel_decode_json(resp, &typetv, rettv, NULL);
vim_free(resp);
}
}

View File

@@ -335,7 +335,6 @@ static void ex_rundo(exarg_T *eap);
static void ex_redo(exarg_T *eap);
static void ex_later(exarg_T *eap);
static void ex_redir(exarg_T *eap);
static void ex_redraw(exarg_T *eap);
static void ex_redrawstatus(exarg_T *eap);
static void close_redir(void);
static void ex_mkrc(exarg_T *eap);
@@ -9466,7 +9465,7 @@ ex_redir(exarg_T *eap)
/*
* ":redraw": force redraw
*/
static void
void
ex_redraw(exarg_T *eap)
{
int r = RedrawingDisabled;

View File

@@ -33,6 +33,33 @@ json_encode(typval_T *val)
return ga.ga_data;
}
/*
* Encode ["nr", "val"] into a JSON format string.
* Returns NULL when out of memory.
*/
char_u *
json_encode_nr_expr(int nr, typval_T *val)
{
typval_T listtv;
typval_T nrtv;
char_u *text;
nrtv.v_type = VAR_NUMBER;
nrtv.vval.v_number = nr;
if (rettv_list_alloc(&listtv) == FAIL)
return NULL;
if (list_append_tv(listtv.vval.v_list, &nrtv) == FAIL
|| list_append_tv(listtv.vval.v_list, val) == FAIL)
{
list_unref(listtv.vval.v_list);
return NULL;
}
text = json_encode(&listtv);
list_unref(listtv.vval.v_list);
return text;
}
static void
write_string(garray_T *gap, char_u *str)
{

View File

@@ -4,7 +4,7 @@ int channel_open(char *hostname, int port_in, void (*close_cb)(void));
void channel_set_json_mode(int idx, int json_mode);
void channel_set_callback(int idx, char_u *callback);
void channel_set_req_callback(int idx, char_u *callback);
int channel_decode_json(char_u *msg, typval_T *tv1, typval_T *tv2);
int channel_decode_json(char_u *msg, typval_T *tv1, typval_T *tv2, typval_T *tv3);
int channel_is_open(int idx);
void channel_close(int idx);
int channel_save(int idx, char_u *buf, int len);

View File

@@ -46,6 +46,7 @@ void post_chdir(int local);
void ex_cd(exarg_T *eap);
void do_sleep(long msec);
void ex_may_print(exarg_T *eap);
void ex_redraw(exarg_T *eap);
int vim_mkdir_emsg(char_u *name, int prot);
FILE *open_exfile(char_u *fname, int forceit, char *mode);
void update_topline_cursor(void);

View File

@@ -1,4 +1,5 @@
/* json.c */
char_u *json_encode(typval_T *val);
char_u *json_encode_nr_expr(int nr, typval_T *val);
void json_decode(js_read_T *reader, typval_T *res);
/* vim: set ft=c : */

View File

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