# HG changeset patch # User Christian Brabandt # Date 1454268604 -3600 # Node ID 6b0891de44a993e268985c3969f1fd9a4d26f15c # Parent e18a688b2e3bbc77ed0da0af7fc3c7302ebb4456 commit https://github.com/vim/vim/commit/fb1f62691eae7c79a28b3b17a60e72ce198c71a2 Author: Bram Moolenaar Date: Sun Jan 31 20:24:32 2016 +0100 patch 7.4.1229 Problem: "eval" and "expr" channel commands don't work yet. Solution: Implement them. Update the error numbers. Also add "redraw". diff --git a/runtime/doc/channel.txt b/runtime/doc/channel.txt --- a/runtime/doc/channel.txt +++ b/runtime/doc/channel.txt @@ -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 emp 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)"] + +You can also use "call |feedkeys()|" to insert any key sequence. + -The "normal" command is executed like with |:normal|. +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 will result in sending back the result of the expression: +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* diff --git a/src/channel.c b/src/channel.c --- a/src/channel.c +++ b/src/channel.c @@ -293,14 +293,14 @@ channel_open(char *hostname, int port_in 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 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 { 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 { /* 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 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 } /* - * 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_ 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 *callbac 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) + 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) { - gui_update_cursor(FALSE, FALSE); - gui_mch_flush(); + 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); } -#endif + 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")); } } diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -16897,8 +16897,6 @@ f_sendexpr(typval_T *argvars, typval_T * { 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->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) - 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); + text = json_encode_nr_expr(channel_get_id(), &argvars[1]); + if (text == NULL) + return; ch_idx = send_common(argvars, text, "sendexpr"); if (ch_idx >= 0) @@ -16929,7 +16917,7 @@ f_sendexpr(typval_T *argvars, typval_T * 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); } } diff --git a/src/ex_docmd.c b/src/ex_docmd.c --- a/src/ex_docmd.c +++ b/src/ex_docmd.c @@ -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; diff --git a/src/json.c b/src/json.c --- a/src/json.c +++ b/src/json.c @@ -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) { diff --git a/src/proto/channel.pro b/src/proto/channel.pro --- a/src/proto/channel.pro +++ b/src/proto/channel.pro @@ -4,7 +4,7 @@ int channel_open(char *hostname, int por 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); diff --git a/src/proto/ex_docmd.pro b/src/proto/ex_docmd.pro --- a/src/proto/ex_docmd.pro +++ b/src/proto/ex_docmd.pro @@ -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); diff --git a/src/proto/json.pro b/src/proto/json.pro --- a/src/proto/json.pro +++ b/src/proto/json.pro @@ -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 : */ diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -743,6 +743,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1229, +/**/ 1228, /**/ 1227,