aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.ci/clang-asan.sh4
-rw-r--r--src/nvim/CMakeLists.txt43
-rw-r--r--src/nvim/eval.c112
-rw-r--r--src/nvim/ex_cmds.c4
-rw-r--r--src/nvim/ex_cmds2.c8
-rw-r--r--src/nvim/ex_docmd.c42
-rw-r--r--src/nvim/fold.c5
-rw-r--r--src/nvim/garray.c5
-rw-r--r--src/nvim/garray.h26
-rw-r--r--src/nvim/memory.c37
-rw-r--r--src/nvim/menu.c15
-rw-r--r--src/nvim/normal.c28
-rw-r--r--src/nvim/ops.c324
-rw-r--r--src/nvim/ops.h7
-rw-r--r--src/nvim/os/input.c93
-rw-r--r--src/nvim/po/CMakeLists.txt3
-rw-r--r--src/nvim/spell.c61
-rw-r--r--src/nvim/syntax.c16
-rw-r--r--src/nvim/testdir/test_eval.in147
-rw-r--r--src/nvim/testdir/test_eval.okbin432 -> 10578 bytes
-rw-r--r--src/nvim/version.c6
-rw-r--r--test/functional/ui/mouse_spec.lua157
-rw-r--r--test/functional/ui/screen.lua6
23 files changed, 840 insertions, 309 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/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..c221b13f8b 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, size_t len)
+ 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/normal.c b/src/nvim/normal.c
index 1d4b47414c..3b4d4047db 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -1856,21 +1856,21 @@ do_mouse (
save_cursor = curwin->w_cursor;
- /*
- * When GUI is active, always recognize mouse events, otherwise:
- * - Ignore mouse event in normal mode if 'mouse' doesn't include 'n'.
- * - Ignore mouse event in visual mode if 'mouse' doesn't include 'v'.
- * - For command line and insert mode 'mouse' is checked before calling
- * do_mouse().
- */
- if (do_always)
- do_always = false;
- else {
- if (VIsual_active) {
- if (!mouse_has(MOUSE_VISUAL))
+ // When "abstract_ui" is active, always recognize mouse events, otherwise:
+ // - Ignore mouse event in normal mode if 'mouse' doesn't include 'n'.
+ // - Ignore mouse event in visual mode if 'mouse' doesn't include 'v'.
+ // - For command line and insert mode 'mouse' is checked before calling
+ // do_mouse().
+ if (!abstract_ui) {
+ if (do_always)
+ do_always = false;
+ else {
+ if (VIsual_active) {
+ if (!mouse_has(MOUSE_VISUAL))
+ return false;
+ } else if (State == NORMAL && !mouse_has(MOUSE_NORMAL))
return false;
- } else if (State == NORMAL && !mouse_has(MOUSE_NORMAL))
- return false;
+ }
}
for (;; ) {
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index 4f14f4ee6c..123cf6a1bc 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -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/os/input.c b/src/nvim/os/input.c
index 246ebf123c..cddc28fac9 100644
--- a/src/nvim/os/input.c
+++ b/src/nvim/os/input.c
@@ -183,17 +183,19 @@ size_t input_enqueue(String keys)
char *ptr = keys.data, *end = ptr + keys.size;
while (rbuffer_available(input_buffer) >= 6 && ptr < end) {
- int new_size = trans_special((char_u **)&ptr,
- (char_u *)rbuffer_write_ptr(input_buffer),
- false);
+ uint8_t buf[6] = {0};
+ int new_size = trans_special((uint8_t **)&ptr, buf, false);
+
if (!new_size) {
// copy the character unmodified
- *rbuffer_write_ptr(input_buffer) = *ptr++;
+ *buf = (uint8_t)*ptr++;
new_size = 1;
}
+
+ new_size = handle_mouse_event(&ptr, buf, new_size);
// TODO(tarruda): Don't produce past unclosed '<' characters, except if
// there's a lot of characters after the '<'
- rbuffer_produced(input_buffer, (size_t)new_size);
+ rbuffer_write(input_buffer, (char *)buf, (size_t)new_size);
}
size_t rv = (size_t)(ptr - keys.data);
@@ -201,6 +203,87 @@ size_t input_enqueue(String keys)
return rv;
}
+// Mouse event handling code(Extract row/col if available and detect multiple
+// clicks)
+static int handle_mouse_event(char **ptr, uint8_t *buf, int bufsize)
+{
+ int mouse_code = 0;
+
+ if (bufsize == 3) {
+ mouse_code = buf[2];
+ } else if (bufsize == 6) {
+ // prefixed with K_SPECIAL KS_MODIFIER mod
+ mouse_code = buf[5];
+ }
+
+ if (mouse_code < KE_LEFTMOUSE || mouse_code > KE_RIGHTRELEASE) {
+ return bufsize;
+ }
+
+ // a <[COL],[ROW]> sequence can follow and will set the mouse_row/mouse_col
+ // global variables. This is ugly but its how the rest of the code expects to
+ // find mouse coordinates, and it would be too expensive to refactor this
+ // now.
+ int col, row, advance;
+ if (sscanf(*ptr, "<%d,%d>%n", &col, &row, &advance)) {
+ if (col >= 0 && row >= 0) {
+ mouse_row = row;
+ mouse_col = col;
+ }
+ *ptr += advance;
+ }
+
+ static int orig_num_clicks = 0;
+ static int orig_mouse_code = 0;
+ static int orig_mouse_col = 0;
+ static int orig_mouse_row = 0;
+ static uint64_t orig_mouse_time = 0; // time of previous mouse click
+ uint64_t mouse_time = os_hrtime(); // time of current mouse click
+
+ // compute the time elapsed since the previous mouse click and
+ // convert p_mouse from ms to ns
+ uint64_t timediff = mouse_time - orig_mouse_time;
+ uint64_t mouset = (uint64_t)p_mouset * 1000000;
+ if (mouse_code == orig_mouse_code
+ && timediff < mouset
+ && orig_num_clicks != 4
+ && orig_mouse_col == mouse_col
+ && orig_mouse_row == mouse_row) {
+ orig_num_clicks++;
+ } else {
+ orig_num_clicks = 1;
+ }
+ orig_mouse_code = mouse_code;
+ orig_mouse_col = mouse_col;
+ orig_mouse_row = mouse_row;
+ orig_mouse_time = mouse_time;
+
+ int modifiers = 0;
+ if (orig_num_clicks == 2) {
+ modifiers |= MOD_MASK_2CLICK;
+ } else if (orig_num_clicks == 3) {
+ modifiers |= MOD_MASK_3CLICK;
+ } else if (orig_num_clicks == 4) {
+ modifiers |= MOD_MASK_4CLICK;
+ }
+
+ if (modifiers) {
+ if (buf[1] != KS_MODIFIER) {
+ // no modifiers in the buffer yet, shift the bytes 3 positions
+ memcpy(buf + 3, buf, 3);
+ // add the modifier sequence
+ buf[0] = K_SPECIAL;
+ buf[1] = KS_MODIFIER;
+ buf[2] = (uint8_t)modifiers;
+ bufsize += 3;
+ } else {
+ buf[2] |= (uint8_t)modifiers;
+ }
+ }
+
+ return bufsize;
+}
+
static bool input_poll(int ms)
{
if (do_profiling == PROF_YES && ms) {
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/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/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(&current_state);
+# define UNREF_STATEITEM_EXTMATCH(si) unref_extmatch((si)->si_extmatch)
+ GA_DEEP_CLEAR(&current_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
index 63b9156442..061e0cfd2f 100644
--- a/src/nvim/testdir/test_eval.ok
+++ b/src/nvim/testdir/test_eval.ok
Binary files differ
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,
diff --git a/test/functional/ui/mouse_spec.lua b/test/functional/ui/mouse_spec.lua
new file mode 100644
index 0000000000..507b5aacae
--- /dev/null
+++ b/test/functional/ui/mouse_spec.lua
@@ -0,0 +1,157 @@
+local helpers = require('test.functional.helpers')
+local Screen = require('test.functional.ui.screen')
+local clear, feed, nvim = helpers.clear, helpers.feed, helpers.nvim
+
+describe('Mouse input', function()
+ local screen, hlgroup_colors
+
+ setup(function()
+ hlgroup_colors = {
+ Visual = nvim('name_to_color', 'LightGrey'),
+ }
+ end)
+
+ before_each(function()
+ clear()
+ nvim('set_option', 'mouse', 'a')
+ -- set mouset to very high value to ensure that even in valgrind/travis,
+ -- nvim will still pick multiple clicks
+ nvim('set_option', 'mouset', 5000)
+ screen = Screen.new(25, 5)
+ screen:attach()
+ screen:set_default_attr_ids({
+ [1] = {background = hlgroup_colors.Visual}
+ })
+ feed('itesting<cr>mouse<cr>support and selection<esc>')
+ screen:expect([[
+ testing |
+ mouse |
+ support and selectio^ |
+ ~ |
+ |
+ ]])
+ end)
+
+ after_each(function()
+ screen:detach()
+ end)
+
+ it('left click moves cursor', function()
+ feed('<LeftMouse><2,1>')
+ screen:expect([[
+ testing |
+ mo^se |
+ support and selection |
+ ~ |
+ |
+ ]])
+ feed('<LeftMouse><0,0>')
+ screen:expect([[
+ ^esting |
+ mouse |
+ support and selection |
+ ~ |
+ |
+ ]])
+ end)
+
+ it('left drag changes visual selection', function()
+ -- drag events must be preceded by a click
+ feed('<LeftMouse><2,1>')
+ screen:expect([[
+ testing |
+ mo^se |
+ support and selection |
+ ~ |
+ |
+ ]])
+ feed('<LeftDrag><4,1>')
+ screen:expect([[
+ testing |
+ mo{1:us}^ |
+ support and selection |
+ ~ |
+ -- VISUAL -- |
+ ]])
+ feed('<LeftDrag><2,2>')
+ screen:expect([[
+ testing |
+ mo{1:use } |
+ {1:su}^port and selection |
+ ~ |
+ -- VISUAL -- |
+ ]])
+ feed('<LeftDrag><0,0>')
+ screen:expect([[
+ ^{1:esting } |
+ {1:mou}se |
+ support and selection |
+ ~ |
+ -- VISUAL -- |
+ ]])
+ end)
+
+ it('two clicks will select the word and enter VISUAL', function()
+ feed('<LeftMouse><2,2><LeftMouse><2,2>')
+ screen:expect([[
+ testing |
+ mouse |
+ {1:suppor}^ and selection |
+ ~ |
+ -- VISUAL -- |
+ ]])
+ end)
+
+ it('three clicks will select the line and enter VISUAL LINE', function()
+ feed('<LeftMouse><2,2><LeftMouse><2,2><LeftMouse><2,2>')
+ screen:expect([[
+ testing |
+ mouse |
+ {1:su}^{1:port and selection } |
+ ~ |
+ -- VISUAL LINE -- |
+ ]])
+ end)
+
+ it('four clicks will enter VISUAL BLOCK', function()
+ feed('<LeftMouse><2,2><LeftMouse><2,2><LeftMouse><2,2><LeftMouse><2,2>')
+ screen:expect([[
+ testing |
+ mouse |
+ su^port and selection |
+ ~ |
+ -- VISUAL BLOCK -- |
+ ]])
+ end)
+
+ it('right click extends visual selection to the clicked location', function()
+ feed('<LeftMouse><0,0>')
+ screen:expect([[
+ ^esting |
+ mouse |
+ support and selection |
+ ~ |
+ |
+ ]])
+ feed('<RightMouse><2,2>')
+ screen:expect([[
+ {1:testing } |
+ {1:mouse } |
+ {1:su}^port and selection |
+ ~ |
+ -- VISUAL -- |
+ ]])
+ end)
+
+ it('ctrl + left click will search for a tag', function()
+ feed('<C-LeftMouse><0,0>')
+ screen:expect([[
+ E433: No tags file |
+ E426: tag not found: test|
+ ing |
+ Press ENTER or type comma|
+ nd to continue^ |
+ ]])
+ feed('<cr>')
+ end)
+end)
diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua
index ff22321e4e..8e7d1ed798 100644
--- a/test/functional/ui/screen.lua
+++ b/test/functional/ui/screen.lua
@@ -147,17 +147,21 @@ function Screen:expect(expected, attr_ids)
end
function Screen:_wait(check, timeout)
- local err
+ local err, checked = false
local function notification_cb(method, args)
assert(method == 'redraw')
self:_redraw(args)
err = check()
+ checked = true
if not err then
stop()
end
return true
end
run(nil, notification_cb, nil, timeout or 5000)
+ if not checked then
+ err = check()
+ end
if err then
error(err)
end