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/runtime/doc/eval.txt b/runtime/doc/eval.txt index 857fd21d17..435076db53 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/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/Makefile b/src/Makefile index 1d9230407c..4b5f2b1d85 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1562,11 +1562,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) @@ -1605,9 +1607,8 @@ OBJ_COMMON = \ $(HANGULIN_OBJ) \ objects/if_cscope.o \ objects/if_xcmdsrv.o \ - objects/json.o \ objects/mark.o \ - objects/memline.o \ + objects/memline.o \ objects/menu.o \ objects/message.o \ objects/misc1.o \ @@ -1649,11 +1650,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 \ @@ -1931,6 +1938,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 @@ -1965,6 +1973,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 \ @@ -2057,6 +2071,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) \ @@ -2837,6 +2858,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 @@ -3360,6 +3384,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 8fe783bff7..28c8a87f47 100644 --- a/src/channel.c +++ b/src/channel.c @@ -565,9 +565,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) @@ -593,6 +592,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); } /* @@ -723,8 +729,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; @@ -736,22 +743,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; @@ -759,7 +766,7 @@ may_invoke_callback(int idx) { /* TODO: give error */ clear_tv(listtv); - return; + return FALSE; } argv[1] = list->lv_first->li_next->li_tv; @@ -774,14 +781,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; } @@ -811,6 +818,8 @@ may_invoke_callback(int idx) if (listtv != NULL) clear_tv(listtv); vim_free(msg); + + return TRUE; } /* @@ -1270,7 +1279,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/eval.c b/src/eval.c index 6061aa1706..51754f0774 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 */ @@ -14119,9 +14202,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)); } @@ -16887,102 +16970,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/json.c b/src/json.c index 9c78e15e85..f97d283120 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; @@ -311,10 +339,15 @@ 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 (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..f50c95608b --- /dev/null +++ b/src/json_test.c @@ -0,0 +1,197 @@ +/* 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" + +#if defined(FEAT_EVAL) +/* + * 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); +} +#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/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/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/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 561bf9feb8..ea99e1b202 100644 --- a/src/structs.h +++ b/src/structs.h @@ -2703,12 +2703,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/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 b468c5aca1..e7222ba38e 100644 --- a/src/version.c +++ b/src/version.c @@ -757,6 +757,24 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1245, +/**/ + 1244, +/**/ + 1243, +/**/ + 1242, +/**/ + 1241, +/**/ + 1240, +/**/ + 1239, +/**/ + 1238, +/**/ + 1237, /**/ 1236, /**/