diff options
-rw-r--r-- | .ci/clang-asan.sh | 4 | ||||
-rw-r--r-- | src/nvim/CMakeLists.txt | 43 | ||||
-rw-r--r-- | src/nvim/eval.c | 112 | ||||
-rw-r--r-- | src/nvim/ex_cmds.c | 4 | ||||
-rw-r--r-- | src/nvim/ex_cmds2.c | 8 | ||||
-rw-r--r-- | src/nvim/ex_docmd.c | 42 | ||||
-rw-r--r-- | src/nvim/ex_getln.c | 2 | ||||
-rw-r--r-- | src/nvim/fold.c | 5 | ||||
-rw-r--r-- | src/nvim/garray.c | 5 | ||||
-rw-r--r-- | src/nvim/garray.h | 26 | ||||
-rw-r--r-- | src/nvim/memory.c | 37 | ||||
-rw-r--r-- | src/nvim/menu.c | 15 | ||||
-rw-r--r-- | src/nvim/ops.c | 356 | ||||
-rw-r--r-- | src/nvim/ops.h | 7 | ||||
-rw-r--r-- | src/nvim/po/CMakeLists.txt | 3 | ||||
-rw-r--r-- | src/nvim/screen.c | 2 | ||||
-rw-r--r-- | src/nvim/spell.c | 61 | ||||
-rw-r--r-- | src/nvim/strings.c | 27 | ||||
-rw-r--r-- | src/nvim/syntax.c | 16 | ||||
-rw-r--r-- | src/nvim/testdir/test_eval.in | 147 | ||||
-rw-r--r-- | src/nvim/testdir/test_eval.ok | bin | 432 -> 10578 bytes | |||
-rw-r--r-- | src/nvim/version.c | 6 |
22 files changed, 594 insertions, 334 deletions
diff --git a/.ci/clang-asan.sh b/.ci/clang-asan.sh index 3f2fd4df90..e0087b17a4 100644 --- a/.ci/clang-asan.sh +++ b/.ci/clang-asan.sh @@ -25,12 +25,10 @@ symbolizer=/usr/local/clang-$clang_version/bin/llvm-symbolizer setup_prebuilt_deps x64 -export SANITIZE=1 export ASAN_SYMBOLIZER_PATH=$symbolizer export ASAN_OPTIONS="detect_leaks=1:log_path=$tmpdir/asan" export TSAN_OPTIONS="external_symbolizer_path=$symbolizer:log_path=$tmpdir/tsan" -export SKIP_UNITTEST=1 export UBSAN_OPTIONS="log_path=$tmpdir/ubsan" # not sure if this works CMAKE_EXTRA_FLAGS="-DTRAVIS_CI_BUILD=ON \ @@ -38,7 +36,7 @@ CMAKE_EXTRA_FLAGS="-DTRAVIS_CI_BUILD=ON \ -DBUSTED_OUTPUT_TYPE=plainTerminal" # Build and output version info. -$MAKE_CMD CMAKE_EXTRA_FLAGS="$CMAKE_EXTRA_FLAGS" nvim +$MAKE_CMD CMAKE_EXTRA_FLAGS="$CMAKE_EXTRA_FLAGS -DSANITIZE=ON" nvim build/bin/nvim --version # Run functional tests. diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 2867857fb3..8c05c6a2b2 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -1,5 +1,11 @@ include(CheckLibraryExists) +option(SANITIZE "Enable Clang sanitizers for nvim binary" OFF) +if(SANITIZE AND NOT CMAKE_C_COMPILER_ID MATCHES "Clang") + message(WARNING "SANITIZE is only supported for Clang ... disabling") + set(SANITIZE OFF) +endif() + set(GENERATED_DIR ${PROJECT_BINARY_DIR}/src/nvim/auto) set(DISPATCH_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/msgpack-gen.lua) file(GLOB API_HEADERS api/*.h) @@ -94,25 +100,15 @@ endforeach() set_source_files_properties( ${CONV_SOURCES} PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion") -if(CMAKE_C_COMPILER_ID MATCHES "Clang") - if(DEFINED ENV{SANITIZE}) - message(STATUS "Enabling the sanitizers") - add_definitions(-DEXITFREE) # is this necessary for LeakSanitizer? - add_definitions(-fno-sanitize-recover -fno-omit-frame-pointer - -fno-optimize-sibling-calls -fsanitize=address -fsanitize=undefined) - set(CMAKE_EXE_LINKER_FLAGS - "-fsanitize=address -fsanitize=undefined ${CMAKE_EXE_LINKER_FLAGS}") - set(CMAKE_SHARED_LINKER_FLAGS - "-fsanitize=address -fsanitize=undefined ${CMAKE_SHARED_LINKER_FLAGS}") - endif() -endif() - get_directory_property(gen_cdefs COMPILE_DEFINITIONS) foreach(gen_cdef ${gen_cdefs} DO_NOT_DEFINE_EMPTY_ATTRIBUTES) if(NOT "${gen_cdef}" MATCHES "INCLUDE_GENERATED_DECLARATIONS") list(APPEND gen_cflags "-D${gen_cdef}") endif() endforeach() +if (SANITIZE) + list(APPEND gen_cflags "-DEXITFREE") +endif() get_directory_property(gen_includes INCLUDE_DIRECTORIES) foreach(gen_include ${gen_includes}) @@ -200,17 +196,20 @@ list(APPEND NVIM_LINK_LIBRARIES m ${CMAKE_THREAD_LIBS_INIT}) -if(NOT DEFINED ENV{SKIP_EXEC}) - add_executable(nvim ${NEOVIM_GENERATED_SOURCES} ${NEOVIM_SOURCES} - ${NEOVIM_HEADERS}) - target_link_libraries(nvim ${NVIM_LINK_LIBRARIES}) - install_helper(TARGETS nvim) +add_executable(nvim ${NEOVIM_GENERATED_SOURCES} ${NEOVIM_SOURCES} + ${NEOVIM_HEADERS}) +target_link_libraries(nvim ${NVIM_LINK_LIBRARIES}) +install_helper(TARGETS nvim) + +if(SANITIZE) + message(STATUS "Enabling Clang sanitizers for nvim") + set_property(TARGET nvim APPEND_STRING PROPERTY COMPILE_FLAGS "-DEXITFREE ") + set_property(TARGET nvim APPEND_STRING PROPERTY COMPILE_FLAGS "-fno-sanitize-recover -fno-omit-frame-pointer -fno-optimize-sibling-calls -fsanitize=address -fsanitize=undefined ") + set_property(TARGET nvim APPEND_STRING PROPERTY LINK_FLAGS "-fsanitize=address -fsanitize=undefined ") endif() -if(NOT DEFINED ENV{SKIP_UNITTEST}) - add_library(nvim-test MODULE EXCLUDE_FROM_ALL ${NEOVIM_GENERATED_SOURCES} +add_library(nvim-test MODULE EXCLUDE_FROM_ALL ${NEOVIM_GENERATED_SOURCES} ${NEOVIM_SOURCES} ${NEOVIM_HEADERS}) - target_link_libraries(nvim-test ${NVIM_LINK_LIBRARIES}) -endif() +target_link_libraries(nvim-test ${NVIM_LINK_LIBRARIES}) add_subdirectory(po) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index b4d1677520..45ab901398 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -1835,7 +1835,7 @@ ex_let_one ( p = get_tv_string_chk(tv); if (p != NULL && op != NULL && *op == '.') { - s = get_reg_contents(*arg == '@' ? '"' : *arg, TRUE, TRUE); + s = get_reg_contents(*arg == '@' ? '"' : *arg, kGRegExprSrc); if (s != NULL) { p = ptofree = concat_str(s, p); free(s); @@ -4076,7 +4076,7 @@ eval7 ( case '@': ++*arg; if (evaluate) { rettv->v_type = VAR_STRING; - rettv->vval.v_string = get_reg_contents(**arg, TRUE, TRUE); + rettv->vval.v_string = get_reg_contents(**arg, kGRegExprSrc); } if (**arg != NUL) ++*arg; @@ -5420,20 +5420,12 @@ static int list_join(garray_T *gap, list_T *l, char_u *sep, int echo_style, int { garray_T join_ga; int retval; - join_T *p; ga_init(&join_ga, (int)sizeof(join_T), l->lv_len); retval = list_join_inner(gap, l, sep, echo_style, copyID, &join_ga); - /* Dispose each item in join_ga. */ - if (join_ga.ga_data != NULL) { - p = (join_T *)join_ga.ga_data; - for (int i = 0; i < join_ga.ga_len; ++i) { - free(p->tofree); - ++p; - } - ga_clear(&join_ga); - } +# define FREE_JOIN_TOFREE(join) free((join)->tofree) + GA_DEEP_CLEAR(&join_ga, join_T, FREE_JOIN_TOFREE); return retval; } @@ -6466,7 +6458,7 @@ static struct fst { {"getpid", 0, 0, f_getpid}, {"getpos", 1, 1, f_getpos}, {"getqflist", 0, 0, f_getqflist}, - {"getreg", 0, 2, f_getreg}, + {"getreg", 0, 3, f_getreg}, {"getregtype", 0, 1, f_getregtype}, {"gettabvar", 2, 3, f_gettabvar}, {"gettabwinvar", 3, 4, f_gettabwinvar}, @@ -9526,30 +9518,44 @@ static void f_getqflist(typval_T *argvars, typval_T *rettv) (void)get_errorlist(wp, rettv->vval.v_list); } -/* - * "getreg()" function - */ +/// "getreg()" function static void f_getreg(typval_T *argvars, typval_T *rettv) { char_u *strregname; int regname; - int arg2 = FALSE; - int error = FALSE; + int arg2 = false; + bool return_list = false; + int error = false; if (argvars[0].v_type != VAR_UNKNOWN) { strregname = get_tv_string_chk(&argvars[0]); error = strregname == NULL; - if (argvars[1].v_type != VAR_UNKNOWN) + if (argvars[1].v_type != VAR_UNKNOWN) { arg2 = get_tv_number_chk(&argvars[1], &error); - } else + if (!error && argvars[2].v_type != VAR_UNKNOWN) { + return_list = get_tv_number_chk(&argvars[2], &error); + } + } + } else { strregname = vimvars[VV_REG].vv_str; + } + + if (error) { + return; + } + regname = (strregname == NULL ? '"' : *strregname); if (regname == 0) regname = '"'; - rettv->v_type = VAR_STRING; - rettv->vval.v_string = error ? NULL : - get_reg_contents(regname, TRUE, arg2); + if (return_list) { + rettv->v_type = VAR_LIST; + rettv->vval.v_list = + get_reg_contents(regname, (arg2 ? kGRegExprSrc : 0) | kGRegList); + } else { + rettv->v_type = VAR_STRING; + rettv->vval.v_string = get_reg_contents(regname, arg2 ? kGRegExprSrc : 0); + } } /* @@ -13314,7 +13320,6 @@ static void f_setreg(typval_T *argvars, typval_T *rettv) int regname; char_u *strregname; char_u *stropt; - char_u *strval; int append; char_u yank_type; long block_len; @@ -13331,8 +13336,6 @@ static void f_setreg(typval_T *argvars, typval_T *rettv) regname = *strregname; if (regname == 0 || regname == '@') regname = '"'; - else if (regname == '=') - return; if (argvars[2].v_type != VAR_UNKNOWN) { stropt = get_tv_string_chk(&argvars[2]); @@ -13360,10 +13363,46 @@ static void f_setreg(typval_T *argvars, typval_T *rettv) } } - strval = get_tv_string_chk(&argvars[1]); - if (strval != NULL) - write_reg_contents_ex(regname, strval, -1, - append, yank_type, block_len); + if (argvars[1].v_type == VAR_LIST) { + int len = argvars[1].vval.v_list->lv_len; + // First half: use for pointers to result lines; second half: use for + // pointers to allocated copies. + char_u **lstval = xmalloc(sizeof(char_u *) * ((len + 1) * 2)); + char_u **curval = lstval; + char_u **allocval = lstval + len + 2; + char_u **curallocval = allocval; + + char_u buf[NUMBUFLEN]; + for (listitem_T *li = argvars[1].vval.v_list->lv_first; + li != NULL; + li = li->li_next) { + char_u *strval = get_tv_string_buf_chk(&li->li_tv, buf); + if (strval == NULL) { + goto free_lstval; + } + if (strval == buf) { + // Need to make a copy, + // next get_tv_string_buf_chk() will overwrite the string. + strval = vim_strsave(buf); + *curallocval++ = strval; + } + *curval++ = strval; + } + *curval++ = NULL; + + write_reg_contents_lst(regname, lstval, -1, append, yank_type, block_len); + +free_lstval: + while (curallocval > allocval) + free(*--curallocval); + free(lstval); + } else { + char_u *strval = get_tv_string_chk(&argvars[1]); + if (strval == NULL) { + return; + } + write_reg_contents_ex(regname, strval, -1, append, yank_type, block_len); + } rettv->vval.v_number = 0; } @@ -16287,28 +16326,33 @@ static linenr_T get_tv_lnum_buf(typval_T *argvars, buf_T *buf) * get_tv_string_chk() and get_tv_string_buf_chk() are similar, but return * NULL on error. */ -static char_u *get_tv_string(typval_T *varp) +static char_u *get_tv_string(const typval_T *varp) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { static char_u mybuf[NUMBUFLEN]; return get_tv_string_buf(varp, mybuf); } -static char_u *get_tv_string_buf(typval_T *varp, char_u *buf) +static char_u *get_tv_string_buf(const typval_T *varp, char_u *buf) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { char_u *res = get_tv_string_buf_chk(varp, buf); return res != NULL ? res : (char_u *)""; } -char_u *get_tv_string_chk(typval_T *varp) +/// Careful: This uses a single, static buffer. YOU CAN ONLY USE IT ONCE! +char_u *get_tv_string_chk(const typval_T *varp) + FUNC_ATTR_NONNULL_ALL { static char_u mybuf[NUMBUFLEN]; return get_tv_string_buf_chk(varp, mybuf); } -static char_u *get_tv_string_buf_chk(typval_T *varp, char_u *buf) +static char_u *get_tv_string_buf_chk(const typval_T *varp, char_u *buf) + FUNC_ATTR_NONNULL_ALL { switch (varp->v_type) { case VAR_NUMBER: diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 5ae03c6be3..701e969393 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -5625,9 +5625,7 @@ helptags_one ( if (mix) got_int = FALSE; /* continue with other languages */ - for (int i = 0; i < ga.ga_len; ++i) - free(((char_u **)ga.ga_data)[i]); - ga_clear(&ga); + GA_DEEP_CLEAR_PTR(&ga); fclose(fd_tags); /* there is no check for an error... */ } diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 794e9930b9..fa78047a46 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -2580,13 +2580,11 @@ char_u *get_scriptname(scid_T id) } # if defined(EXITFREE) || defined(PROTO) -void free_scriptnames(void) +void free_scriptnames() { - for (int i = script_items.ga_len; i > 0; --i) - free(SCRIPT_ITEM(i).sn_name); - ga_clear(&script_items); +# define FREE_SCRIPTNAME(item) free((item)->sn_name) + GA_DEEP_CLEAR(&script_items, scriptitem_T, FREE_SCRIPTNAME); } - # endif diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index e180be4421..6bca1ff34d 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -97,6 +97,8 @@ typedef struct { linenr_T lnum; /* sourcing_lnum of the line */ } wcmd_T; +#define FREE_WCMD(wcmd) free((wcmd)->line) + /* * Structure used to store info for line position in a while or for loop. * This is required, because do_one_cmd() may invoke ex_function(), which @@ -708,9 +710,8 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, */ if (cstack.cs_looplevel == 0) { if (!GA_EMPTY(&lines_ga)) { - sourcing_lnum = - ((wcmd_T *)lines_ga.ga_data)[lines_ga.ga_len - 1].lnum; - free_cmdlines(&lines_ga); + sourcing_lnum = ((wcmd_T *)lines_ga.ga_data)[lines_ga.ga_len - 1].lnum; + GA_DEEP_CLEAR(&lines_ga, wcmd_T, FREE_WCMD); } current_line = 0; } @@ -777,8 +778,7 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, free(cmdline_copy); did_emsg_syntax = FALSE; - free_cmdlines(&lines_ga); - ga_clear(&lines_ga); + GA_DEEP_CLEAR(&lines_ga, wcmd_T, FREE_WCMD); if (cstack.cs_idx >= 0) { /* @@ -1018,17 +1018,6 @@ static void store_loop_line(garray_T *gap, char_u *line) } /* - * Free the lines stored for a ":while" or ":for" loop. - */ -static void free_cmdlines(garray_T *gap) -{ - while (!GA_EMPTY(gap)) { - free(((wcmd_T *)(gap->ga_data))[gap->ga_len - 1].line); - --gap->ga_len; - } -} - -/* * If "fgetline" is get_loop_line(), return TRUE if the getline it uses equals * "func". * Otherwise return TRUE when "fgetline" equals "func". */ @@ -4546,20 +4535,18 @@ void ex_comclear(exarg_T *eap) uc_clear(&curbuf->b_ucmds); } +static void free_ucmd(ucmd_T* cmd) { + free(cmd->uc_name); + free(cmd->uc_rep); + free(cmd->uc_compl_arg); +} + /* * Clear all user commands for "gap". */ void uc_clear(garray_T *gap) { - ucmd_T *cmd; - - for (int i = 0; i < gap->ga_len; ++i) { - cmd = USER_CMD_GA(gap, i); - free(cmd->uc_name); - free(cmd->uc_rep); - free(cmd->uc_compl_arg); - } - ga_clear(gap); + GA_DEEP_CLEAR(gap, ucmd_T, free_ucmd); } static void ex_delcommand(exarg_T *eap) @@ -5488,9 +5475,8 @@ static void ex_goto(exarg_T *eap) */ void alist_clear(alist_T *al) { - while (--al->al_ga.ga_len >= 0) - free(AARGLIST(al)[al->al_ga.ga_len].ae_fname); - ga_clear(&al->al_ga); +# define FREE_AENTRY_FNAME(arg) free(arg->ae_fname) + GA_DEEP_CLEAR(&al->al_ga, aentry_T, FREE_AENTRY_FNAME); } /* diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index d3051c5202..aed0484356 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -213,7 +213,7 @@ getcmdline ( /* autoindent for :insert and :append */ if (firstc <= 0) { - copy_spaces(ccline.cmdbuff, indent); + memset(ccline.cmdbuff, ' ', indent); ccline.cmdbuff[indent] = NUL; ccline.cmdpos = indent; ccline.cmdspos = indent; diff --git a/src/nvim/fold.c b/src/nvim/fold.c index e76aacbadc..505ac8da0d 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -1336,9 +1336,8 @@ static void deleteFoldEntry(garray_T *gap, int idx, int recursive) */ void deleteFoldRecurse(garray_T *gap) { - for (int i = 0; i < gap->ga_len; ++i) - deleteFoldRecurse(&(((fold_T *)(gap->ga_data))[i].fd_nested)); - ga_clear(gap); +# define DELETE_FOLD_NESTED(fd) deleteFoldRecurse(&((fd)->fd_nested)) + GA_DEEP_CLEAR(gap, fold_T, DELETE_FOLD_NESTED); } /* foldMarkAdjust() {{{2 */ diff --git a/src/nvim/garray.c b/src/nvim/garray.c index 08a38493bf..c4f8f66bfe 100644 --- a/src/nvim/garray.c +++ b/src/nvim/garray.c @@ -37,10 +37,7 @@ void ga_clear(garray_T *gap) /// @param gap void ga_clear_strings(garray_T *gap) { - for (int i = 0; i < gap->ga_len; ++i) { - free(((char_u **)(gap->ga_data))[i]); - } - ga_clear(gap); + GA_DEEP_CLEAR_PTR(gap); } /// Initialize a growing array. diff --git a/src/nvim/garray.h b/src/nvim/garray.h index b32bab52f7..b758fce5da 100644 --- a/src/nvim/garray.h +++ b/src/nvim/garray.h @@ -43,4 +43,30 @@ static inline void *ga_append_via_ptr(garray_T *gap, size_t item_size) return ((char *)gap->ga_data) + (item_size * (size_t)gap->ga_len++); } +/// Deep free a garray of specific type using a custom free function. +/// Items in the array as well as the array itself are freed. +/// +/// @param gap the garray to be freed +/// @param item_type type of the item in the garray +/// @param free_item_fn free function that takes (*item_type) as parameter +#define GA_DEEP_CLEAR(gap, item_type, free_item_fn) \ + do { \ + garray_T *_gap = (gap); \ + if (_gap->ga_data != NULL) { \ + for (int i = 0; i < _gap->ga_len; i++) { \ + item_type *_item = &(((item_type *)_gap->ga_data)[i]); \ + free_item_fn(_item); \ + } \ + } \ + ga_clear(_gap); \ + } while (false) + +#define FREE_PTR_PTR(ptr) free(*(ptr)) + +/// Call `free` for every pointer stored in the garray and then frees the +/// garray. +/// +/// @param gap the garray to be freed +#define GA_DEEP_CLEAR_PTR(gap) GA_DEEP_CLEAR(gap, void*, FREE_PTR_PTR) + #endif // NVIM_GARRAY_H diff --git a/src/nvim/memory.c b/src/nvim/memory.c index f959ea55e4..f38dfd56ac 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -234,6 +234,43 @@ void memchrsub(void *data, char c, char x, size_t len) } } +/// Counts the number of occurrences of `c` in `str`. +/// +/// @warning Unsafe if `c == NUL`. +/// +/// @param str Pointer to the string to search. +/// @param c The byte to search for. +/// @returns the number of occurrences of `c` in `str`. +size_t strcnt(const char *str, char c) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE +{ + assert(c != 0); + size_t cnt = 0; + while ((str = strchr(str, c))) { + cnt++; + str++; // Skip the instance of c. + } + return cnt; +} + +/// Counts the number of occurrences of byte `c` in `data[len]`. +/// +/// @param data Pointer to the data to search. +/// @param c The byte to search for. +/// @param len The length of `data`. +/// @returns the number of occurrences of `c` in `data[len]`. +size_t memcnt(const void *data, char c, size_t len) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE +{ + size_t cnt = 0; + const char *ptr = data, *end = ptr + len; + while ((ptr = memchr(ptr, c, (size_t)(end - ptr))) != NULL) { + cnt++; + ptr++; // Skip the instance of c. + } + return cnt; +} + /// The xstpcpy() function shall copy the string pointed to by src (including /// the terminating NUL character) into the array pointed to by dst. /// diff --git a/src/nvim/menu.c b/src/nvim/menu.c index 1573aaae84..b31b6c1cec 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -1424,6 +1424,12 @@ typedef struct { static garray_T menutrans_ga = GA_EMPTY_INIT_VALUE; +#define FREE_MENUTRANS(mt) \ + menutrans_T* _mt = (mt); \ + free(_mt->from); \ + free(_mt->from_noamp); \ + free(_mt->to) + /* * ":menutrans". * This function is also defined without the +multi_lang feature, in which @@ -1441,13 +1447,8 @@ void ex_menutranslate(exarg_T *eap) * ":menutrans clear": clear all translations. */ if (STRNCMP(arg, "clear", 5) == 0 && ends_excmd(*skipwhite(arg + 5))) { - menutrans_T *tp = (menutrans_T *)menutrans_ga.ga_data; - for (int i = 0; i < menutrans_ga.ga_len; ++i) { - free(tp[i].from); - free(tp[i].from_noamp); - free(tp[i].to); - } - ga_clear(&menutrans_ga); + GA_DEEP_CLEAR(&menutrans_ga, menutrans_T, FREE_MENUTRANS); + /* Delete all "menutrans_" global variables. */ del_menutrans_vars(); } else { diff --git a/src/nvim/ops.c b/src/nvim/ops.c index c20a017088..123cf6a1bc 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -369,8 +369,8 @@ static void shift_block(oparg_T *oap, int amount) newp = (char_u *) xmalloc((size_t)(bd.textcol + i + j + len)); memset(newp, NUL, (size_t)(bd.textcol + i + j + len)); memmove(newp, oldp, (size_t)bd.textcol); - copy_chars(newp + bd.textcol, (size_t)i, TAB); - copy_spaces(newp + bd.textcol + i, (size_t)j); + memset(newp + bd.textcol, TAB, (size_t)i); + memset(newp + bd.textcol + i, ' ', (size_t)j); /* the end */ memmove(newp + bd.textcol + i + j, bd.textstart, (size_t)len); } else { /* left */ @@ -455,7 +455,7 @@ static void shift_block(oparg_T *oap, int amount) newp = (char_u *) xmalloc((size_t)(new_line_len)); memmove(newp, oldp, (size_t)(verbatim_copy_end - oldp)); - copy_spaces(newp + (verbatim_copy_end - oldp), (size_t)fill); + memset(newp + (verbatim_copy_end - oldp), ' ', (size_t)fill); STRMOVE(newp + (verbatim_copy_end - oldp) + fill, non_white); } /* replace the line */ @@ -534,7 +534,7 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def oldp += offset; /* insert pre-padding */ - copy_spaces(newp + offset, (size_t)spaces); + memset(newp + offset, ' ', (size_t)spaces); /* copy the new text */ memmove(newp + offset + spaces, s, (size_t)s_len); @@ -542,7 +542,7 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def if (spaces && !bdp->is_short) { /* insert post-padding */ - copy_spaces(newp + offset + spaces, (size_t)(p_ts - spaces)); + memset(newp + offset + spaces, ' ', (size_t)(p_ts - spaces)); /* We're splitting a TAB, don't copy it. */ oldp++; /* We allowed for that TAB, remember this now */ @@ -1468,7 +1468,7 @@ int op_delete(oparg_T *oap) /* copy up to deleted part */ memmove(newp, oldp, (size_t)bd.textcol); /* insert spaces */ - copy_spaces(newp + bd.textcol, + memset(newp + bd.textcol, ' ', (size_t)(bd.startspaces + bd.endspaces)); /* copy the part after the deleted part */ oldp += bd.textcol + bd.textlen; @@ -1752,7 +1752,7 @@ int op_replace(oparg_T *oap, int c) memmove(newp, oldp, (size_t)bd.textcol); oldp += bd.textcol + bd.textlen; /* insert pre-spaces */ - copy_spaces(newp + bd.textcol, (size_t)bd.startspaces); + memset(newp + bd.textcol, ' ', (size_t)bd.startspaces); /* insert replacement chars CHECK FOR ALLOCATED SPACE */ /* -1/-2 is used for entering CR literally. */ if (had_ctrl_v_cr || (c != '\r' && c != '\n')) { @@ -1761,10 +1761,10 @@ int op_replace(oparg_T *oap, int c) while (--num_chars >= 0) n += (*mb_char2bytes)(c, newp + n); } else - copy_chars(newp + STRLEN(newp), (size_t)numc, c); + memset(newp + STRLEN(newp), c, (size_t)numc); if (!bd.is_short) { /* insert post-spaces */ - copy_spaces(newp + STRLEN(newp), (size_t)bd.endspaces); + memset(newp + STRLEN(newp), ' ', (size_t)bd.endspaces); /* copy the part after the changed part */ STRMOVE(newp + STRLEN(newp), oldp); } @@ -2263,7 +2263,7 @@ int op_change(oparg_T *oap) /* copy up to block start */ memmove(newp, oldp, (size_t)bd.textcol); offset = bd.textcol; - copy_spaces(newp + offset, (size_t)vpos.coladd); + memset(newp + offset, ' ', (size_t)vpos.coladd); offset += vpos.coladd; memmove(newp + offset, ins_text, (size_t)ins_len); offset += ins_len; @@ -2554,11 +2554,11 @@ static void yank_copy_line(struct block_def *bd, long y_idx) char_u *pnew = xmallocz(bd->startspaces + bd->endspaces + bd->textlen); y_current->y_array[y_idx] = pnew; - copy_spaces(pnew, (size_t)bd->startspaces); + memset(pnew, ' ', (size_t)bd->startspaces); pnew += bd->startspaces; memmove(pnew, bd->textstart, (size_t)bd->textlen); pnew += bd->textlen; - copy_spaces(pnew, (size_t)bd->endspaces); + memset(pnew, ' ', (size_t)bd->endspaces); pnew += bd->endspaces; *pnew = NUL; } @@ -2867,7 +2867,7 @@ do_put ( memmove(ptr, oldp, (size_t)bd.textcol); ptr += bd.textcol; /* may insert some spaces before the new text */ - copy_spaces(ptr, (size_t)bd.startspaces); + memset(ptr, ' ', (size_t)bd.startspaces); ptr += bd.startspaces; /* insert the new text */ for (j = 0; j < count; ++j) { @@ -2876,12 +2876,12 @@ do_put ( /* insert block's trailing spaces only if there's text behind */ if ((j < count - 1 || !shortline) && spaces) { - copy_spaces(ptr, (size_t)spaces); + memset(ptr, ' ', (size_t)spaces); ptr += spaces; } } /* may insert some spaces after the new text */ - copy_spaces(ptr, (size_t)bd.endspaces); + memset(ptr, ' ', (size_t)bd.endspaces); ptr += bd.endspaces; /* move the text after the cursor to the end of the line. */ memmove(ptr, oldp + bd.textcol + delcount, @@ -3523,7 +3523,7 @@ int do_join(long count, memmove(cend, curr, (size_t)currsize); if (spaces[t] > 0) { cend -= spaces[t]; - copy_spaces(cend, (size_t)(spaces[t])); + memset(cend, ' ', (size_t)(spaces[t])); } mark_col_adjust(curwin->w_cursor.lnum + t, (colnr_T)0, (linenr_T)-t, (long)(cend - newp + spaces[t] - (curr - curr_start))); @@ -4654,30 +4654,43 @@ char_u get_reg_type(int regname, long *reglen) return MAUTO; } -/* - * Return the contents of a register as a single allocated string. - * Used for "@r" in expressions and for getreg(). - * Returns NULL for error. - */ -char_u * -get_reg_contents ( - int regname, - int allowexpr, /* allow "=" register */ - int expr_src /* get expression for "=" register */ -) +/// When `flags` has `kGRegList` return a list with text `s`. +/// Otherwise just return `s`. +/// +/// Returns a void * for use in get_reg_contents(). +static void *get_reg_wrap_one_line(char_u *s, int flags) +{ + if (!(flags & kGRegList)) { + return s; + } + list_T *list = list_alloc(); + list_append_string(list, NULL, -1); + list->lv_first->li_tv.vval.v_string = s; + return list; +} + +/// Gets the contents of a register. +/// @remark Used for `@r` in expressions and for `getreg()`. +/// +/// @param regname The register. +/// @param flags see @ref GRegFlags +/// +/// @returns The contents of the register as an allocated string. +/// @returns A linked list when `flags` contains @ref kGRegList. +/// @returns NULL for error. +void *get_reg_contents(int regname, int flags) { long i; - char_u *retval; - int allocated; - /* Don't allow using an expression register inside an expression */ + // Don't allow using an expression register inside an expression. if (regname == '=') { - if (allowexpr) { - if (expr_src) - return get_expr_line_src(); - return get_expr_line(); + if (flags & kGRegNoExpr) { + return NULL; } - return NULL; + if (flags & kGRegExprSrc) { + return get_reg_wrap_one_line(get_expr_line_src(), flags); + } + return get_reg_wrap_one_line(get_expr_line(), flags); } if (regname == '@') /* "@@" is used for unnamed register */ @@ -4689,18 +4702,30 @@ get_reg_contents ( get_clipboard(regname); + char_u *retval; + int allocated; if (get_spec_reg(regname, &retval, &allocated, FALSE)) { if (retval == NULL) return NULL; - if (!allocated) - retval = vim_strsave(retval); - return retval; + if (allocated) { + return get_reg_wrap_one_line(retval, flags); + } + return get_reg_wrap_one_line(vim_strsave(retval), flags); } get_yank_register(regname, FALSE); if (y_current->y_array == NULL) return NULL; + if (flags & kGRegList) { + list_T *list = list_alloc(); + for (int i = 0; i < y_current->y_size; ++i) { + list_append_string(list, y_current->y_array[i], -1); + } + + return list; + } + /* * Compute length of resulting string. */ @@ -4737,17 +4762,77 @@ get_reg_contents ( return retval; } +static bool init_write_reg(int name, struct yankreg **old_y_previous, + struct yankreg **old_y_current, int must_append) +{ + if (!valid_yank_reg(name, true)) { // check for valid reg name + emsg_invreg(name); + return false; + } + + // Don't want to change the current (unnamed) register. + *old_y_previous = y_previous; + *old_y_current = y_current; + + get_yank_register(name, true); + if (!y_append && !must_append) { + free_yank_all(); + } + return true; +} + +static void finish_write_reg(int name, struct yankreg *old_y_previous, + struct yankreg *old_y_current) +{ + // Send text of clipboard register to the clipboard. + set_clipboard(name); + + // ':let @" = "val"' should change the meaning of the "" register + if (name != '"') { + y_previous = old_y_previous; + } + y_current = old_y_current; +} + /// write_reg_contents - store `str` in register `name` /// /// @see write_reg_contents_ex -void write_reg_contents(int name, - const char_u *str, - ssize_t len, +void write_reg_contents(int name, const char_u *str, ssize_t len, int must_append) { write_reg_contents_ex(name, str, len, must_append, MAUTO, 0L); } +void write_reg_contents_lst(int name, char_u **strings, int maxlen, + int must_append, int yank_type, long block_len) +{ + if (name == '/' || name == '=') { + char_u *s = strings[0]; + if (strings[0] == NULL) { + s = (char_u *)""; + } else if (strings[1] != NULL) { + EMSG(_("E883: search pattern and expression register may not " + "contain two or more lines")); + return; + } + write_reg_contents_ex(name, s, -1, must_append, yank_type, block_len); + return; + } + + // black hole: nothing to do + if (name == '_') { + return; + } + + struct yankreg *old_y_previous, *old_y_current; + if (!init_write_reg(name, &old_y_previous, &old_y_current, must_append)) { + return; + } + + str_to_reg(y_current, yank_type, (char_u *) strings, -1, block_len, true); + finish_write_reg(name, old_y_previous, old_y_current); +} + /// write_reg_contents_ex - store `str` in register `name` /// /// If `str` ends in '\n' or '\r', use linewise, otherwise use @@ -4774,8 +4859,6 @@ void write_reg_contents_ex(int name, int yank_type, long block_len) { - struct yankreg *old_y_previous, *old_y_current; - if (len < 0) { len = (ssize_t) STRLEN(str); } @@ -4809,29 +4892,16 @@ void write_reg_contents_ex(int name, return; } - if (!valid_yank_reg(name, TRUE)) { /* check for valid reg name */ - emsg_invreg(name); + if (name == '_') { // black hole: nothing to do return; } - if (name == '_') /* black hole: nothing to do */ - return; - - /* Don't want to change the current (unnamed) register */ - old_y_previous = y_previous; - old_y_current = y_current; - - get_yank_register(name, TRUE); - if (!y_append && !must_append) - free_yank_all(); - str_to_reg(y_current, yank_type, str, len, block_len); - set_clipboard(name); - - - /* ':let @" = "val"' should change the meaning of the "" register */ - if (name != '"') - y_previous = old_y_previous; - y_current = old_y_current; + struct yankreg *old_y_previous, *old_y_current; + if (!init_write_reg(name, &old_y_previous, &old_y_current, must_append)) { + return; + } + str_to_reg(y_current, yank_type, str, len, block_len, false); + finish_write_reg(name, old_y_previous, old_y_current); } /// str_to_reg - Put a string into a register. @@ -4840,100 +4910,100 @@ void write_reg_contents_ex(int name, /// /// @param y_ptr pointer to yank register /// @param yank_type MCHAR, MLINE, MBLOCK or MAUTO -/// @param str string to put in register -/// @param len length of the string -/// @param blocklen width of visual block -static void str_to_reg(struct yankreg *y_ptr, - int yank_type, - const char_u *str, - long len, - long blocklen) +/// @param str string or list of strings to put in register +/// @param len length of the string (Ignored when str_list=true.) +/// @param blocklen width of visual block, or -1 for "I don't know." +/// @param str_list True if str is `char_u **`. +static void str_to_reg(struct yankreg *y_ptr, int yank_type, const char_u *str, + size_t len, colnr_T blocklen, bool str_list) + FUNC_ATTR_NONNULL_ALL { - int type; /* MCHAR, MLINE or MBLOCK */ - int lnum; - long start; - long i; - int extra; - size_t newlines; /* number of lines added */ - int extraline = 0; /* extra line at the end */ - int append = FALSE; /* append to last line in register */ - char_u *s; - char_u **pp; - long maxlen; - - if (y_ptr->y_array == NULL) /* NULL means empty register */ + if (y_ptr->y_array == NULL) { // NULL means empty register y_ptr->y_size = 0; + } - if (yank_type == MAUTO) - type = ((len > 0 && (str[len - 1] == NL || str[len - 1] == CAR)) + int type = yank_type; // MCHAR, MLINE or MBLOCK + if (yank_type == MAUTO) { + type = ((str_list || + (len > 0 && (str[len - 1] == NL || str[len - 1] == CAR))) ? MLINE : MCHAR); - else - type = yank_type; - - /* - * Count the number of lines within the string - */ - newlines = 0; - for (i = 0; i < len; i++) - if (str[i] == '\n') - ++newlines; - if (type == MCHAR || len == 0 || str[len - 1] != '\n') { - extraline = 1; - ++newlines; /* count extra newline at the end */ } - if (y_ptr->y_size > 0 && y_ptr->y_type == MCHAR) { - append = TRUE; - --newlines; /* uncount newline when appending first line */ + + size_t newlines = 0; + bool extraline = false; // extra line at the end + bool append = false; // append to last line in register + + // Count the number of lines within the string + if (str_list) { + for (char_u **ss = (char_u **) str; *ss != NULL; ++ss) { + newlines++; + } + } else { + newlines = memcnt(str, '\n', len); + if (type == MCHAR || len == 0 || str[len - 1] != '\n') { + extraline = 1; + ++newlines; // count extra newline at the end + } + if (y_ptr->y_size > 0 && y_ptr->y_type == MCHAR) { + append = true; + --newlines; // uncount newline when appending first line + } } - /* - * Allocate an array to hold the pointers to the new register lines. - * If the register was not empty, move the existing lines to the new array. - */ - pp = xcalloc((y_ptr->y_size + newlines), sizeof(char_u *)); - for (lnum = 0; lnum < y_ptr->y_size; ++lnum) - pp[lnum] = y_ptr->y_array[lnum]; - free(y_ptr->y_array); + + // Grow the register array to hold the pointers to the new lines. + char_u **pp = xrealloc(y_ptr->y_array, + (y_ptr->y_size + newlines) * sizeof(char_u *)); y_ptr->y_array = pp; - maxlen = 0; - /* - * Find the end of each line and save it into the array. - */ - for (start = 0; start < len + extraline; start += i + 1) { - // Let i represent the length of one line. - const char_u *p = str + start; - i = (char_u *)xmemscan(p, '\n', len - start) - p; - if (i > maxlen) - maxlen = i; - if (append) { - --lnum; - extra = (int)STRLEN(y_ptr->y_array[lnum]); - } else - extra = 0; - s = xmalloc(i + extra + 1); - if (extra) - memmove(s, y_ptr->y_array[lnum], (size_t)extra); - if (append) - free(y_ptr->y_array[lnum]); - if (i) - memmove(s + extra, str + start, (size_t)i); - extra += i; - s[extra] = NUL; - y_ptr->y_array[lnum++] = s; - while (--extra >= 0) { - if (*s == NUL) - *s = '\n'; /* replace NUL with newline */ - ++s; + linenr_T lnum = y_ptr->y_size; // The current line number. + + // If called with `blocklen < 0`, we have to update the yank reg's width. + size_t maxlen = 0; + + // Find the end of each line and save it into the array. + if (str_list) { + for (char_u **ss = (char_u **) str; *ss != NULL; ++ss, ++lnum) { + size_t ss_len = STRLEN(*ss); + pp[lnum] = xmemdupz(*ss, ss_len); + if (ss_len > maxlen) { + maxlen = ss_len; + } + } + } else { + size_t line_len; + for (const char_u *start = str, *end = str + len; + start < end + extraline; + start += line_len + 1, lnum++) { + line_len = (const char_u *) xmemscan(start, '\n', end - start) - start; + if (line_len > maxlen) { + maxlen = line_len; + } + + // When appending, copy the previous line and free it after. + size_t extra = append ? STRLEN(pp[--lnum]) : 0; + char_u *s = xmallocz(line_len + extra); + memcpy(s, pp[lnum], extra); + memcpy(s + extra, start, line_len); + ssize_t s_len = extra + line_len; + + if (append) { + free(pp[lnum]); + append = false; // only first line is appended + } + pp[lnum] = s; + + // Convert NULs to '\n' to prevent truncation. + memchrsub(pp[lnum], NUL, '\n', s_len); } - append = FALSE; /* only first line is appended */ } y_ptr->y_type = type; y_ptr->y_size = lnum; - if (type == MBLOCK) - y_ptr->y_width = (blocklen < 0 ? maxlen - 1 : blocklen); - else + if (type == MBLOCK) { + y_ptr->y_width = (blocklen == -1 ? (colnr_T) maxlen - 1 : blocklen); + } else { y_ptr->y_width = 0; + } } void clear_oparg(oparg_T *oap) diff --git a/src/nvim/ops.h b/src/nvim/ops.h index ca70684ab8..3251042c8f 100644 --- a/src/nvim/ops.h +++ b/src/nvim/ops.h @@ -47,6 +47,13 @@ typedef int (*Indenter)(void); #define OP_FORMAT2 26 /* "gw" format operator, keeps cursor pos */ #define OP_FUNCTION 27 /* "g@" call 'operatorfunc' */ +/// Flags for get_reg_contents(). +enum GRegFlags { + kGRegNoExpr = 1, ///< Do not allow expression register. + kGRegExprSrc = 2, ///< Return expression itself for "=" register. + kGRegList = 4 ///< Return list. +}; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ops.h.generated.h" #endif diff --git a/src/nvim/po/CMakeLists.txt b/src/nvim/po/CMakeLists.txt index 6a56e302e9..443f3c6595 100644 --- a/src/nvim/po/CMakeLists.txt +++ b/src/nvim/po/CMakeLists.txt @@ -2,8 +2,7 @@ find_package(Gettext) find_program(XGETTEXT_PRG xgettext) find_program(ICONV_PRG iconv) -if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG AND - NOT DEFINED ENV{SKIP_EXEC}) +if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG) set(ENV{OLD_PO_FILE_INPUT} yes) set(ENV{OLD_PO_FILE_OUTPUT} yes) diff --git a/src/nvim/screen.c b/src/nvim/screen.c index c0a909f147..9e21676b2a 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -2139,7 +2139,7 @@ fill_foldcolumn ( int empty; /* Init to all spaces. */ - copy_spaces(p, (size_t)wp->w_p_fdc); + memset(p, ' ', (size_t)wp->w_p_fdc); level = win_foldinfo.fi_level; if (level > 0) { diff --git a/src/nvim/spell.c b/src/nvim/spell.c index fa786fdd74..83dcddecd6 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -2379,13 +2379,26 @@ static void slang_free(slang_T *lp) free(lp); } +/// Frees a salitem_T +static void free_salitem(salitem_T *smp) { + free(smp->sm_lead); + // Don't free sm_oneof and sm_rules, they point into sm_lead. + free(smp->sm_to); + free(smp->sm_lead_w); + free(smp->sm_oneof_w); + free(smp->sm_to_w); +} + +/// Frees a fromto_T +static void free_fromto(fromto_T *ftp) { + free(ftp->ft_from); + free(ftp->ft_to); +} + // Clear an slang_T so that the file can be reloaded. static void slang_clear(slang_T *lp) { garray_T *gap; - fromto_T *ftp; - salitem_T *smp; - int round; free(lp->sl_fbyts); lp->sl_fbyts = NULL; @@ -2401,36 +2414,17 @@ static void slang_clear(slang_T *lp) free(lp->sl_pidxs); lp->sl_pidxs = NULL; - for (round = 1; round <= 2; ++round) { - gap = round == 1 ? &lp->sl_rep : &lp->sl_repsal; - while (!GA_EMPTY(gap)) { - ftp = &((fromto_T *)gap->ga_data)[--gap->ga_len]; - free(ftp->ft_from); - free(ftp->ft_to); - } - ga_clear(gap); - } + GA_DEEP_CLEAR(&lp->sl_rep, fromto_T, free_fromto); + GA_DEEP_CLEAR(&lp->sl_repsal, fromto_T, free_fromto); gap = &lp->sl_sal; if (lp->sl_sofo) { // "ga_len" is set to 1 without adding an item for latin1 - if (gap->ga_data != NULL) - // SOFOFROM and SOFOTO items: free lists of wide characters. - for (int i = 0; i < gap->ga_len; ++i) { - free(((int **)gap->ga_data)[i]); - } - } else + GA_DEEP_CLEAR_PTR(gap); + } else { // SAL items: free salitem_T items - while (!GA_EMPTY(gap)) { - smp = &((salitem_T *)gap->ga_data)[--gap->ga_len]; - free(smp->sm_lead); - // Don't free sm_oneof and sm_rules, they point into sm_lead. - free(smp->sm_to); - free(smp->sm_lead_w); - free(smp->sm_oneof_w); - free(smp->sm_to_w); - } - ga_clear(gap); + GA_DEEP_CLEAR(gap, salitem_T, free_salitem); + } for (int i = 0; i < lp->sl_prefixcnt; ++i) { vim_regfree(lp->sl_prefprog[i]); @@ -9206,15 +9200,10 @@ static void tree_count_words(char_u *byts, idx_T *idxs) // Free the info put in "*su" by spell_find_suggest(). static void spell_find_cleanup(suginfo_T *su) { +# define FREE_SUG_WORD(sug) free(sug->st_word) // Free the suggestions. - for (int i = 0; i < su->su_ga.ga_len; ++i) { - free(SUG(su->su_ga, i).st_word); - } - ga_clear(&su->su_ga); - for (int i = 0; i < su->su_sga.ga_len; ++i) { - free(SUG(su->su_sga, i).st_word); - } - ga_clear(&su->su_sga); + GA_DEEP_CLEAR(&su->su_ga, suggest_T, FREE_SUG_WORD); + GA_DEEP_CLEAR(&su->su_sga, suggest_T, FREE_SUG_WORD); // Free the banned words. hash_clear_all(&su->su_banned, 0); diff --git a/src/nvim/strings.c b/src/nvim/strings.c index 1e619b1c6e..25e4a6c93b 100644 --- a/src/nvim/strings.c +++ b/src/nvim/strings.c @@ -288,33 +288,6 @@ char_u *strup_save(const char_u *orig) } /* - * copy a space a number of times - */ -void copy_spaces(char_u *ptr, size_t count) - FUNC_ATTR_NONNULL_ALL -{ - size_t i = count; - char_u *p = ptr; - - while (i--) - *p++ = ' '; -} - -/* - * Copy a character a number of times. - * Does not work for multi-byte characters! - */ -void copy_chars(char_u *ptr, size_t count, char_u c) - FUNC_ATTR_NONNULL_ALL -{ - size_t i = count; - char_u *p = ptr; - - while (i--) - *p++ = c; -} - -/* * delete spaces at the end of a string */ void del_trailing_spaces(char_u *ptr) diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 45b42730bc..896f27f9e4 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -550,14 +550,9 @@ void syntax_start(win_T *wp, linenr_T lnum) */ static void clear_syn_state(synstate_T *p) { - garray_T *gap; - if (p->sst_stacksize > SST_FIX_STATES) { - gap = &(p->sst_union.sst_ga); - for (int i = 0; i < gap->ga_len; i++) { - unref_extmatch(SYN_STATE_P(gap)[i].bs_extmatch); - } - ga_clear(gap); +# define UNREF_BUFSTATE_EXTMATCH(bs) unref_extmatch((bs)->bs_extmatch) + GA_DEEP_CLEAR(&(p->sst_union.sst_ga), bufstate_T, UNREF_BUFSTATE_EXTMATCH); } else { for (int i = 0; i < p->sst_stacksize; i++) { unref_extmatch(p->sst_union.sst_stack[i].bs_extmatch); @@ -570,11 +565,8 @@ static void clear_syn_state(synstate_T *p) */ static void clear_current_state(void) { - stateitem_T *sip = (stateitem_T *)(current_state.ga_data); - for (int i = 0; i < current_state.ga_len; i++) { - unref_extmatch(sip[i].si_extmatch); - } - ga_clear(¤t_state); +# define UNREF_STATEITEM_EXTMATCH(si) unref_extmatch((si)->si_extmatch) + GA_DEEP_CLEAR(¤t_state, stateitem_T, UNREF_STATEITEM_EXTMATCH); } /* diff --git a/src/nvim/testdir/test_eval.in b/src/nvim/testdir/test_eval.in index a29fefc3b6..95a59ee42a 100644 --- a/src/nvim/testdir/test_eval.in +++ b/src/nvim/testdir/test_eval.in @@ -1,7 +1,154 @@ +Test for various eval features. vim: set ft=vim : + +Note: system clipboard support is not tested. I do not think anybody will thank +me for messing with clipboard. + STARTTEST :so small.vim :set encoding=latin1 :set noswapfile +:lang C +:fun AppendRegContents(reg) + call append('$', printf('%s: type %s; value: %s (%s), expr: %s (%s)', a:reg, getregtype(a:reg), getreg(a:reg), string(getreg(a:reg, 0, 1)), getreg(a:reg, 1), string(getreg(a:reg, 1, 1)))) +endfun +:command -nargs=? AR :call AppendRegContents(<q-args>) +:fun SetReg(...) + call call('setreg', a:000) + call append('$', printf('{{{2 setreg(%s)', string(a:000)[1:-2])) + call AppendRegContents(a:1) + if a:1 isnot# '=' + execute "silent normal! Go==\n==\e\"".a:1."P" + endif +endfun +:fun ErrExe(str) + call append('$', 'Executing '.a:str) + try + execute a:str + catch + $put =v:exception + endtry +endfun +:fun Test() +$put ='{{{1 let tests' +let @" = 'abc' +AR " +let @" = "abc\n" +AR " +let @" = "abc\<C-m>" +AR " +let @= = '"abc"' +AR = + +$put ='{{{1 Basic setreg tests' +call SetReg('a', 'abcA', 'c') +call SetReg('b', 'abcB', 'v') +call SetReg('c', 'abcC', 'l') +call SetReg('d', 'abcD', 'V') +call SetReg('e', 'abcE', 'b') +call SetReg('f', 'abcF', "\<C-v>") +call SetReg('g', 'abcG', 'b10') +call SetReg('h', 'abcH', "\<C-v>10") +call SetReg('I', 'abcI') + +$put ='{{{1 Appending single lines with setreg()' +call SetReg('A', 'abcAc', 'c') +call SetReg('A', 'abcAl', 'l') +call SetReg('A', 'abcAc2','c') +call SetReg('b', 'abcBc', 'ca') +call SetReg('b', 'abcBb', 'ba') +call SetReg('b', 'abcBc2','ca') +call SetReg('b', 'abcBb2','b50a') + +call SetReg('C', 'abcCl', 'l') +call SetReg('C', 'abcCc', 'c') +call SetReg('D', 'abcDb', 'b') + +call SetReg('E', 'abcEb', 'b') +call SetReg('E', 'abcEl', 'l') +call SetReg('F', 'abcFc', 'c') + +$put ='{{{1 Appending NL with setreg()' +call setreg('a', 'abcA2', 'c') +call setreg('b', 'abcB2', 'v') +call setreg('c', 'abcC2', 'l') +call setreg('d', 'abcD2', 'V') +call setreg('e', 'abcE2', 'b') +call setreg('f', 'abcF2', "\<C-v>") +call setreg('g', 'abcG2', 'b10') +call setreg('h', 'abcH2', "\<C-v>10") +call setreg('I', 'abcI2') + +call SetReg('A', "\n") +call SetReg('B', "\n", 'c') +call SetReg('C', "\n") +call SetReg('D', "\n", 'l') +call SetReg('E', "\n") +call SetReg('F', "\n", 'b') + +$put ='{{{1 Setting lists with setreg()' +call SetReg('a', ['abcA3'], 'c') +call SetReg('b', ['abcB3'], 'l') +call SetReg('c', ['abcC3'], 'b') +call SetReg('d', ['abcD3']) +call SetReg('e', [1, 2, 'abc', 3]) +call SetReg('f', [1, 2, 3]) + +$put ='{{{1 Appending lists with setreg()' +call SetReg('A', ['abcA3c'], 'c') +call SetReg('b', ['abcB3l'], 'la') +call SetReg('C', ['abcC3b'], 'lb') +call SetReg('D', ['abcD32']) + +call SetReg('A', ['abcA32']) +call SetReg('B', ['abcB3c'], 'c') +call SetReg('C', ['abcC3l'], 'l') +call SetReg('D', ['abcD3b'], 'b') + +$put ='{{{1 Appending lists with NL with setreg()' +call SetReg('A', ["\n", 'abcA3l2'], 'l') +call SetReg('B', ["\n", 'abcB3c2'], 'c') +call SetReg('C', ["\n", 'abcC3b2'], 'b') +call SetReg('D', ["\n", 'abcD3b50'],'b50') + +$put ='{{{1 Setting lists with NLs with setreg()' +call SetReg('a', ['abcA4-0', "\n", "abcA4-2\n", "\nabcA4-3", "abcA4-4\nabcA4-4-2"]) +call SetReg('b', ['abcB4c-0', "\n", "abcB4c-2\n", "\nabcB4c-3", "abcB4c-4\nabcB4c-4-2"], 'c') +call SetReg('c', ['abcC4l-0', "\n", "abcC4l-2\n", "\nabcC4l-3", "abcC4l-4\nabcC4l-4-2"], 'l') +call SetReg('d', ['abcD4b-0', "\n", "abcD4b-2\n", "\nabcD4b-3", "abcD4b-4\nabcD4b-4-2"], 'b') +call SetReg('e', ['abcE4b10-0', "\n", "abcE4b10-2\n", "\nabcE4b10-3", "abcE4b10-4\nabcE4b10-4-2"], 'b10') + +$put ='{{{1 Search and expressions' +call SetReg('/', ['abc/']) +call SetReg('/', ["abc/\n"]) +call SetReg('=', ['"abc/"']) +call SetReg('=', ["\"abc/\n\""]) + +$put ='{{{1 Errors' +call ErrExe('call setreg()') +call ErrExe('call setreg(1)') +call ErrExe('call setreg(1, 2, 3, 4)') +call ErrExe('call setreg([], 2)') +call ErrExe('call setreg(1, {})') +call ErrExe('call setreg(1, 2, [])') +call ErrExe('call setreg("/", ["1", "2"])') +call ErrExe('call setreg("=", ["1", "2"])') +call ErrExe('call setreg(1, ["", "", [], ""])') +endfun +:" +:call Test() +:" +:delfunction SetReg +:delfunction AppendRegContents +:delfunction ErrExe +:delfunction Test +:delcommand AR +:call garbagecollect(1) +:" +:/^start:/+1,$wq! test.out +:" vim: et ts=4 isk-=\: fmr=???,??? +:call getchar() +:e test.out +:%d :" function name not starting with a capital :try diff --git a/src/nvim/testdir/test_eval.ok b/src/nvim/testdir/test_eval.ok Binary files differindex 63b9156442..061e0cfd2f 100644 --- a/src/nvim/testdir/test_eval.ok +++ b/src/nvim/testdir/test_eval.ok diff --git a/src/nvim/version.c b/src/nvim/version.c index f73e5c8cae..6f37982f4d 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -416,14 +416,14 @@ static int included_patches[] = { //252 NA 251, //250 NA - //249, + 249, 248, 247, //246, 245, //244, - //243, - //242, + 243, + 242, 241, 240, 239, |