changeset 9969:176e34b0d678 v7.4.2258

commit https://github.com/vim/vim/commit/f1f0792e55e72cdc7c833b30f565a9b02f18bb1e Author: Bram Moolenaar <Bram@vim.org> Date: Fri Aug 26 17:58:53 2016 +0200 patch 7.4.2258 Problem: Two JSON messages are sent without a separator. Solution: Separate messages with a NL. (closes https://github.com/vim/vim/issues/1001)
author Christian Brabandt <cb@256bit.org>
date Fri, 26 Aug 2016 18:00:08 +0200
parents 7171d7dd51b0
children c57383365947
files runtime/doc/channel.txt src/channel.c src/json.c src/testdir/test_channel.py src/testdir/test_channel.vim src/version.c src/vim.h
diffstat 7 files changed, 53 insertions(+), 18 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 Jul 15
+*channel.txt*      For Vim version 7.4.  Last change: 2016 Aug 26
 
 
 		  VIM REFERENCE MANUAL	  by Bram Moolenaar
@@ -247,9 +247,15 @@ In which {number} is different every tim
 This way Vim knows which sent message matches with which received message and
 can call the right handler.  Also when the messages arrive out of order.
 
+A newline character is terminating the JSON text.  This can be used to
+separate the read text.  For example, in Python:
+	splitidx = read_text.find('\n')
+	message = read_text[:splitidx]
+	rest = read_text[splitidx + 1:]
+
 The sender must always send valid JSON to Vim.  Vim can check for the end of
 the message by parsing the JSON.  It will only accept the message if the end
-was received.
+was received.  A newline after the message is optional.
 
 When the process wants to send a message to Vim without first receiving a
 message, it must use the number zero:
--- a/src/channel.c
+++ b/src/channel.c
@@ -2165,7 +2165,7 @@ channel_exe_cmd(channel_T *channel, int 
 		int id = argv[id_idx].vval.v_number;
 
 		if (tv != NULL)
-		    json = json_encode_nr_expr(id, tv, options);
+		    json = json_encode_nr_expr(id, tv, options | JSON_NL);
 		if (tv == NULL || (json != NULL && *json == NUL))
 		{
 		    /* If evaluation failed or the result can't be encoded
@@ -2175,7 +2175,7 @@ channel_exe_cmd(channel_T *channel, int 
 		    err_tv.v_type = VAR_STRING;
 		    err_tv.vval.v_string = (char_u *)"ERROR";
 		    tv = &err_tv;
-		    json = json_encode_nr_expr(id, tv, options);
+		    json = json_encode_nr_expr(id, tv, options | JSON_NL);
 		}
 		if (json != NULL)
 		{
@@ -3500,7 +3500,7 @@ ch_expr_common(typval_T *argvars, typval
 
     id = ++channel->ch_last_msg_id;
     text = json_encode_nr_expr(id, &argvars[1],
-					    ch_mode == MODE_JS ? JSON_JS : 0);
+				 (ch_mode == MODE_JS ? JSON_JS : 0) | JSON_NL);
     if (text == NULL)
 	return;
 
--- a/src/json.c
+++ b/src/json.c
@@ -23,9 +23,26 @@ static int json_decode_item(js_read_T *r
 
 /*
  * Encode "val" into a JSON format string.
+ * The result is added to "gap"
+ * Returns FAIL on failure and makes gap->ga_data empty.
+ */
+    static int
+json_encode_gap(garray_T *gap, typval_T *val, int options)
+{
+    if (json_encode_item(gap, val, get_copyID(), options) == FAIL)
+    {
+	ga_clear(gap);
+	gap->ga_data = vim_strsave((char_u *)"");
+	return FAIL;
+    }
+    return OK;
+}
+
+/*
+ * Encode "val" into a JSON format string.
  * The result is in allocated memory.
  * The result is empty when encoding fails.
- * "options" can be JSON_JS or zero;
+ * "options" can contain JSON_JS, JSON_NO_NONE and JSON_NL.
  */
     char_u *
 json_encode(typval_T *val, int options)
@@ -34,17 +51,13 @@ json_encode(typval_T *val, int options)
 
     /* Store bytes in the growarray. */
     ga_init2(&ga, 1, 4000);
-    if (json_encode_item(&ga, val, get_copyID(), options) == FAIL)
-    {
-	vim_free(ga.ga_data);
-	return vim_strsave((char_u *)"");
-    }
+    json_encode_gap(&ga, val, options);
     return ga.ga_data;
 }
 
 /*
  * Encode ["nr", "val"] into a JSON format string in allocated memory.
- * "options" can be JSON_JS or zero;
+ * "options" can contain JSON_JS, JSON_NO_NONE and JSON_NL.
  * Returns NULL when out of memory.
  */
     char_u *
@@ -52,7 +65,7 @@ json_encode_nr_expr(int nr, typval_T *va
 {
     typval_T	listtv;
     typval_T	nrtv;
-    char_u	*text;
+    garray_T	ga;
 
     nrtv.v_type = VAR_NUMBER;
     nrtv.vval.v_number = nr;
@@ -65,9 +78,11 @@ json_encode_nr_expr(int nr, typval_T *va
 	return NULL;
     }
 
-    text = json_encode(&listtv, options);
+    ga_init2(&ga, 1, 4000);
+    if (json_encode_gap(&ga, &listtv, options) == OK && (options & JSON_NL))
+	ga_append(&ga, '\n');
     list_unref(listtv.vval.v_list);
-    return text;
+    return ga.ga_data;
 }
 
     static void
--- a/src/testdir/test_channel.py
+++ b/src/testdir/test_channel.py
@@ -38,15 +38,15 @@ class ThreadedTCPRequestHandler(socketse
             print("received: {0}".format(received))
 
             # We may receive two messages at once. Take the part up to the
-            # matching "]" (recognized by finding "][").
+            # newline, which should be after the matching "]".
             todo = received
             while todo != '':
-                splitidx = todo.find('][')
+                splitidx = todo.find('\n')
                 if splitidx < 0:
                      used = todo
                      todo = ''
                 else:
-                     used = todo[:splitidx + 1]
+                     used = todo[:splitidx]
                      todo = todo[splitidx + 1:]
                 if used != received:
                     print("using: {0}".format(used))
--- a/src/testdir/test_channel.vim
+++ b/src/testdir/test_channel.vim
@@ -55,6 +55,17 @@ func Ch_communicate(port)
   call WaitFor('exists("g:split")')
   call assert_equal(123, g:split)
 
+  " string with ][ should work
+  call assert_equal('this][that', ch_evalexpr(handle, 'echo this][that'))
+
+  " sending three messages quickly then reading should work
+  for i in range(3)
+    call ch_sendexpr(handle, 'echo hello ' . i)
+  endfor
+  call assert_equal('hello 0', ch_read(handle)[1])
+  call assert_equal('hello 1', ch_read(handle)[1])
+  call assert_equal('hello 2', ch_read(handle)[1])
+
   " Request that triggers sending two ex commands.  These will usually be
   " handled before getting the response, but it's not guaranteed, thus wait a
   " tiny bit for the commands to get executed.
--- a/src/version.c
+++ b/src/version.c
@@ -764,6 +764,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    2258,
+/**/
     2257,
 /**/
     2256,
--- a/src/vim.h
+++ b/src/vim.h
@@ -2440,6 +2440,7 @@ typedef enum
 /* Options for json_encode() and json_decode. */
 #define JSON_JS		1   /* use JS instead of JSON */
 #define JSON_NO_NONE	2   /* v:none item not allowed */
+#define JSON_NL		4   /* append a NL */
 
 /* Used for flags of do_in_path() */
 #define DIP_ALL	    0x01	/* all matches, not just the first one */