From d9ea9069f5ef5b8b9f9e0d0daecdd124e2dcd818 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Tue, 2 Feb 2016 12:38:02 +0100 Subject: [PATCH 1/9] patch 7.4.1237 Problem: Can't translate message without adding a line break. Solution: Join the two parts of the message. --- src/memline.c | 3 +-- src/version.c | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/memline.c b/src/memline.c index 15810fdad0..969e1e292f 100644 --- a/src/memline.c +++ b/src/memline.c @@ -4075,8 +4075,7 @@ attention_message( } /* Some of these messages are long to allow translation to * other languages. */ - MSG_PUTS(_("\n(1) Another program may be editing the same file. If this is the case,\n be careful not to end up with two different instances of the same\n file when making changes.")); - MSG_PUTS(_(" Quit, or continue with caution.\n")); + MSG_PUTS(_("\n(1) Another program may be editing the same file. If this is the case,\n be careful not to end up with two different instances of the same\n file when making changes. Quit, or continue with caution.\n")); MSG_PUTS(_("(2) An edit session for this file crashed.\n")); MSG_PUTS(_(" If this is the case, use \":recover\" or \"vim -r ")); msg_outtrans(buf->b_fname); diff --git a/src/version.c b/src/version.c index 165b396c9f..53949e63d9 100644 --- a/src/version.c +++ b/src/version.c @@ -742,6 +742,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1237, /**/ 1236, /**/ From 56ead341a75e1a0395eee94a3280c67e2278a57e Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Tue, 2 Feb 2016 18:20:08 +0100 Subject: [PATCH 2/9] patch 7.4.1238 Problem: Can't handle two messages right after each other. Solution: Find the end of the JSON. Read more when incomplete. Add a C test for the JSON decoding. --- src/Makefile | 28 ++- src/channel.c | 3 +- src/eval.c | 4 +- src/json.c | 427 ++++++++++++++++++++++++++++++++------------- src/json_test.c | 193 ++++++++++++++++++++ src/memfile_test.c | 2 - src/proto/json.pro | 2 + src/structs.h | 14 +- src/version.c | 2 + 9 files changed, 535 insertions(+), 140 deletions(-) create mode 100644 src/json_test.c diff --git a/src/Makefile b/src/Makefile index 8fd59fb089..31664b6716 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1545,11 +1545,13 @@ EXTRA_SRC = hangulin.c if_lua.c if_mzsch.c auto/if_perl.c if_perlsfio.c \ $(GRESOURCE_SRC) # Unittest files +JSON_TEST_SRC = json_test.c +JSON_TEST_TARGET = json_test$(EXEEXT) MEMFILE_TEST_SRC = memfile_test.c MEMFILE_TEST_TARGET = memfile_test$(EXEEXT) -UNITTEST_SRC = $(MEMFILE_TEST_SRC) -UNITTEST_TARGETS = $(MEMFILE_TEST_TARGET) +UNITTEST_SRC = $(JSON_TEST_SRC) $(MEMFILE_TEST_SRC) +UNITTEST_TARGETS = $(JSON_TEST_TARGET) $(MEMFILE_TEST_TARGET) # All sources, also the ones that are not configured ALL_SRC = $(BASIC_SRC) $(ALL_GUI_SRC) $(UNITTEST_SRC) $(EXTRA_SRC) @@ -1588,7 +1590,6 @@ OBJ_COMMON = \ $(HANGULIN_OBJ) \ objects/if_cscope.o \ objects/if_xcmdsrv.o \ - objects/json.o \ objects/mark.o \ objects/memline.o \ objects/menu.o \ @@ -1914,6 +1915,7 @@ types.vim: $(TAGS_SRC) $(TAGS_INCL) ctags --c-kinds=gstu -o- $(TAGS_SRC) $(TAGS_INCL) |\ awk 'BEGIN{printf("syntax keyword Type\t")}\ {printf("%s ", $$1)}END{print ""}' > $@ + echo "syn keyword Constant OK FAIL TRUE FALSE MAYBE" >> $@ # Execute the test scripts. Run these after compiling Vim, before installing. # This doesn't depend on $(VIMTARGET), because that won't work when configure @@ -1948,6 +1950,12 @@ unittest unittests: $(UNITTEST_TARGETS) ./$$t || exit 1; echo $$t passed; \ done +run_json_test: $(JSON_TEST_TARGET) + ./$(JSON_TEST_TARGET) + +run_memfile_test: $(MEMFILE_TEST_TARGET) + ./$(MEMFILE_TEST_TARGET) + # Run individual OLD style test, assuming that Vim was already compiled. test1 \ test_autocmd_option \ @@ -2040,6 +2048,13 @@ testclean: # Unittests # It's build just like Vim to satisfy all dependencies. +$(JSON_TEST_TARGET): auto/config.mk objects $(JSON_TEST_OBJ) + $(CCC) version.c -o objects/version.o + @LINK="$(PURIFY) $(SHRPENV) $(CClink) $(ALL_LIB_DIRS) $(LDFLAGS) \ + -o $(JSON_TEST_TARGET) $(JSON_TEST_OBJ) $(ALL_LIBS)" \ + MAKE="$(MAKE)" LINK_AS_NEEDED=$(LINK_AS_NEEDED) \ + sh $(srcdir)/link.sh + $(MEMFILE_TEST_TARGET): auto/config.mk objects $(MEMFILE_TEST_OBJ) $(CCC) version.c -o objects/version.o @LINK="$(PURIFY) $(SHRPENV) $(CClink) $(ALL_LIB_DIRS) $(LDFLAGS) \ @@ -2811,6 +2826,9 @@ objects/integration.o: integration.c objects/json.o: json.c $(CCC) -o $@ json.c +objects/json_test.o: json_test.c + $(CCC) -o $@ json_test.c + objects/main.o: main.c $(CCC) -o $@ main.c @@ -3301,6 +3319,10 @@ objects/gui_at_fs.o: gui_at_fs.c vim.h auto/config.h feature.h os_unix.h \ objects/pty.o: pty.c vim.h auto/config.h feature.h os_unix.h auto/osdef.h ascii.h \ keymap.h term.h macros.h option.h structs.h regexp.h gui.h gui_beval.h \ proto/gui_beval.pro alloc.h ex_cmds.h proto.h globals.h farsi.h arabic.h +objects/json_test.o: json_test.c main.c vim.h auto/config.h feature.h os_unix.h \ + auto/osdef.h ascii.h keymap.h term.h macros.h option.h structs.h \ + regexp.h gui.h gui_beval.h proto/gui_beval.pro alloc.h ex_cmds.h proto.h \ + globals.h farsi.h arabic.h farsi.c arabic.c json.c objects/memfile_test.o: memfile_test.c main.c vim.h auto/config.h feature.h \ os_unix.h auto/osdef.h ascii.h keymap.h term.h macros.h option.h \ structs.h regexp.h gui.h gui_beval.h proto/gui_beval.pro alloc.h \ diff --git a/src/channel.c b/src/channel.c index 8cb9a352ee..db13c8a9d7 100644 --- a/src/channel.c +++ b/src/channel.c @@ -540,9 +540,8 @@ channel_read_json(int ch_idx) /* TODO: make reader work properly */ /* reader.js_buf = channel_peek(ch_idx); */ reader.js_buf = channel_get_all(ch_idx); - reader.js_eof = TRUE; - /* reader.js_eof = FALSE; */ reader.js_used = 0; + reader.js_fill = NULL; /* reader.js_fill = channel_fill; */ reader.js_cookie = &ch_idx; if (json_decode(&reader, &listtv) == OK) diff --git a/src/eval.c b/src/eval.c index 01a4512e2e..9a4e1bbe21 100644 --- a/src/eval.c +++ b/src/eval.c @@ -14100,9 +14100,9 @@ f_jsondecode(typval_T *argvars, typval_T *rettv) js_read_T reader; reader.js_buf = get_tv_string(&argvars[0]); - reader.js_eof = TRUE; + reader.js_fill = NULL; reader.js_used = 0; - if (json_decode(&reader, rettv) == FAIL) + if (json_decode_all(&reader, rettv) != OK) EMSG(_(e_invarg)); } diff --git a/src/json.c b/src/json.c index 9c78e15e85..72b065f9ac 100644 --- a/src/json.c +++ b/src/json.c @@ -17,7 +17,7 @@ #if defined(FEAT_EVAL) || defined(PROTO) static int json_encode_item(garray_T *gap, typval_T *val, int copyID); -static void json_decode_item(js_read_T *reader, typval_T *res); +static int json_decode_item(js_read_T *reader, typval_T *res); /* * Encode "val" into a JSON format string. @@ -234,37 +234,60 @@ json_encode_item(garray_T *gap, typval_T *val, int copyID) return OK; } +/* + * When "reader" has less than NUMBUFLEN bytes available, call the fill + * callback to get more. + */ + static void +fill_numbuflen(js_read_T *reader) +{ + if (reader->js_fill != NULL && (int)(reader->js_end - reader->js_buf) + - reader->js_used < NUMBUFLEN) + { + if (reader->js_fill(reader)) + reader->js_end = reader->js_buf + STRLEN(reader->js_buf); + } +} + /* * Skip white space in "reader". + * Also tops up readahead when needed. */ static void json_skip_white(js_read_T *reader) { int c; - while ((c = reader->js_buf[reader->js_used]) == ' ' - || c == TAB || c == NL || c == CAR) + for (;;) + { + c = reader->js_buf[reader->js_used]; + if (reader->js_fill != NULL && c == NUL) + { + if (reader->js_fill(reader)) + reader->js_end = reader->js_buf + STRLEN(reader->js_buf); + continue; + } + if (c != ' ' && c != TAB && c != NL && c != CAR) + break; ++reader->js_used; + } + fill_numbuflen(reader); } -/* - * Make sure there are at least enough characters buffered to read a number. - */ - static void -json_fill_buffer(js_read_T *reader UNUSED) -{ - /* TODO */ -} - - static void + static int json_decode_array(js_read_T *reader, typval_T *res) { char_u *p; typval_T item; listitem_T *li; + int ret; - if (rettv_list_alloc(res) == FAIL) - goto failsilent; + if (res != NULL && rettv_list_alloc(res) == FAIL) + { + res->v_type = VAR_SPECIAL; + res->vval.v_number = VVAL_NONE; + return FAIL; + } ++reader->js_used; /* consume the '[' */ while (TRUE) @@ -272,38 +295,43 @@ json_decode_array(js_read_T *reader, typval_T *res) json_skip_white(reader); p = reader->js_buf + reader->js_used; if (*p == NUL) - goto fail; + return MAYBE; if (*p == ']') { ++reader->js_used; /* consume the ']' */ - return; + break; } - if (!reader->js_eof && (int)(reader->js_end - p) < NUMBUFLEN) - json_fill_buffer(reader); - - json_decode_item(reader, &item); - li = listitem_alloc(); - if (li == NULL) - return; - li->li_tv = item; - list_append(res->vval.v_list, li); + ret = json_decode_item(reader, res == NULL ? NULL : &item); + if (ret != OK) + return ret; + if (res != NULL) + { + li = listitem_alloc(); + if (li == NULL) + { + clear_tv(&item); + return FAIL; + } + li->li_tv = item; + list_append(res->vval.v_list, li); + } json_skip_white(reader); p = reader->js_buf + reader->js_used; if (*p == ',') ++reader->js_used; else if (*p != ']') - goto fail; + { + if (*p == NUL) + return MAYBE; + return FAIL; + } } -fail: - EMSG(_(e_invarg)); -failsilent: - res->v_type = VAR_SPECIAL; - res->vval.v_number = VVAL_NONE; + return OK; } - static void + static int json_decode_object(js_read_T *reader, typval_T *res) { char_u *p; @@ -312,9 +340,14 @@ json_decode_object(js_read_T *reader, typval_T *res) dictitem_T *di; char_u buf[NUMBUFLEN]; char_u *key; + int ret; - if (rettv_dict_alloc(res) == FAIL) - goto failsilent; + if (res != NULL && rettv_dict_alloc(res) == FAIL) + { + res->v_type = VAR_SPECIAL; + res->vval.v_number = VVAL_NONE; + return FAIL; + } ++reader->js_used; /* consume the '{' */ while (TRUE) @@ -322,243 +355,387 @@ json_decode_object(js_read_T *reader, typval_T *res) json_skip_white(reader); p = reader->js_buf + reader->js_used; if (*p == NUL) - goto fail; + return MAYBE; if (*p == '}') { ++reader->js_used; /* consume the '}' */ - return; + break; } - if (!reader->js_eof && (int)(reader->js_end - p) < NUMBUFLEN) - json_fill_buffer(reader); - json_decode_item(reader, &tvkey); - key = get_tv_string_buf_chk(&tvkey, buf); - if (key == NULL || *key == NUL) + ret = json_decode_item(reader, res == NULL ? NULL : &tvkey); + if (ret != OK) + return ret; + if (res != NULL) { - /* "key" is NULL when get_tv_string_buf_chk() gave an errmsg */ - if (key != NULL) - EMSG(_(e_emptykey)); - clear_tv(&tvkey); - goto failsilent; + key = get_tv_string_buf_chk(&tvkey, buf); + if (key == NULL || *key == NUL) + { + clear_tv(&tvkey); + return FAIL; + } } json_skip_white(reader); p = reader->js_buf + reader->js_used; if (*p != ':') { - clear_tv(&tvkey); - goto fail; + if (res != NULL) + clear_tv(&tvkey); + if (*p == NUL) + return MAYBE; + return FAIL; } ++reader->js_used; json_skip_white(reader); - if (!reader->js_eof && (int)(reader->js_end - p) < NUMBUFLEN) - json_fill_buffer(reader); - json_decode_item(reader, &item); - - di = dictitem_alloc(key); - clear_tv(&tvkey); - if (di == NULL) + ret = json_decode_item(reader, res == NULL ? NULL : &item); + if (ret != OK) { - clear_tv(&item); - goto fail; + if (res != NULL) + clear_tv(&tvkey); + return ret; + } + + if (res != NULL) + { + di = dictitem_alloc(key); + clear_tv(&tvkey); + if (di == NULL) + { + clear_tv(&item); + return FAIL; + } + di->di_tv = item; + if (dict_add(res->vval.v_dict, di) == FAIL) + { + dictitem_free(di); + return FAIL; + } } - di->di_tv = item; - if (dict_add(res->vval.v_dict, di) == FAIL) - dictitem_free(di); json_skip_white(reader); p = reader->js_buf + reader->js_used; if (*p == ',') ++reader->js_used; else if (*p != '}') - goto fail; + { + if (*p == NUL) + return MAYBE; + return FAIL; + } } -fail: - EMSG(_(e_invarg)); -failsilent: - res->v_type = VAR_SPECIAL; - res->vval.v_number = VVAL_NONE; + return OK; } - static void + static int json_decode_string(js_read_T *reader, typval_T *res) { garray_T ga; int len; - char_u *p = reader->js_buf + reader->js_used + 1; + char_u *p; int c; long nr; char_u buf[NUMBUFLEN]; - ga_init2(&ga, 1, 200); + if (res != NULL) + ga_init2(&ga, 1, 200); - /* TODO: fill buffer when needed. */ - while (*p != NUL && *p != '"') + p = reader->js_buf + reader->js_used + 1; /* skip over " */ + while (*p != '"') { + if (*p == NUL || p[1] == NUL +#ifdef FEAT_MBYTE + || utf_ptr2len(p) < utf_byte2len(*p) +#endif + ) + { + if (reader->js_fill == NULL) + break; + len = (int)(reader->js_end - p); + reader->js_used = (int)(p - reader->js_buf); + if (!reader->js_fill(reader)) + break; /* didn't get more */ + p = reader->js_buf + reader->js_used; + reader->js_end = reader->js_buf + STRLEN(reader->js_buf); + continue; + } + if (*p == '\\') { c = -1; switch (p[1]) { + case '\\': c = '\\'; break; + case '"': c = '"'; break; case 'b': c = BS; break; case 't': c = TAB; break; case 'n': c = NL; break; case 'f': c = FF; break; case 'r': c = CAR; break; case 'u': + if (reader->js_fill != NULL + && (int)(reader->js_end - p) < NUMBUFLEN) + { + reader->js_used = (int)(p - reader->js_buf); + if (reader->js_fill(reader)) + { + p = reader->js_buf + reader->js_used; + reader->js_end = reader->js_buf + + STRLEN(reader->js_buf); + } + } vim_str2nr(p + 2, NULL, &len, STR2NR_HEX + STR2NR_FORCE, &nr, NULL, 4); p += len + 2; + if (res != NULL) + { #ifdef FEAT_MBYTE - buf[(*mb_char2bytes)((int)nr, buf)] = NUL; - ga_concat(&ga, buf); + buf[(*mb_char2bytes)((int)nr, buf)] = NUL; + ga_concat(&ga, buf); #else - ga_append(&ga, nr); + ga_append(&ga, nr); #endif + } break; - default: c = p[1]; break; + default: + /* not a special char, skip over \ */ + ++p; + continue; } if (c > 0) { p += 2; - ga_append(&ga, c); + if (res != NULL) + ga_append(&ga, c); } } else { len = MB_PTR2LEN(p); - if (ga_grow(&ga, len) == OK) + if (res != NULL) { + if (ga_grow(&ga, len) == FAIL) + { + ga_clear(&ga); + return FAIL; + } mch_memmove((char *)ga.ga_data + ga.ga_len, p, (size_t)len); ga.ga_len += len; } p += len; } - if (!reader->js_eof && (int)(reader->js_end - p) < NUMBUFLEN) - { - reader->js_used = (int)(p - reader->js_buf); - json_fill_buffer(reader); - p = reader->js_buf + reader->js_used; - } } + reader->js_used = (int)(p - reader->js_buf); if (*p == '"') { ++reader->js_used; - res->v_type = VAR_STRING; - if (ga.ga_data == NULL) - res->vval.v_string = NULL; - else - res->vval.v_string = vim_strsave(ga.ga_data); + if (res != NULL) + { + res->v_type = VAR_STRING; + if (ga.ga_data == NULL) + res->vval.v_string = NULL; + else + res->vval.v_string = vim_strsave(ga.ga_data); + } + return OK; } - else + if (res != NULL) { - EMSG(_(e_invarg)); res->v_type = VAR_SPECIAL; res->vval.v_number = VVAL_NONE; + ga_clear(&ga); } - ga_clear(&ga); + return MAYBE; } /* - * Decode one item and put it in "result". + * Decode one item and put it in "res". If "res" is NULL only advance. * Must already have skipped white space. + * + * Return FAIL for a decoding error. + * Return MAYBE for an incomplete message. */ - static void + static int json_decode_item(js_read_T *reader, typval_T *res) { - char_u *p = reader->js_buf + reader->js_used; + char_u *p; + int len; + fill_numbuflen(reader); + p = reader->js_buf + reader->js_used; switch (*p) { case '[': /* array */ - json_decode_array(reader, res); - return; + return json_decode_array(reader, res); case '{': /* object */ - json_decode_object(reader, res); - return; + return json_decode_object(reader, res); case '"': /* string */ - json_decode_string(reader, res); - return; + return json_decode_string(reader, res); case ',': /* comma: empty item */ case NUL: /* empty */ - res->v_type = VAR_SPECIAL; - res->vval.v_number = VVAL_NONE; - return; + if (res != NULL) + { + res->v_type = VAR_SPECIAL; + res->vval.v_number = VVAL_NONE; + } + return OK; default: if (VIM_ISDIGIT(*p) || *p == '-') { - int len; char_u *sp = p; + #ifdef FEAT_FLOAT if (*sp == '-') + { ++sp; + if (*sp == NUL) + return MAYBE; + if (!VIM_ISDIGIT(*sp)) + return FAIL; + } sp = skipdigits(sp); if (*sp == '.' || *sp == 'e' || *sp == 'E') { - res->v_type = VAR_FLOAT; - len = string2float(p, &res->vval.v_float); + if (res == NULL) + { + float_T f; + + len = string2float(p, &f); + } + else + { + res->v_type = VAR_FLOAT; + len = string2float(p, &res->vval.v_float); + } } else #endif { long nr; - res->v_type = VAR_NUMBER; vim_str2nr(reader->js_buf + reader->js_used, NULL, &len, 0, /* what */ &nr, NULL, 0); - res->vval.v_number = nr; + if (res != NULL) + { + res->v_type = VAR_NUMBER; + res->vval.v_number = nr; + } } reader->js_used += len; - return; + return OK; } if (STRNICMP((char *)p, "false", 5) == 0) { reader->js_used += 5; - res->v_type = VAR_SPECIAL; - res->vval.v_number = VVAL_FALSE; - return; + if (res != NULL) + { + res->v_type = VAR_SPECIAL; + res->vval.v_number = VVAL_FALSE; + } + return OK; } if (STRNICMP((char *)p, "true", 4) == 0) { reader->js_used += 4; - res->v_type = VAR_SPECIAL; - res->vval.v_number = VVAL_TRUE; - return; + if (res != NULL) + { + res->v_type = VAR_SPECIAL; + res->vval.v_number = VVAL_TRUE; + } + return OK; } if (STRNICMP((char *)p, "null", 4) == 0) { reader->js_used += 4; - res->v_type = VAR_SPECIAL; - res->vval.v_number = VVAL_NULL; - return; + if (res != NULL) + { + res->v_type = VAR_SPECIAL; + res->vval.v_number = VVAL_NULL; + } + return OK; } + /* check for truncated name */ + len = (int)(reader->js_end - (reader->js_buf + reader->js_used)); + if ((len < 5 && STRNICMP((char *)p, "false", len) == 0) + || (len < 4 && (STRNICMP((char *)p, "true", len) == 0 + || STRNICMP((char *)p, "null", len) == 0))) + return MAYBE; break; } - EMSG(_(e_invarg)); - res->v_type = VAR_SPECIAL; - res->vval.v_number = VVAL_NONE; + if (res != NUL) + { + res->v_type = VAR_SPECIAL; + res->vval.v_number = VVAL_NONE; + } + return FAIL; } /* * Decode the JSON from "reader" and store the result in "res". - * Return OK or FAIL; + * Return FAIL if not the whole message was consumed. */ int -json_decode(js_read_T *reader, typval_T *res) +json_decode_all(js_read_T *reader, typval_T *res) { + int ret; + + /* We get the end once, to avoid calling strlen() many times. */ + reader->js_end = reader->js_buf + STRLEN(reader->js_buf); json_skip_white(reader); - json_decode_item(reader, res); + ret = json_decode_item(reader, res); + if (ret != OK) + return FAIL; json_skip_white(reader); if (reader->js_buf[reader->js_used] != NUL) return FAIL; return OK; } + +/* + * Decode the JSON from "reader" and store the result in "res". + * Return FAIL if the message has a decoding error or the message is + * truncated. Consumes the message anyway. + */ + int +json_decode(js_read_T *reader, typval_T *res) +{ + int ret; + + /* We get the end once, to avoid calling strlen() many times. */ + reader->js_end = reader->js_buf + STRLEN(reader->js_buf); + json_skip_white(reader); + ret = json_decode_item(reader, res); + json_skip_white(reader); + + return ret == OK ? OK : FAIL; +} + +/* + * Decode the JSON from "reader" to find the end of the message. + * Return FAIL if the message has a decoding error. + * Return MAYBE if the message is truncated, need to read more. + * This only works reliable if the message contains an object, array or + * string. A number might be trucated without knowing. + * Does not advance the reader. + */ + int +json_find_end(js_read_T *reader) +{ + int used_save = reader->js_used; + int ret; + + /* We get the end once, to avoid calling strlen() many times. */ + reader->js_end = reader->js_buf + STRLEN(reader->js_buf); + json_skip_white(reader); + ret = json_decode_item(reader, NULL); + reader->js_used = used_save; + return ret; +} #endif diff --git a/src/json_test.c b/src/json_test.c new file mode 100644 index 0000000000..0416543922 --- /dev/null +++ b/src/json_test.c @@ -0,0 +1,193 @@ +/* vi:set ts=8 sts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +/* + * json_test.c: Unittests for json.c + */ + +#undef NDEBUG +#include + +/* Must include main.c because it contains much more than just main() */ +#define NO_VIM_MAIN +#include "main.c" + +/* This file has to be included because the tested functions are static */ +#include "json.c" + +/* + * Test json_find_end() with imcomplete items. + */ + static void +test_decode_find_end(void) +{ + js_read_T reader; + + reader.js_fill = NULL; + reader.js_used = 0; + + /* string and incomplete string */ + reader.js_buf = (char_u *)"\"hello\""; + assert(json_find_end(&reader) == OK); + reader.js_buf = (char_u *)" \"hello\" "; + assert(json_find_end(&reader) == OK); + reader.js_buf = (char_u *)"\"hello"; + assert(json_find_end(&reader) == MAYBE); + + /* number and dash (incomplete number) */ + reader.js_buf = (char_u *)"123"; + assert(json_find_end(&reader) == OK); + reader.js_buf = (char_u *)"-"; + assert(json_find_end(&reader) == MAYBE); + + /* false, true and null, also incomplete */ + reader.js_buf = (char_u *)"false"; + assert(json_find_end(&reader) == OK); + reader.js_buf = (char_u *)"f"; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)"fa"; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)"fal"; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)"fals"; + assert(json_find_end(&reader) == MAYBE); + + reader.js_buf = (char_u *)"true"; + assert(json_find_end(&reader) == OK); + reader.js_buf = (char_u *)"t"; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)"tr"; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)"tru"; + assert(json_find_end(&reader) == MAYBE); + + reader.js_buf = (char_u *)"null"; + assert(json_find_end(&reader) == OK); + reader.js_buf = (char_u *)"n"; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)"nu"; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)"nul"; + assert(json_find_end(&reader) == MAYBE); + + /* object without white space */ + reader.js_buf = (char_u *)"{\"a\":123}"; + assert(json_find_end(&reader) == OK); + reader.js_buf = (char_u *)"{\"a\":123"; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)"{\"a\":"; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)"{\"a\""; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)"{\"a"; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)"{\""; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)"{"; + assert(json_find_end(&reader) == MAYBE); + + /* object with white space */ + reader.js_buf = (char_u *)" { \"a\" : 123 } "; + assert(json_find_end(&reader) == OK); + reader.js_buf = (char_u *)" { \"a\" : 123 "; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)" { \"a\" : "; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)" { \"a\" "; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)" { \"a "; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)" { "; + assert(json_find_end(&reader) == MAYBE); + + /* array without white space */ + reader.js_buf = (char_u *)"[\"a\",123]"; + assert(json_find_end(&reader) == OK); + reader.js_buf = (char_u *)"[\"a\",123"; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)"[\"a\","; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)"[\"a\""; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)"[\"a"; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)"[\""; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)"["; + assert(json_find_end(&reader) == MAYBE); + + /* array with white space */ + reader.js_buf = (char_u *)" [ \"a\" , 123 ] "; + assert(json_find_end(&reader) == OK); + reader.js_buf = (char_u *)" [ \"a\" , 123 "; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)" [ \"a\" , "; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)" [ \"a\" "; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)" [ \"a "; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)" [ "; + assert(json_find_end(&reader) == MAYBE); +} + + static int +fill_from_cookie(js_read_T *reader) +{ + reader->js_buf = reader->js_cookie; + return TRUE; +} + +/* + * Test json_find_end with an incomplete array, calling the fill function. + */ + static void +test_fill_called_on_find_end(void) +{ + js_read_T reader; + + reader.js_fill = fill_from_cookie; + reader.js_used = 0; + reader.js_buf = (char_u *)" [ \"a\" , 123 "; + reader.js_cookie = " [ \"a\" , 123 ] "; + assert(json_find_end(&reader) == OK); + reader.js_buf = (char_u *)" [ \"a\" , "; + assert(json_find_end(&reader) == OK); + reader.js_buf = (char_u *)" [ \"a\" "; + assert(json_find_end(&reader) == OK); + reader.js_buf = (char_u *)" [ \"a"; + assert(json_find_end(&reader) == OK); + reader.js_buf = (char_u *)" [ "; + assert(json_find_end(&reader) == OK); +} + +/* + * Test json_find_end with an incomplete string, calling the fill function. + */ + static void +test_fill_called_on_string(void) +{ + js_read_T reader; + + reader.js_fill = fill_from_cookie; + reader.js_used = 0; + reader.js_buf = (char_u *)" \"foo"; + reader.js_end = reader.js_buf + STRLEN(reader.js_buf); + reader.js_cookie = " \"foobar\" "; + assert(json_decode_string(&reader, NULL) == OK); +} + + int +main(void) +{ + test_decode_find_end(); + test_fill_called_on_find_end(); + test_fill_called_on_string(); + return 0; +} diff --git a/src/memfile_test.c b/src/memfile_test.c index 3fc13516dc..b742cd6705 100644 --- a/src/memfile_test.c +++ b/src/memfile_test.c @@ -25,8 +25,6 @@ #define index_to_key(i) ((i) ^ 15167) #define TEST_COUNT 50000 -static void test_mf_hash(void); - /* * Test mf_hash_*() functions. */ diff --git a/src/proto/json.pro b/src/proto/json.pro index 0b39e84230..5d2e7ba900 100644 --- a/src/proto/json.pro +++ b/src/proto/json.pro @@ -1,5 +1,7 @@ /* json.c */ char_u *json_encode(typval_T *val); char_u *json_encode_nr_expr(int nr, typval_T *val); +int json_decode_all(js_read_T *reader, typval_T *res); int json_decode(js_read_T *reader, typval_T *res); +int json_find_end(js_read_T *reader); /* vim: set ft=c : */ diff --git a/src/structs.h b/src/structs.h index 62a4bd5e37..26f403f2a8 100644 --- a/src/structs.h +++ b/src/structs.h @@ -2687,12 +2687,14 @@ typedef struct { /* * Structure used for reading in json_decode(). */ -typedef struct +struct js_reader { char_u *js_buf; /* text to be decoded */ - char_u *js_end; /* NUL in js_buf when js_eof is FALSE */ + char_u *js_end; /* NUL in js_buf */ int js_used; /* bytes used from js_buf */ - int js_eof; /* when TRUE js_buf is all there is */ - int (*js_fill)(void *); /* function to fill the buffer */ - void *js_cookie; /* passed to js_fill */ -} js_read_T; + int (*js_fill)(struct js_reader *); + /* function to fill the buffer or NULL; + * return TRUE when the buffer was filled */ + void *js_cookie; /* can be used by js_fill */ +}; +typedef struct js_reader js_read_T; diff --git a/src/version.c b/src/version.c index 53949e63d9..8fabfc91ad 100644 --- a/src/version.c +++ b/src/version.c @@ -742,6 +742,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1238, /**/ 1237, /**/ From df5b27b20ec023274fb0f5347973d5abcde7ddd6 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Tue, 2 Feb 2016 18:43:17 +0100 Subject: [PATCH 3/9] patch 7.4.1239 Problem: JSON message after the first one is dropped. Solution: Put remainder of message back in the queue. --- src/channel.c | 29 ++++++++++++++++++++--------- src/version.c | 2 ++ 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/channel.c b/src/channel.c index db13c8a9d7..7183ff61fc 100644 --- a/src/channel.c +++ b/src/channel.c @@ -567,6 +567,13 @@ channel_read_json(int ch_idx) } } } + + /* Put the unread part back into the channel. + * TODO: insert in front */ + if (reader.js_buf[reader.js_used] != NUL) + channel_save(ch_idx, reader.js_buf + reader.js_used, + (int)(reader.js_end - reader.js_buf) - reader.js_used); + vim_free(reader.js_buf); } /* @@ -697,8 +704,9 @@ channel_exe_cmd(int idx, char_u *cmd, typval_T *arg2, typval_T *arg3) /* * Invoke a callback for channel "idx" if needed. + * Return OK when a message was handled, there might be another one. */ - static void + static int may_invoke_callback(int idx) { char_u *msg = NULL; @@ -710,22 +718,22 @@ may_invoke_callback(int idx) int json_mode = channels[idx].ch_json_mode; if (channel_peek(idx) == NULL) - return; + return FALSE; if (channels[idx].ch_close_cb != NULL) /* this channel is handled elsewhere (netbeans) */ - return; + return FALSE; if (json_mode) { /* Get any json message. Return if there isn't one. */ channel_read_json(idx); if (channel_get_json(idx, -1, &listtv) == FAIL) - return; + return FALSE; if (listtv->v_type != VAR_LIST) { /* TODO: give error */ clear_tv(listtv); - return; + return FALSE; } list = listtv->vval.v_list; @@ -733,7 +741,7 @@ may_invoke_callback(int idx) { /* TODO: give error */ clear_tv(listtv); - return; + return FALSE; } argv[1] = list->lv_first->li_next->li_tv; @@ -748,14 +756,14 @@ may_invoke_callback(int idx) arg3 = &list->lv_last->li_tv; channel_exe_cmd(idx, cmd, &argv[1], arg3); clear_tv(listtv); - return; + return TRUE; } if (typetv->v_type != VAR_NUMBER) { /* TODO: give error */ clear_tv(listtv); - return; + return FALSE; } seq_nr = typetv->vval.v_number; } @@ -785,6 +793,8 @@ may_invoke_callback(int idx) if (listtv != NULL) clear_tv(listtv); vim_free(msg); + + return TRUE; } /* @@ -1244,7 +1254,8 @@ channel_parse_messages(void) int i; for (i = 0; i < channel_count; ++i) - may_invoke_callback(i); + while (may_invoke_callback(i) == OK) + ; } #endif /* FEAT_CHANNEL */ diff --git a/src/version.c b/src/version.c index 8fabfc91ad..1caaff1923 100644 --- a/src/version.c +++ b/src/version.c @@ -742,6 +742,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1239, /**/ 1238, /**/ From bc073092254df17b282d162d8e8181e8f6a7a356 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Tue, 2 Feb 2016 18:50:45 +0100 Subject: [PATCH 4/9] patch 7.4.1240 Problem: Visual studio tools are noisy. Solution: Suppress startup info. (Mike Williams) --- src/GvimExt/Makefile | 2 +- src/Make_mvc.mak | 2 +- src/tee/Make_mvc.mak | 2 +- src/version.c | 2 ++ 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/GvimExt/Makefile b/src/GvimExt/Makefile index a57840fa1f..361c229322 100644 --- a/src/GvimExt/Makefile +++ b/src/GvimExt/Makefile @@ -52,7 +52,7 @@ gvimext.obj: gvimext.h $(cc) $(cflags) -DFEAT_GETTEXT $(cvarsmt) $*.cpp gvimext.res: gvimext.rc - $(rc) $(rcflags) $(rcvars) gvimext.rc + $(rc) /nologo $(rcflags) $(rcvars) gvimext.rc clean: - if exist gvimext.dll del gvimext.dll diff --git a/src/Make_mvc.mak b/src/Make_mvc.mak index c34a63ed6d..11d562959c 100644 --- a/src/Make_mvc.mak +++ b/src/Make_mvc.mak @@ -1294,7 +1294,7 @@ $(OUTDIR)/xpm_w32.obj: $(OUTDIR) xpm_w32.c $(OUTDIR)/vim.res: $(OUTDIR) vim.rc gvim.exe.mnf version.h tools.bmp \ tearoff.bmp vim.ico vim_error.ico \ vim_alert.ico vim_info.ico vim_quest.ico - $(RC) /l 0x409 /Fo$(OUTDIR)/vim.res $(RCFLAGS) vim.rc + $(RC) /nologo /l 0x409 /Fo$(OUTDIR)/vim.res $(RCFLAGS) vim.rc iid_ole.c if_ole.h vim.tlb: if_ole.idl midl /nologo /error none /proxy nul /iid iid_ole.c /tlb vim.tlb \ diff --git a/src/tee/Make_mvc.mak b/src/tee/Make_mvc.mak index a957f944ac..7fe22df372 100644 --- a/src/tee/Make_mvc.mak +++ b/src/tee/Make_mvc.mak @@ -1,7 +1,7 @@ # A very (if not the most) simplistic Makefile for MSVC CC=cl -CFLAGS=/O2 +CFLAGS=/O2 /nologo tee.exe: tee.obj $(CC) $(CFLAGS) /Fo$@ $** diff --git a/src/version.c b/src/version.c index 1caaff1923..712377a23d 100644 --- a/src/version.c +++ b/src/version.c @@ -742,6 +742,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1240, /**/ 1239, /**/ From 04b08c3de68534adff95c8823787299e07ed3b49 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Tue, 2 Feb 2016 19:01:55 +0100 Subject: [PATCH 5/9] patch 7.4.1241 Problem: Missing change in Makefile due to diff mismatch Solution: Update the list of object files. --- src/Makefile | 10 ++++++++-- src/version.c | 2 ++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Makefile b/src/Makefile index 31664b6716..93a889831b 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1591,7 +1591,7 @@ OBJ_COMMON = \ objects/if_cscope.o \ objects/if_xcmdsrv.o \ objects/mark.o \ - objects/memline.o \ + objects/memline.o \ objects/menu.o \ objects/message.o \ objects/misc1.o \ @@ -1633,11 +1633,17 @@ OBJ_COMMON = \ $(WSDEBUG_OBJ) OBJ = $(OBJ_COMMON) \ + objects/json.o \ objects/main.o \ objects/memfile.o +JSON_TEST_OBJ = $(OBJ_COMMON) \ + objects/json_test.o \ + objects/memfile.o + MEMFILE_TEST_OBJ = $(OBJ_COMMON) \ - objects/memfile_test.o + objects/json.o \ + objects/memfile_test.o PRO_AUTO = \ blowfish.pro \ diff --git a/src/version.c b/src/version.c index 712377a23d..39090d4fed 100644 --- a/src/version.c +++ b/src/version.c @@ -742,6 +742,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1241, /**/ 1240, /**/ From 8d8c509ac8dea59ad07712971d74afae08521f79 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Tue, 2 Feb 2016 19:15:38 +0100 Subject: [PATCH 6/9] patch 7.4.1242 Problem: json_test fails without the eval feature. Solution: Add #ifdef. --- src/json_test.c | 4 ++++ src/version.c | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/json_test.c b/src/json_test.c index 0416543922..f50c95608b 100644 --- a/src/json_test.c +++ b/src/json_test.c @@ -21,6 +21,7 @@ /* This file has to be included because the tested functions are static */ #include "json.c" +#if defined(FEAT_EVAL) /* * Test json_find_end() with imcomplete items. */ @@ -182,12 +183,15 @@ test_fill_called_on_string(void) reader.js_cookie = " \"foobar\" "; assert(json_decode_string(&reader, NULL) == OK); } +#endif int main(void) { +#if defined(FEAT_EVAL) test_decode_find_end(); test_fill_called_on_find_end(); test_fill_called_on_string(); +#endif return 0; } diff --git a/src/version.c b/src/version.c index 39090d4fed..b252d99375 100644 --- a/src/version.c +++ b/src/version.c @@ -742,6 +742,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1242, /**/ 1241, /**/ From fbf9c6b6c3bdb1c2eb42ea8c227e8ee021a7a8f2 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Tue, 2 Feb 2016 19:43:57 +0100 Subject: [PATCH 7/9] patch 7.4.1243 Problem: Compiler warning for uninitialized variable. Solution: Initialize it. (Elias Diem) --- src/json.c | 2 +- src/version.c | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/json.c b/src/json.c index 72b065f9ac..f97d283120 100644 --- a/src/json.c +++ b/src/json.c @@ -339,7 +339,7 @@ json_decode_object(js_read_T *reader, typval_T *res) typval_T item; dictitem_T *di; char_u buf[NUMBUFLEN]; - char_u *key; + char_u *key = NULL; int ret; if (res != NULL && rettv_dict_alloc(res) == FAIL) diff --git a/src/version.c b/src/version.c index b252d99375..34922b6ea5 100644 --- a/src/version.c +++ b/src/version.c @@ -742,6 +742,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1243, /**/ 1242, /**/ From f57969a20a4398f56e3028a6cc1102f9f9286ccf Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Tue, 2 Feb 2016 20:47:49 +0100 Subject: [PATCH 8/9] patch 7.4.1244 Problem: The channel functions don't sort together. Solution: Use a common "ch_" prefix. --- runtime/doc/eval.txt | 54 +++-- runtime/tools/demoserver.py | 10 +- src/eval.c | 451 +++++++++++++++++------------------- src/version.c | 2 + 4 files changed, 258 insertions(+), 259 deletions(-) diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 5727918a06..4a298dfceb 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -1,4 +1,4 @@ -*eval.txt* For Vim version 7.4. Last change: 2016 Feb 01 +*eval.txt* For Vim version 7.4. Last change: 2016 Feb 02 VIM REFERENCE MANUAL by Bram Moolenaar @@ -1810,6 +1810,13 @@ byteidxcomp( {expr}, {nr}) Number byte index of {nr}'th char in {expr} call( {func}, {arglist} [, {dict}]) any call {func} with arguments {arglist} ceil( {expr}) Float round {expr} up +ch_close( {handle}) none close a channel +ch_open( {address}, {mode} [, {callback}]) + Number open a channel +ch_sendexpr( {handle}, {expr} [, {callback}]) + any send {expr} over JSON channel {handle} +ch_sendraw( {handle}, {string} [, {callback}]) + any send {string} over raw channel {handle} changenr() Number current change number char2nr( {expr}[, {utf8}]) Number ASCII/UTF8 value of first char in {expr} cindent( {lnum}) Number C indent for line {lnum} @@ -1820,8 +1827,6 @@ complete_add( {expr}) Number add completion match complete_check() Number check for key typed during completion confirm( {msg} [, {choices} [, {default} [, {type}]]]) Number number of choice picked by user -connect( {address}, {mode} [, {callback}]) - Number open a channel copy( {expr}) any make a shallow copy of {expr} cos( {expr}) Float cosine of {expr} cosh( {expr}) Float hyperbolic cosine of {expr} @@ -2029,10 +2034,6 @@ searchpairpos( {start}, {middle}, {end} [, {flags} [, {skip} [...]]]) List search for other end of start/end pair searchpos( {pattern} [, {flags} [, {stopline} [, {timeout}]]]) List search for {pattern} -sendexpr( {handle}, {expr} [, {callback}]) - any send {expr} over JSON channel {handle} -sendraw( {handle}, {string} [, {callback}]) - any send {string} over raw channel {handle} server2client( {clientid}, {string}) Number send reply string serverlist() String get a list of available servers @@ -2666,7 +2667,10 @@ confirm({msg} [, {choices} [, {default} [, {type}]]]) don't fit, a vertical layout is used anyway. For some systems the horizontal layout is always used. -connect({address}, {mode} [, {callback}]) *connect()* +ch_close({handle}) *ch_close()* + Close channel {handle}. See |channel|. + +ch_open({address}, {mode} [, {callback}]) *ch_open()* Open a channel to {address}. See |channel|. Returns the channel handle on success. Returns a negative number for failure. @@ -2680,6 +2684,23 @@ connect({address}, {mode} [, {callback}]) *connect()* {callback} is a function that handles received messages on the channel. See |channel-callback|. +ch_sendexpr({handle}, {expr} [, {callback}]) ch_*sendexpr()* + Send {expr} over JSON channel {handle}. See |channel-use|. + + When {callback} is given returns immediately. Without + {callback} waits for a JSON response and returns the decoded + expression. When there is an error or timeout returns an + empty string. + + When {callback} is zero no response is expected. + Otherwise {callback} must be a Funcref or the name of a + function. It is called when the response is received. See + |channel-callback|. + +ch_sendraw({handle}, {string} [, {callback}]) *ch_sendraw()* + Send {string} over raw channel {handle}. See |channel-raw|. + Works like |ch_sendexpr()|, but does not decode the response. + *copy()* copy({expr}) Make a copy of {expr}. For Numbers and Strings this isn't different from using {expr} directly. @@ -5615,23 +5636,6 @@ searchpos({pattern} [, {flags} [, {stopline} [, {timeout}]]]) *searchpos()* < In this example "submatch" is 2 when a lowercase letter is found |/\l|, 3 when an uppercase letter is found |/\u|. -sendexpr({handle}, {expr} [, {callback}]) *sendexpr()* - Send {expr} over JSON channel {handle}. See |channel-use|. - - When {callback} is given returns immediately. Without - {callback} waits for a JSON response and returns the decoded - expression. When there is an error or timeout returns an - empty string. - - When {callback} is zero no response is expected. - Otherwise {callback} must be a Funcref or the name of a - function. It is called when the response is received. See - |channel-callback|. - -sendraw({handle}, {string} [, {callback}]) *sendraw()* - Send {string} over raw channel {handle}. See |channel-raw|. - Works like |sendexpr()|, but does not decode the response. - server2client( {clientid}, {string}) *server2client()* Send a reply string to {clientid}. The most recent {clientid} that sent a string can be retrieved with expand(""). diff --git a/runtime/tools/demoserver.py b/runtime/tools/demoserver.py index 0f6a3740c6..9f22aa2937 100644 --- a/runtime/tools/demoserver.py +++ b/runtime/tools/demoserver.py @@ -1,15 +1,21 @@ #!/usr/bin/python +# # Server that will accept connections from a Vim channel. # Run this server and then in Vim you can open the channel: -# :let handle = connect('localhost:8765', 'json') +# :let handle = ch_open('localhost:8765', 'json') # # Then Vim can send requests to the server: -# :let response = sendexpr(handle, 'hello!') +# :let response = ch_sendexpr(handle, 'hello!') # # And you can control Vim by typing a JSON message here, e.g.: # ["ex","echo 'hi there'"] # +# There is no prompt, just type a line and press Enter. +# To exit cleanly type "quit". +# # See ":help channel-demo" in Vim. +# +# This requires Python 2.6 or later. from __future__ import print_function import json diff --git a/src/eval.c b/src/eval.c index 9a4e1bbe21..892e14adac 100644 --- a/src/eval.c +++ b/src/eval.c @@ -499,6 +499,12 @@ static void f_call(typval_T *argvars, typval_T *rettv); #ifdef FEAT_FLOAT static void f_ceil(typval_T *argvars, typval_T *rettv); #endif +#ifdef FEAT_CHANNEL +static void f_ch_open(typval_T *argvars, typval_T *rettv); +static void f_ch_close(typval_T *argvars, typval_T *rettv); +static void f_ch_sendexpr(typval_T *argvars, typval_T *rettv); +static void f_ch_sendraw(typval_T *argvars, typval_T *rettv); +#endif static void f_changenr(typval_T *argvars, typval_T *rettv); static void f_char2nr(typval_T *argvars, typval_T *rettv); static void f_cindent(typval_T *argvars, typval_T *rettv); @@ -515,9 +521,6 @@ static void f_copy(typval_T *argvars, typval_T *rettv); static void f_cos(typval_T *argvars, typval_T *rettv); static void f_cosh(typval_T *argvars, typval_T *rettv); #endif -#ifdef FEAT_CHANNEL -static void f_connect(typval_T *argvars, typval_T *rettv); -#endif static void f_count(typval_T *argvars, typval_T *rettv); static void f_cscope_connection(typval_T *argvars, typval_T *rettv); static void f_cursor(typval_T *argsvars, typval_T *rettv); @@ -526,9 +529,6 @@ static void f_delete(typval_T *argvars, typval_T *rettv); static void f_did_filetype(typval_T *argvars, typval_T *rettv); static void f_diff_filler(typval_T *argvars, typval_T *rettv); static void f_diff_hlID(typval_T *argvars, typval_T *rettv); -#ifdef FEAT_CHANNEL -static void f_disconnect(typval_T *argvars, typval_T *rettv); -#endif static void f_empty(typval_T *argvars, typval_T *rettv); static void f_escape(typval_T *argvars, typval_T *rettv); static void f_eval(typval_T *argvars, typval_T *rettv); @@ -703,10 +703,6 @@ static void f_searchdecl(typval_T *argvars, typval_T *rettv); static void f_searchpair(typval_T *argvars, typval_T *rettv); static void f_searchpairpos(typval_T *argvars, typval_T *rettv); static void f_searchpos(typval_T *argvars, typval_T *rettv); -#ifdef FEAT_CHANNEL -static void f_sendexpr(typval_T *argvars, typval_T *rettv); -static void f_sendraw(typval_T *argvars, typval_T *rettv); -#endif static void f_server2client(typval_T *argvars, typval_T *rettv); static void f_serverlist(typval_T *argvars, typval_T *rettv); static void f_setbufvar(typval_T *argvars, typval_T *rettv); @@ -8002,6 +7998,12 @@ static struct fst {"call", 2, 3, f_call}, #ifdef FEAT_FLOAT {"ceil", 1, 1, f_ceil}, +#endif +#ifdef FEAT_CHANNEL + {"ch_close", 1, 1, f_ch_close}, + {"ch_open", 2, 3, f_ch_open}, + {"ch_sendexpr", 2, 3, f_ch_sendexpr}, + {"ch_sendraw", 2, 3, f_ch_sendraw}, #endif {"changenr", 0, 0, f_changenr}, {"char2nr", 1, 2, f_char2nr}, @@ -8014,9 +8016,6 @@ static struct fst {"complete_check", 0, 0, f_complete_check}, #endif {"confirm", 1, 4, f_confirm}, -#ifdef FEAT_CHANNEL - {"connect", 2, 3, f_connect}, -#endif {"copy", 1, 1, f_copy}, #ifdef FEAT_FLOAT {"cos", 1, 1, f_cos}, @@ -8030,9 +8029,6 @@ static struct fst {"did_filetype", 0, 0, f_did_filetype}, {"diff_filler", 1, 1, f_diff_filler}, {"diff_hlID", 2, 2, f_diff_hlID}, -#ifdef FEAT_CHANNEL - {"disconnect", 1, 1, f_disconnect}, -#endif {"empty", 1, 1, f_empty}, {"escape", 2, 2, f_escape}, {"eval", 1, 1, f_eval}, @@ -8211,10 +8207,6 @@ static struct fst {"searchpair", 3, 7, f_searchpair}, {"searchpairpos", 3, 7, f_searchpairpos}, {"searchpos", 1, 4, f_searchpos}, -#ifdef FEAT_CHANNEL - {"sendexpr", 2, 3, f_sendexpr}, - {"sendraw", 2, 3, f_sendraw}, -#endif {"server2client", 2, 2, f_server2client}, {"serverlist", 0, 0, f_serverlist}, {"setbufvar", 3, 3, f_setbufvar}, @@ -9685,6 +9677,213 @@ f_ceil(typval_T *argvars, typval_T *rettv) } #endif +#ifdef FEAT_CHANNEL +/* + * Get the channel index from the handle argument. + * Returns -1 if the handle is invalid or the channel is closed. + */ + static int +get_channel_arg(typval_T *tv) +{ + int ch_idx; + + if (tv->v_type != VAR_NUMBER) + { + EMSG2(_(e_invarg2), get_tv_string(tv)); + return -1; + } + ch_idx = tv->vval.v_number; + + if (!channel_is_open(ch_idx)) + { + EMSGN(_("E906: not an open channel"), ch_idx); + return -1; + } + return ch_idx; +} + +/* + * "ch_close()" function + */ + static void +f_ch_close(typval_T *argvars, typval_T *rettv UNUSED) +{ + int ch_idx = get_channel_arg(&argvars[0]); + + if (ch_idx >= 0) + channel_close(ch_idx); +} + +/* + * Get a callback from "arg". It can be a Funcref or a function name. + * When "arg" is zero return an empty string. + * Return NULL for an invalid argument. + */ + static char_u * +get_callback(typval_T *arg) +{ + if (arg->v_type == VAR_FUNC || arg->v_type == VAR_STRING) + return arg->vval.v_string; + if (arg->v_type == VAR_NUMBER && arg->vval.v_number == 0) + return (char_u *)""; + EMSG(_("E999: Invalid callback argument")); + return NULL; +} + +/* + * "ch_open()" function + */ + static void +f_ch_open(typval_T *argvars, typval_T *rettv) +{ + char_u *address; + char_u *mode; + char_u *callback = NULL; + char_u buf1[NUMBUFLEN]; + char_u *p; + int port; + int json_mode = FALSE; + + /* default: fail */ + rettv->vval.v_number = -1; + + address = get_tv_string(&argvars[0]); + mode = get_tv_string_buf(&argvars[1], buf1); + if (argvars[2].v_type != VAR_UNKNOWN) + { + callback = get_callback(&argvars[2]); + if (callback == NULL) + return; + } + + /* parse address */ + p = vim_strchr(address, ':'); + if (p == NULL) + { + EMSG2(_(e_invarg2), address); + return; + } + *p++ = NUL; + port = atoi((char *)p); + if (*address == NUL || port <= 0) + { + p[-1] = ':'; + EMSG2(_(e_invarg2), address); + return; + } + + /* parse mode */ + if (STRCMP(mode, "json") == 0) + json_mode = TRUE; + else if (STRCMP(mode, "raw") != 0) + { + EMSG2(_(e_invarg2), mode); + return; + } + + rettv->vval.v_number = channel_open((char *)address, port, NULL); + if (rettv->vval.v_number >= 0) + { + channel_set_json_mode(rettv->vval.v_number, json_mode); + if (callback != NULL && *callback != NUL) + channel_set_callback(rettv->vval.v_number, callback); + } +} + +/* + * common for "sendexpr()" and "sendraw()" + * Returns the channel index if the caller should read the response. + * Otherwise returns -1. + */ + static int +send_common(typval_T *argvars, char_u *text, char *fun) +{ + int ch_idx; + char_u *callback = NULL; + + ch_idx = get_channel_arg(&argvars[0]); + if (ch_idx < 0) + return -1; + + if (argvars[2].v_type != VAR_UNKNOWN) + { + callback = get_callback(&argvars[2]); + if (callback == NULL) + return -1; + } + /* Set the callback or clear it. An empty callback means no callback and + * not reading the response. */ + channel_set_req_callback(ch_idx, + callback != NULL && *callback == NUL ? NULL : callback); + + if (channel_send(ch_idx, text, fun) == OK && callback == NULL) + return ch_idx; + return -1; +} + +/* + * "ch_sendexpr()" function + */ + static void +f_ch_sendexpr(typval_T *argvars, typval_T *rettv) +{ + char_u *text; + typval_T *listtv; + int ch_idx; + int id; + + /* return an empty string by default */ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + id = channel_get_id(); + text = json_encode_nr_expr(id, &argvars[1]); + if (text == NULL) + return; + + ch_idx = send_common(argvars, text, "sendexpr"); + if (ch_idx >= 0) + { + if (channel_read_json_block(ch_idx, id, &listtv) == OK) + { + if (listtv->v_type == VAR_LIST) + { + list_T *list = listtv->vval.v_list; + + if (list->lv_len == 2) + { + /* Move the item from the list and then change the type to + * avoid the value being freed. */ + *rettv = list->lv_last->li_tv; + list->lv_last->li_tv.v_type = VAR_NUMBER; + } + } + clear_tv(listtv); + } + } +} + +/* + * "ch_sendraw()" function + */ + static void +f_ch_sendraw(typval_T *argvars, typval_T *rettv) +{ + char_u buf[NUMBUFLEN]; + char_u *text; + int ch_idx; + + /* return an empty string by default */ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + text = get_tv_string_buf(&argvars[1], buf); + ch_idx = send_common(argvars, text, "sendraw"); + if (ch_idx >= 0) + rettv->vval.v_string = channel_read_block(ch_idx); +} +#endif + /* * "changenr()" function */ @@ -10033,84 +10232,6 @@ f_count(typval_T *argvars, typval_T *rettv) rettv->vval.v_number = n; } -#ifdef FEAT_CHANNEL -/* - * Get a callback from "arg". It can be a Funcref or a function name. - * When "arg" is zero return an empty string. - * Return NULL for an invalid argument. - */ - static char_u * -get_callback(typval_T *arg) -{ - if (arg->v_type == VAR_FUNC || arg->v_type == VAR_STRING) - return arg->vval.v_string; - if (arg->v_type == VAR_NUMBER && arg->vval.v_number == 0) - return (char_u *)""; - EMSG(_("E999: Invalid callback argument")); - return NULL; -} - -/* - * "connect()" function - */ - static void -f_connect(typval_T *argvars, typval_T *rettv) -{ - char_u *address; - char_u *mode; - char_u *callback = NULL; - char_u buf1[NUMBUFLEN]; - char_u *p; - int port; - int json_mode = FALSE; - - /* default: fail */ - rettv->vval.v_number = -1; - - address = get_tv_string(&argvars[0]); - mode = get_tv_string_buf(&argvars[1], buf1); - if (argvars[2].v_type != VAR_UNKNOWN) - { - callback = get_callback(&argvars[2]); - if (callback == NULL) - return; - } - - /* parse address */ - p = vim_strchr(address, ':'); - if (p == NULL) - { - EMSG2(_(e_invarg2), address); - return; - } - *p++ = NUL; - port = atoi((char *)p); - if (*address == NUL || port <= 0) - { - p[-1] = ':'; - EMSG2(_(e_invarg2), address); - return; - } - - /* parse mode */ - if (STRCMP(mode, "json") == 0) - json_mode = TRUE; - else if (STRCMP(mode, "raw") != 0) - { - EMSG2(_(e_invarg2), mode); - return; - } - - rettv->vval.v_number = channel_open((char *)address, port, NULL); - if (rettv->vval.v_number >= 0) - { - channel_set_json_mode(rettv->vval.v_number, json_mode); - if (callback != NULL && *callback != NUL) - channel_set_callback(rettv->vval.v_number, callback); - } -} -#endif - /* * "cscope_connection([{num} , {dbpath} [, {prepend}]])" function * @@ -10349,44 +10470,6 @@ f_diff_hlID(typval_T *argvars UNUSED, typval_T *rettv UNUSED) #endif } -#ifdef FEAT_CHANNEL -/* - * Get the channel index from the handle argument. - * Returns -1 if the handle is invalid or the channel is closed. - */ - static int -get_channel_arg(typval_T *tv) -{ - int ch_idx; - - if (tv->v_type != VAR_NUMBER) - { - EMSG2(_(e_invarg2), get_tv_string(tv)); - return -1; - } - ch_idx = tv->vval.v_number; - - if (!channel_is_open(ch_idx)) - { - EMSGN(_("E906: not an open channel"), ch_idx); - return -1; - } - return ch_idx; -} - -/* - * "disconnect()" function - */ - static void -f_disconnect(typval_T *argvars, typval_T *rettv UNUSED) -{ - int ch_idx = get_channel_arg(&argvars[0]); - - if (ch_idx >= 0) - channel_close(ch_idx); -} -#endif - /* * "empty({expr})" function */ @@ -16860,102 +16943,6 @@ f_searchpos(typval_T *argvars, typval_T *rettv) list_append_number(rettv->vval.v_list, (varnumber_T)n); } -#ifdef FEAT_CHANNEL -/* - * common for "sendexpr()" and "sendraw()" - * Returns the channel index if the caller should read the response. - * Otherwise returns -1. - */ - static int -send_common(typval_T *argvars, char_u *text, char *fun) -{ - int ch_idx; - char_u *callback = NULL; - - ch_idx = get_channel_arg(&argvars[0]); - if (ch_idx < 0) - return -1; - - if (argvars[2].v_type != VAR_UNKNOWN) - { - callback = get_callback(&argvars[2]); - if (callback == NULL) - return -1; - } - /* Set the callback or clear it. An empty callback means no callback and - * not reading the response. */ - channel_set_req_callback(ch_idx, - callback != NULL && *callback == NUL ? NULL : callback); - - if (channel_send(ch_idx, text, fun) == OK && callback == NULL) - return ch_idx; - return -1; -} - -/* - * "sendexpr()" function - */ - static void -f_sendexpr(typval_T *argvars, typval_T *rettv) -{ - char_u *text; - typval_T *listtv; - int ch_idx; - int id; - - /* return an empty string by default */ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - id = channel_get_id(); - text = json_encode_nr_expr(id, &argvars[1]); - if (text == NULL) - return; - - ch_idx = send_common(argvars, text, "sendexpr"); - if (ch_idx >= 0) - { - if (channel_read_json_block(ch_idx, id, &listtv) == OK) - { - if (listtv->v_type == VAR_LIST) - { - list_T *list = listtv->vval.v_list; - - if (list->lv_len == 2) - { - /* Move the item from the list and then change the type to - * avoid the value being freed. */ - *rettv = list->lv_last->li_tv; - list->lv_last->li_tv.v_type = VAR_NUMBER; - } - } - clear_tv(listtv); - } - } -} - -/* - * "sendraw()" function - */ - static void -f_sendraw(typval_T *argvars, typval_T *rettv) -{ - char_u buf[NUMBUFLEN]; - char_u *text; - int ch_idx; - - /* return an empty string by default */ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - text = get_tv_string_buf(&argvars[1], buf); - ch_idx = send_common(argvars, text, "sendraw"); - if (ch_idx >= 0) - rettv->vval.v_string = channel_read_block(ch_idx); -} -#endif - - static void f_server2client(typval_T *argvars UNUSED, typval_T *rettv) { diff --git a/src/version.c b/src/version.c index 34922b6ea5..1676415e74 100644 --- a/src/version.c +++ b/src/version.c @@ -742,6 +742,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1244, /**/ 1243, /**/ From d087566a419cc107adab77db997b184ea0e433ad Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Tue, 2 Feb 2016 20:52:42 +0100 Subject: [PATCH 9/9] patch 7.4.1245 Problem: File missing from distribution. Solution: Add json_test.c. --- Filelist | 1 + src/version.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Filelist b/Filelist index 89fd0595d1..b7fb61bdf7 100644 --- a/Filelist +++ b/Filelist @@ -41,6 +41,7 @@ SRC_ALL = \ src/hardcopy.c \ src/hashtab.c \ src/json.c \ + src/json_test.c \ src/keymap.h \ src/macros.h \ src/main.c \ diff --git a/src/version.c b/src/version.c index 1676415e74..b93a59dcf3 100644 --- a/src/version.c +++ b/src/version.c @@ -742,6 +742,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1245, /**/ 1244, /**/