changeset 7864:6b0891de44a9 v7.4.1229

commit https://github.com/vim/vim/commit/fb1f62691eae7c79a28b3b17a60e72ce198c71a2 Author: Bram Moolenaar <Bram@vim.org> 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".
author Christian Brabandt <cb@256bit.org>
date Sun, 31 Jan 2016 20:30:04 +0100
parents e18a688b2e3b
children c6322fd49aeb
files runtime/doc/channel.txt src/channel.c src/eval.c src/ex_docmd.c src/json.c src/proto/channel.pro src/proto/ex_docmd.pro src/proto/json.pro src/version.c
diffstat 9 files changed, 208 insertions(+), 79 deletions(-) [+]
line wrap: on
line diff
--- 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*
--- 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"));
 	}
     }
 
--- 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);
 	}
     }
--- 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;
--- 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)
 {
--- 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);
--- 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);
--- 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 : */
--- 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,