aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.ci/clang-asan.sh13
-rw-r--r--.ci/common.sh78
-rw-r--r--.ci/gcc-32.sh (renamed from .ci/gcc-ia32.sh)7
-rw-r--r--.ci/gcc-unittest.sh11
-rw-r--r--.ci/gcc.sh20
-rw-r--r--.travis.yml4
-rw-r--r--CMakeLists.txt22
-rw-r--r--Makefile6
-rw-r--r--cmake/RunTests.cmake23
-rw-r--r--cmake/RunUnittests.cmake14
-rw-r--r--scripts/run-functional-tests.py93
-rw-r--r--src/nvim/api/buffer.c14
-rw-r--r--src/nvim/buffer.c2
-rw-r--r--src/nvim/eval.c249
-rw-r--r--src/nvim/ex_cmds2.c4
-rw-r--r--src/nvim/fileio.c8
-rw-r--r--src/nvim/globals.h8
-rw-r--r--src/nvim/memory.c61
-rw-r--r--src/nvim/message.c3
-rw-r--r--src/nvim/misc1.c9
-rw-r--r--src/nvim/ops.c7
-rw-r--r--src/nvim/os/fs.c5
-rw-r--r--src/nvim/os/shell.c19
-rw-r--r--src/nvim/os/wstream.c5
-rw-r--r--src/nvim/os/wstream_defs.h2
-rw-r--r--src/nvim/path.c2
-rw-r--r--src/nvim/screen.c80
-rw-r--r--src/nvim/testdir/Makefile1
-rw-r--r--src/nvim/testdir/test_changelist.in22
-rw-r--r--src/nvim/testdir/test_changelist.ok1
-rw-r--r--src/nvim/version.c27
-rw-r--r--src/nvim/window.c20
-rw-r--r--test/functional/helpers.lua102
-rw-r--r--test/functional/legacy/002_filename_recognition_spec.lua42
-rw-r--r--test/functional/shell/viml_system_spec.lua125
-rw-r--r--test/legacy/test2-filename-recognition.vroom40
36 files changed, 864 insertions, 285 deletions
diff --git a/.ci/clang-asan.sh b/.ci/clang-asan.sh
index 4b5ff26d0d..0dff61f297 100644
--- a/.ci/clang-asan.sh
+++ b/.ci/clang-asan.sh
@@ -1,9 +1,9 @@
. "$CI_SCRIPTS/common.sh"
-install_vroom
-
set_environment /opt/neovim-deps/64
+install_functional_test_deps
+
sudo pip install cpp-coveralls
clang_version=3.4
@@ -26,14 +26,19 @@ export UBSAN_OPTIONS="log_path=$tmpdir/ubsan" # not sure if this works
install_dir="$(pwd)/dist"
$MAKE_CMD cmake CMAKE_EXTRA_FLAGS="-DTRAVIS_CI_BUILD=ON -DCMAKE_INSTALL_PREFIX=$install_dir -DUSE_GCOV=ON"
-$MAKE_CMD
if ! $MAKE_CMD test; then
- reset
asan_check "$tmpdir"
exit 1
fi
+asan_check "$tmpdir"
+if ! $MAKE_CMD oldtest; then
+ reset
+ asan_check "$tmpdir"
+ exit 1
+fi
asan_check "$tmpdir"
+
coveralls --encoding iso-8859-1 || echo 'coveralls upload failed.'
$MAKE_CMD install
diff --git a/.ci/common.sh b/.ci/common.sh
index ba00011222..7939f2f4b7 100644
--- a/.ci/common.sh
+++ b/.ci/common.sh
@@ -1,50 +1,32 @@
valgrind_check() {
- (
- cd $1
- set -- valgrind-[*] valgrind-*
- case $1$2 in
- 'valgrind-[*]valgrind-*')
- ;;
- *)
- shift
- local err=''
- for valgrind_log in "$@"; do
- # Remove useless warning
- sed -i "$valgrind_log" \
- -e '/Warning: noted but unhandled ioctl/d' \
- -e '/could cause spurious value errors to appear/d' \
- -e '/See README_MISSING_SYSCALL_OR_IOCTL for guidance/d'
- if [ "$(stat -c %s $valgrind_log)" != "0" ]; then
- # if after removing the warning, the log still has errors, show its
- # contents and set the flag so we exit with non-zero status
- cat "$valgrind_log"
- err=1
- fi
- done
- if [ -n "$err" ]; then
- echo "Runtime errors detected"
- exit 1
- fi
- ;;
- esac
- )
+ check_logs "$1" "valgrind-*"
}
asan_check() {
- (
- cd $1
- set -- [*]san.[*] *san.*
- case $1$2 in
- '[*]san.[*]*san.*')
- ;;
- *)
- shift
- cat "$@"
- echo "Runtime errors detected"
- exit 1
- ;;
- esac
- )
+ check_logs "$1" "*san.*"
+}
+
+check_logs() {
+ # For some strange reason, now we need to give ubuntu some time to flush it's
+ # FS cache in order to see error logs, even though all commands are executing
+ # synchronously
+ sleep 1
+ # Iterate through each log to remove an useless warning
+ for log in $(find "$1" -type f -name "$2"); do
+ sed -i "$log" \
+ -e '/Warning: noted but unhandled ioctl/d' \
+ -e '/could cause spurious value errors to appear/d' \
+ -e '/See README_MISSING_SYSCALL_OR_IOCTL for guidance/d'
+ done
+ # Now do it again, but only consider files with size > 0
+ for log in $(find "$1" -type f -name "$2" -size +0); do
+ cat "$log"
+ err=1
+ done
+ if [ -n "$err" ]; then
+ echo "Runtime errors detected"
+ exit 1
+ fi
}
set_environment() {
@@ -65,14 +47,10 @@ install_prebuilt_deps() {
fi
}
-install_vroom() {
- (
+install_functional_test_deps() {
sudo pip install git+https://github.com/neovim/python-client.git
- git clone git://github.com/google/vroom
- cd vroom
- python setup.py build
- sudo python setup.py install
- )
+ # Pass -E to let pip use PKG_CONFIG_PATH for luajit
+ sudo -E pip install lupa
}
tmpdir="$(pwd)/tmp"
diff --git a/.ci/gcc-ia32.sh b/.ci/gcc-32.sh
index 97e4190ccc..ec51cfab69 100644
--- a/.ci/gcc-ia32.sh
+++ b/.ci/gcc-32.sh
@@ -1,6 +1,9 @@
. "$CI_SCRIPTS/common.sh"
-install_vroom
+# To install lupa, a temporarary functional test dependency, we require the
+# 64-bit luajit since travis version of python is 64-bit.
+export PKG_CONFIG_PATH="/opt/neovim-deps/64/usr/lib/pkgconfig"
+install_functional_test_deps
set_environment /opt/neovim-deps/32
@@ -22,5 +25,7 @@ CMAKE_EXTRA_FLAGS="-DTRAVIS_CI_BUILD=ON \
-DFIND_LIBRARY_USE_LIB64_PATHS=OFF \
-DCMAKE_IGNORE_PATH=/lib:/usr/lib:/usr/local/lib \
-DCMAKE_TOOLCHAIN_FILE=cmake/i386-linux-gnu.toolchain.cmake"
+
$MAKE_CMD CMAKE_EXTRA_FLAGS="${CMAKE_EXTRA_FLAGS}" unittest
$MAKE_CMD test
+$MAKE_CMD oldtest
diff --git a/.ci/gcc-unittest.sh b/.ci/gcc-unittest.sh
deleted file mode 100644
index 95925ddbc9..0000000000
--- a/.ci/gcc-unittest.sh
+++ /dev/null
@@ -1,11 +0,0 @@
-. "$CI_SCRIPTS/common.sh"
-
-set_environment /opt/neovim-deps/64
-
-sudo pip install cpp-coveralls
-
-export CC=gcc
-export SKIP_EXEC=1
-$MAKE_CMD CMAKE_EXTRA_FLAGS="-DTRAVIS_CI_BUILD=ON -DUSE_GCOV=ON" unittest
-
-coveralls --encoding iso-8859-1 || echo 'coveralls upload failed.'
diff --git a/.ci/gcc.sh b/.ci/gcc.sh
new file mode 100644
index 0000000000..842e34405a
--- /dev/null
+++ b/.ci/gcc.sh
@@ -0,0 +1,20 @@
+. "$CI_SCRIPTS/common.sh"
+
+set_environment /opt/neovim-deps/64
+
+install_functional_test_deps
+
+sudo pip install cpp-coveralls
+
+sudo apt-get install valgrind
+
+export VALGRIND=1
+export VALGRIND_LOG="$tmpdir/valgrind-%p.log"
+CMAKE_EXTRA_FLAGS="-DTRAVIS_CI_BUILD=ON -DUSE_GCOV=ON"
+
+$MAKE_CMD CMAKE_EXTRA_FLAGS="${CMAKE_EXTRA_FLAGS}" unittest
+$MAKE_CMD test
+valgrind_check "$tmpdir"
+$MAKE_CMD oldtest
+
+coveralls --encoding iso-8859-1 || echo 'coveralls upload failed.'
diff --git a/.travis.yml b/.travis.yml
index 1965fb72b8..8c3b986aa4 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -8,8 +8,8 @@ env:
- secure: "QEz92NyItkzQu52kCFD928jEwUYnA2OIgSyeNrp+Y3gm5rOmSZerY8hGiXyNZxocap9+qIPCapRRYU3ZYKWZPeucWMLN3aIjxAFdhugKbnmNYE1jFugb6b8N3SxiX/3206NHXlYaz0OZhh6OBAFmPUXamJC8OrWVgPNPo7wv4UQ="
matrix:
- CI_TARGET=clang-asan
- - CI_TARGET=gcc-ia32
- - CI_TARGET=gcc-unittest
+ - CI_TARGET=gcc
+ - CI_TARGET=gcc-32
- CI_TARGET=clint
- CI_TARGET=api-python
- CI_TARGET=coverity
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3622dabb23..435a215e51 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -211,6 +211,26 @@ if(BUSTED_PRG)
-DBUSTED_OUTPUT_TYPE=${BUSTED_OUTPUT_TYPE}
-DTEST_DIR=${CMAKE_CURRENT_SOURCE_DIR}/test
-DBUILD_DIR=${CMAKE_BINARY_DIR}
- -P ${CMAKE_MODULE_PATH}/RunUnittests.cmake
+ -DTEST_TYPE=unit
+ -P ${CMAKE_MODULE_PATH}/RunTests.cmake
DEPENDS nvim-test unittest-headers)
+
+ # For the functional tests we need the full path to the real busted script,
+ # which will be included by run-functional-tests.py.
+ get_filename_component(LUA_PRG_DIR ${LUA_PRG} PATH)
+ get_filename_component(LUA_PREFIX_DIR ${LUA_PRG_DIR} PATH)
+ file(GLOB_RECURSE BUSTED_REAL_PRG
+ ${LUA_PREFIX_DIR}/lib/luarocks/rocks/busted/*busted)
+
+ add_custom_target(test
+ COMMAND ${CMAKE_COMMAND}
+ -DBUSTED_PRG=${PROJECT_SOURCE_DIR}/scripts/run-functional-tests.py
+ -DBUSTED_REAL_PRG=${BUSTED_REAL_PRG}
+ -DWORKING_DIR=${CMAKE_CURRENT_SOURCE_DIR}
+ -DBUSTED_OUTPUT_TYPE=${BUSTED_OUTPUT_TYPE}
+ -DTEST_DIR=${CMAKE_CURRENT_SOURCE_DIR}/test
+ -DBUILD_DIR=${CMAKE_BINARY_DIR}
+ -DTEST_TYPE=functional
+ -P ${CMAKE_MODULE_PATH}/RunTests.cmake
+ DEPENDS nvim)
endif()
diff --git a/Makefile b/Makefile
index 54708c7c1a..a4604cc67f 100644
--- a/Makefile
+++ b/Makefile
@@ -74,9 +74,11 @@ endif
mkdir -p build
touch $@
-test: | nvim
+oldtest: | nvim
+$(SINGLE_MAKE) -C src/nvim/testdir $(MAKEOVERRIDES)
- PATH="$$(pwd)/build/bin:$$PATH" vroom --neovim --crawl test
+
+test: | nvim
+ +$(BUILD_CMD) -C build test
unittest: | nvim
+$(BUILD_CMD) -C build unittest
diff --git a/cmake/RunTests.cmake b/cmake/RunTests.cmake
new file mode 100644
index 0000000000..b89957bb28
--- /dev/null
+++ b/cmake/RunTests.cmake
@@ -0,0 +1,23 @@
+get_filename_component(BUSTED_DIR ${BUSTED_PRG} PATH)
+set(ENV{PATH} "${BUSTED_DIR}:$ENV{PATH}")
+if(DEFINED ENV{TEST_FILE})
+ set(TEST_DIR $ENV{TEST_FILE})
+endif()
+
+if(TEST_TYPE STREQUAL "functional")
+ execute_process(
+ COMMAND python ${BUSTED_PRG} ${BUSTED_REAL_PRG} -v -o
+ ${BUSTED_OUTPUT_TYPE} --lpath=${BUILD_DIR}/?.lua ${TEST_DIR}/functional
+ WORKING_DIRECTORY ${WORKING_DIR}
+ RESULT_VARIABLE res)
+else()
+ execute_process(
+ COMMAND ${BUSTED_PRG} -v -o ${BUSTED_OUTPUT_TYPE}
+ --lpath=${BUILD_DIR}/?.lua ${TEST_DIR}/unit
+ WORKING_DIRECTORY ${WORKING_DIR}
+ RESULT_VARIABLE res)
+endif()
+
+if(NOT res EQUAL 0)
+ message(FATAL_ERROR "Unit tests failed.")
+endif()
diff --git a/cmake/RunUnittests.cmake b/cmake/RunUnittests.cmake
deleted file mode 100644
index f51fc8a90a..0000000000
--- a/cmake/RunUnittests.cmake
+++ /dev/null
@@ -1,14 +0,0 @@
-get_filename_component(BUSTED_DIR ${BUSTED_PRG} PATH)
-set(ENV{PATH} "${BUSTED_DIR}:$ENV{PATH}")
-if(DEFINED ENV{TEST_FILE})
- set(TEST_DIR $ENV{TEST_FILE})
-endif()
-
-execute_process(
- COMMAND ${BUSTED_PRG} -v -o ${BUSTED_OUTPUT_TYPE} --lpath=${BUILD_DIR}/?.lua ${TEST_DIR}
- WORKING_DIRECTORY ${WORKING_DIR}
- RESULT_VARIABLE res)
-
-if(NOT res EQUAL 0)
- message(FATAL_ERROR "Unit tests failed.")
-endif()
diff --git a/scripts/run-functional-tests.py b/scripts/run-functional-tests.py
new file mode 100644
index 0000000000..3e931b248c
--- /dev/null
+++ b/scripts/run-functional-tests.py
@@ -0,0 +1,93 @@
+# Run functional tests using lua, busted and the python client
+
+import os
+import sys
+import textwrap
+
+from lupa import LuaRuntime, as_attrgetter
+from neovim import Nvim, spawn_session
+
+
+# Extract arguments
+busted_script = sys.argv[1]
+busted_argv = sys.argv[2:]
+
+# Setup a lua state for running busted
+lua = LuaRuntime(unpack_returned_tuples=True)
+lua_globals = lua.globals()
+
+# helper to transform iterables into lua tables
+list_to_table = lua.eval('''
+function(l)
+ local t = {}
+ for i, item in python.enumerate(l) do t[i + 1] = item end
+ return t
+end
+''')
+
+dict_to_table = lua.eval('''
+function(d)
+ local t = {}
+ for k, v in python.iterex(d.items()) do t[k] = v end
+ return t
+end
+''')
+
+def to_table(obj):
+ if type(obj) in [tuple, list]:
+ return list_to_table(list(to_table(e) for e in obj))
+ if type(obj) is dict:
+ return dict_to_table(as_attrgetter(
+ dict((k, to_table(v)) for k, v in obj.items())))
+ return obj
+
+nvim_prog = os.environ.get('NVIM_PROG', 'build/bin/nvim')
+nvim_argv = [nvim_prog, '-u', 'NONE', '--embed']
+
+if 'VALGRIND' in os.environ:
+ log_file = os.environ.get('VALGRIND_LOG', 'valgrind-%p.log')
+ valgrind_argv = ['valgrind', '-q', '--tool=memcheck', '--leak-check=yes',
+ '--track-origins=yes', '--suppressions=.valgrind.supp',
+ '--log-file={0}'.format(log_file)]
+ if 'VALGRIND_GDB' in os.environ:
+ valgrind_argv += ['--vgdb=yes', '--vgdb-error=0']
+ nvim_argv = valgrind_argv + nvim_argv
+
+session = spawn_session(nvim_argv)
+nvim = Nvim.from_session(session)
+
+def nvim_command(cmd):
+ nvim.command(cmd)
+
+def nvim_eval(expr):
+ return to_table(nvim.eval(expr))
+
+def nvim_feed(input, mode=''):
+ nvim.feedkeys(input)
+
+def buffer_slice(start=None, stop=None, buffer_idx=None):
+ rv = '\n'.join(nvim.buffers[buffer_idx or 0][start:stop])
+ return rv
+
+def nvim_replace_termcodes(input, *opts):
+ return nvim.replace_termcodes(input, *opts)
+
+expose = [
+ nvim_command,
+ nvim_eval,
+ nvim_feed,
+ nvim_replace_termcodes,
+ buffer_slice,
+ textwrap.dedent,
+]
+
+for fn in expose:
+ lua_globals[fn.__name__] = fn
+
+# Set 'arg' global to let busted parse arguments
+lua_globals['arg'] = list_to_table(busted_argv)
+
+# Read the busted script and execute in the lua state
+with open(busted_script) as f:
+ busted_setup = f.read()
+lua.execute(busted_setup)
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 62559163d8..4ff5845bd4 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -31,7 +31,7 @@
/// @param buffer The buffer handle
/// @param[out] err Details of an error that may have occurred
/// @return The line count
-Integer buffer_get_length(Buffer buffer, Error *err)
+Integer buffer_line_count(Buffer buffer, Error *err)
{
buf_T *buf = find_buffer_by_handle(buffer, err);
@@ -51,7 +51,7 @@ Integer buffer_get_length(Buffer buffer, Error *err)
String buffer_get_line(Buffer buffer, Integer index, Error *err)
{
String rv = {.size = 0};
- Array slice = buffer_get_slice(buffer, index, index, true, true, err);
+ Array slice = buffer_get_line_slice(buffer, index, index, true, true, err);
if (!err->set && slice.size) {
rv = slice.items[0].data.string;
@@ -72,7 +72,7 @@ void buffer_set_line(Buffer buffer, Integer index, String line, Error *err)
{
Object l = STRING_OBJ(line);
Array array = {.items = &l, .size = 1};
- buffer_set_slice(buffer, index, index, true, true, array, err);
+ buffer_set_line_slice(buffer, index, index, true, true, array, err);
}
/// Deletes a buffer line
@@ -83,7 +83,7 @@ void buffer_set_line(Buffer buffer, Integer index, String line, Error *err)
void buffer_del_line(Buffer buffer, Integer index, Error *err)
{
Array array = ARRAY_DICT_INIT;
- buffer_set_slice(buffer, index, index, true, true, array, err);
+ buffer_set_line_slice(buffer, index, index, true, true, array, err);
}
/// Retrieves a line range from the buffer
@@ -95,7 +95,7 @@ void buffer_del_line(Buffer buffer, Integer index, Error *err)
/// @param include_end True if the slice includes the `end` parameter
/// @param[out] err Details of an error that may have occurred
/// @return An array of lines
-ArrayOf(String) buffer_get_slice(Buffer buffer,
+ArrayOf(String) buffer_get_line_slice(Buffer buffer,
Integer start,
Integer end,
Boolean include_start,
@@ -156,7 +156,7 @@ end:
/// @param replacement An array of lines to use as replacement(A 0-length
// array will simply delete the line range)
/// @param[out] err Details of an error that may have occurred
-void buffer_set_slice(Buffer buffer,
+void buffer_set_line_slice(Buffer buffer,
Integer start,
Integer end,
Boolean include_start,
@@ -445,7 +445,7 @@ void buffer_insert(Buffer buffer,
ArrayOf(String) lines,
Error *err)
{
- buffer_set_slice(buffer, lnum, lnum, false, true, lines, err);
+ buffer_set_line_slice(buffer, lnum, lnum, false, true, lines, err);
}
/// Return a tuple (row,col) representing the position of the named mark
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index b3bcf54258..6ce3c77c66 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -318,7 +318,7 @@ close_buffer (
} else if (buf->b_p_bh[0] == 'u') /* 'bufhidden' == "unload" */
unload_buf = true;
- if (win != NULL) {
+ if (win_valid(win)) {
/* Set b_last_cursor when closing the last window for the buffer.
* Remember the last cursor position and window options of the buffer.
* This used to be only for the current window, but then options like
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 3d0607d8f7..8b02a4187a 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -4686,12 +4686,13 @@ list_T *list_alloc(void) FUNC_ATTR_NONNULL_RET
/*
* Allocate an empty list for a return value.
*/
-static void rettv_list_alloc(typval_T *rettv)
+static list_T *rettv_list_alloc(typval_T *rettv)
{
list_T *l = list_alloc();
rettv->vval.v_list = l;
rettv->v_type = VAR_LIST;
++l->lv_refcount;
+ return l;
}
/*
@@ -6543,6 +6544,7 @@ static struct fst {
{"synconcealed", 2, 2, f_synconcealed},
{"synstack", 2, 2, f_synstack},
{"system", 1, 2, f_system},
+ {"systemlist", 1, 2, f_systemlist},
{"tabpagebuflist", 0, 1, f_tabpagebuflist},
{"tabpagenr", 0, 1, f_tabpagenr},
{"tabpagewinnr", 1, 2, f_tabpagewinnr},
@@ -9407,16 +9409,15 @@ static void f_getpid(typval_T *argvars, typval_T *rettv)
static void getpos_both(typval_T *argvars, typval_T *rettv, bool getcurpos)
{
pos_T *fp;
- list_T *l;
int fnum = -1;
- rettv_list_alloc(rettv);
- l = rettv->vval.v_list;
if (getcurpos) {
fp = &curwin->w_cursor;
} else {
fp = var2fpos(&argvars[0], true, &fnum);
}
+
+ list_T *l = rettv_list_alloc(rettv);
list_append_number(l, (fnum != -1) ? (varnumber_T)fnum : (varnumber_T)0);
list_append_number(l, (fp != NULL) ? (varnumber_T)fp->lnum : (varnumber_T)0);
list_append_number(l,
@@ -9873,7 +9874,7 @@ static void f_has(typval_T *argvars, typval_T *rettv)
"spell",
"syntax",
#if !defined(UNIX)
- "system",
+ "system", // TODO(SplinterOfChaos): This IS defined for UNIX!
#endif
"tag_binary",
"tag_old_static",
@@ -12009,8 +12010,7 @@ static void f_remove(typval_T *argvars, typval_T *rettv)
EMSG(_(e_invrange));
else {
vim_list_remove(l, item, item2);
- rettv_list_alloc(rettv);
- l = rettv->vval.v_list;
+ l = rettv_list_alloc(rettv);
l->lv_first = item;
l->lv_last = item2;
item->li_prev = NULL;
@@ -13461,15 +13461,36 @@ static int item_compare(const void *s1, const void *s2)
{
sortItem_T *si1, *si2;
char_u *p1, *p2;
- char_u *tofree1, *tofree2;
+ char_u *tofree1 = NULL, *tofree2 = NULL;
int res;
char_u numbuf1[NUMBUFLEN];
char_u numbuf2[NUMBUFLEN];
si1 = (sortItem_T *)s1;
si2 = (sortItem_T *)s2;
- p1 = tv2string(&si1->item->li_tv, &tofree1, numbuf1, 0);
- p2 = tv2string(&si2->item->li_tv, &tofree2, numbuf2, 0);
+ typval_T *tv1 = &si1->item->li_tv;
+ typval_T *tv2 = &si2->item->li_tv;
+ // tv2string() puts quotes around a string and allocates memory. Don't do
+ // that for string variables. Use a single quote when comparing with a
+ // non-string to do what the docs promise.
+ if (tv1->v_type == VAR_STRING) {
+ if (tv2->v_type != VAR_STRING || item_compare_numeric) {
+ p1 = (char_u *)"'";
+ } else {
+ p1 = tv1->vval.v_string;
+ }
+ } else {
+ p1 = tv2string(tv1, &tofree1, numbuf1, 0);
+ }
+ if (tv2->v_type == VAR_STRING) {
+ if (tv1->v_type != VAR_STRING || item_compare_numeric) {
+ p2 = (char_u *)"'";
+ } else {
+ p2 = tv2->vval.v_string;
+ }
+ } else {
+ p2 = tv2string(tv2, &tofree2, numbuf2, 0);
+ }
if (p1 == NULL)
p1 = (char_u *)"";
if (p2 == NULL)
@@ -13487,8 +13508,8 @@ static int item_compare(const void *s1, const void *s2)
res = n1 == n2 ? 0 : n1 > n2 ? 1 : -1;
}
- // When the result would be zero, compare the pointers themselves. Makes
- // the sort stable.
+ // When the result would be zero, compare the item indexes. Makes the
+ // sort stable.
if (res == 0 && !item_compare_keep_zero) {
res = si1->idx > si2->idx ? 1 : -1;
}
@@ -14394,8 +14415,8 @@ static void f_synstack(typval_T *argvars, typval_T *rettv)
}
}
-/// f_system - the VimL system() function
-static void f_system(typval_T *argvars, typval_T *rettv)
+static void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv,
+ bool retlist)
{
rettv->v_type = VAR_STRING;
rettv->vval.v_string = NULL;
@@ -14405,10 +14426,11 @@ static void f_system(typval_T *argvars, typval_T *rettv)
}
// get input to the shell command (if any), and its length
- char_u buf[NUMBUFLEN];
- const char *input = (argvars[1].v_type != VAR_UNKNOWN)
- ? (char *) get_tv_string_buf_chk(&argvars[1], buf): NULL;
- size_t input_len = input ? strlen(input) : 0;
+ ssize_t input_len;
+ char *input = (char *) save_tv_as_string(&argvars[1], &input_len);
+ if (input_len == -1) {
+ return;
+ }
// get shell command to execute
const char *cmd = (char *) get_tv_string(&argvars[0]);
@@ -14418,11 +14440,40 @@ static void f_system(typval_T *argvars, typval_T *rettv)
char *res = NULL;
int status = os_system(cmd, input, input_len, &res, &nread);
+ free(input);
+
set_vim_var_nr(VV_SHELL_ERROR, (long) status);
+ if (res == NULL) {
+ return;
+ }
+
+ if (retlist) {
+ list_T *list = rettv_list_alloc(rettv);
+
+ // Copy each line to a list element using NL as the delimiter.
+ for (size_t i = 0; i < nread; i++) {
+ char_u *start = (char_u *) res + i;
+ size_t len = (char_u *) xmemscan(start, NL, nread - i) - start;
+ i += len;
+
+ // Don't use a str function to copy res as it may contains NULs.
+ char_u *s = xmemdupz(start, len);
+ memchrsub(s, NUL, NL, len); // Replace NUL with NL to avoid truncation.
+
+ listitem_T *li = listitem_alloc();
+ li->li_tv.v_type = VAR_STRING;
+ li->li_tv.vval.v_string = s;
+ list_append(list, li);
+ }
+
+ free(res);
+ } else {
+ // res may contain several NULs before the final terminating one.
+ // Replace them with SOH (1) like in get_cmd_output() to avoid truncation.
+ memchrsub(res, NUL, 1, nread);
#ifdef USE_CRNL
- // translate <CR><NL> into <NL>
- if (res != NULL) {
+ // translate <CR><NL> into <NL>
char *d = res;
for (char *s = res; *s; ++s) {
if (s[0] == CAR && s[1] == NL) {
@@ -14433,12 +14484,23 @@ static void f_system(typval_T *argvars, typval_T *rettv)
}
*d = NUL;
- }
#endif
+ rettv->vval.v_string = (char_u *) res;
+ }
+}
- rettv->vval.v_string = (char_u *) res;
+/// f_system - the VimL system() function
+static void f_system(typval_T *argvars, typval_T *rettv)
+{
+ get_system_output_as_rettv(argvars, rettv, false);
+}
+
+static void f_systemlist(typval_T *argvars, typval_T *rettv)
+{
+ get_system_output_as_rettv(argvars, rettv, true);
}
+
/*
* "tabpagebuflist()" function
*/
@@ -14579,8 +14641,7 @@ static void f_taglist(typval_T *argvars, typval_T *rettv)
if (*tag_pattern == NUL)
return;
- rettv_list_alloc(rettv);
- (void)get_tags(rettv->vval.v_list, tag_pattern);
+ (void)get_tags(rettv_list_alloc(rettv), tag_pattern);
}
/*
@@ -15037,6 +15098,87 @@ static void f_winsaveview(typval_T *argvars, typval_T *rettv)
dict_add_nr_str(dict, "skipcol", (long)curwin->w_skipcol, NULL);
}
+/// Writes list of strings to file
+static bool write_list(FILE *fd, list_T *list, bool binary)
+{
+ int ret = true;
+
+ for (listitem_T *li = list->lv_first; li != NULL; li = li->li_next) {
+ for (char_u *s = get_tv_string(&li->li_tv); *s != NUL; ++s) {
+ if (putc(*s == '\n' ? NUL : *s, fd) == EOF) {
+ ret = false;
+ break;
+ }
+ }
+ if (!binary || li->li_next != NULL) {
+ if (putc('\n', fd) == EOF) {
+ ret = false;
+ break;
+ }
+ }
+ if (ret == false) {
+ EMSG(_(e_write));
+ break;
+ }
+ }
+ return ret;
+}
+
+/// Saves a typval_T as a string.
+///
+/// For lists, replaces NLs with NUL and separates items with NLs.
+///
+/// @param[in] tv A value to store as a string.
+/// @param[out] len The length of the resulting string or -1 on error.
+/// @returns an allocated string if `tv` represents a VimL string, list, or
+/// number; NULL otherwise.
+static char_u *save_tv_as_string(typval_T *tv, ssize_t *len)
+ FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL
+{
+ if (tv->v_type == VAR_UNKNOWN) {
+ *len = 0;
+ return NULL;
+ }
+
+ // For types other than list, let get_tv_string_buf_chk() get the value or
+ // print an error.
+ if (tv->v_type != VAR_LIST) {
+ char_u *ret = get_tv_string_chk(tv);
+ if (ret && (*len = STRLEN(ret))) {
+ ret = vim_strsave(ret);
+ } else {
+ ret = NULL;
+ *len = -1;
+ }
+ return ret;
+ }
+
+ // Pre-calculate the resulting length.
+ *len = 0;
+ list_T *list = tv->vval.v_list;
+ for (listitem_T *li = list->lv_first; li != NULL; li = li->li_next) {
+ *len += STRLEN(get_tv_string(&li->li_tv)) + 1;
+ }
+
+ if (*len == 0) {
+ return NULL;
+ }
+
+ char_u *ret = xmalloc(*len);
+ char_u *end = ret;
+ for (listitem_T *li = list->lv_first; li != NULL; li = li->li_next) {
+ for (char_u *s = get_tv_string(&li->li_tv); *s != NUL; s++) {
+ *end++ = (*s == '\n') ? NUL : *s;
+ }
+ if (li->li_next != NULL) {
+ *end++ = '\n';
+ }
+ }
+ *end = NUL;
+ *len = end - ret;
+ return ret;
+}
+
/*
* "winwidth(nr)" function
*/
@@ -15051,68 +15193,43 @@ static void f_winwidth(typval_T *argvars, typval_T *rettv)
rettv->vval.v_number = wp->w_width;
}
-/*
- * "writefile()" function
- */
+/// "writefile()" function
static void f_writefile(typval_T *argvars, typval_T *rettv)
{
- int binary = FALSE;
- char_u *fname;
- FILE *fd;
- listitem_T *li;
- char_u *s;
- int ret = 0;
- int c;
+ rettv->vval.v_number = 0; // Assuming success.
- if (check_restricted() || check_secure())
+ if (check_restricted() || check_secure()) {
return;
+ }
if (argvars[0].v_type != VAR_LIST) {
EMSG2(_(e_listarg), "writefile()");
return;
}
- if (argvars[0].vval.v_list == NULL)
+ if (argvars[0].vval.v_list == NULL) {
return;
+ }
+ bool binary = false;
if (argvars[2].v_type != VAR_UNKNOWN
- && STRCMP(get_tv_string(&argvars[2]), "b") == 0)
- binary = TRUE;
+ && STRCMP(get_tv_string(&argvars[2]), "b") == 0) {
+ binary = true;
+ }
- /* Always open the file in binary mode, library functions have a mind of
- * their own about CR-LF conversion. */
- fname = get_tv_string(&argvars[1]);
+ // Always open the file in binary mode, library functions have a mind of
+ // their own about CR-LF conversion.
+ char_u *fname = get_tv_string(&argvars[1]);
+ FILE *fd;
if (*fname == NUL || (fd = mch_fopen((char *)fname, WRITEBIN)) == NULL) {
EMSG2(_(e_notcreate), *fname == NUL ? (char_u *)_("<empty>") : fname);
- ret = -1;
+ rettv->vval.v_number = -1;
} else {
- for (li = argvars[0].vval.v_list->lv_first; li != NULL;
- li = li->li_next) {
- for (s = get_tv_string(&li->li_tv); *s != NUL; ++s) {
- if (*s == '\n')
- c = putc(NUL, fd);
- else
- c = putc(*s, fd);
- if (c == EOF) {
- ret = -1;
- break;
- }
- }
- if (!binary || li->li_next != NULL)
- if (putc('\n', fd) == EOF) {
- ret = -1;
- break;
- }
- if (ret < 0) {
- EMSG(_(e_write));
- break;
- }
+ if (write_list(fd, argvars[0].vval.v_list, binary) == false) {
+ rettv->vval.v_number = -1;
}
fclose(fd);
}
-
- rettv->vval.v_number = ret;
}
-
/*
* "xor(expr, expr)" function
*/
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index f826213d08..ee34d23dd2 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -3181,8 +3181,8 @@ static char_u **find_locales(void)
/* Find all available locales by running command "locale -a". If this
* doesn't work we won't have completion. */
- char_u *locale_a = get_cmd_output((char_u *)"locale -a",
- NULL, kShellOptSilent);
+ char_u *locale_a = get_cmd_output((char_u *)"locale -a", NULL,
+ kShellOptSilent, NULL);
if (locale_a == NULL)
return NULL;
ga_init(&locales_ga, sizeof(char_u *), 20);
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index 948580bff2..e9f1aad781 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -6750,7 +6750,8 @@ apply_autocmds_group (
--nesting; /* see matching increment above */
// When stopping to execute autocommands, restore the search patterns and
- // the redo buffer. Free buffers in the au_pending_free_buf list.
+ // the redo buffer. Free any buffers in the au_pending_free_buf list and
+ // free any windows in the au_pending_free_win list.
if (!autocmd_busy) {
restore_search_patterns();
restoreRedobuff();
@@ -6760,6 +6761,11 @@ apply_autocmds_group (
free(au_pending_free_buf);
au_pending_free_buf = b;
}
+ while (au_pending_free_win != NULL) {
+ win_T *w = au_pending_free_win->w_next;
+ free(au_pending_free_win);
+ au_pending_free_win = w;
+ }
}
/*
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index d9c774439c..25c453fea8 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -468,10 +468,12 @@ EXTERN int keep_filetype INIT(= FALSE); /* value for did_filetype when
* which one is preferred, au_new_curbuf is set to it */
EXTERN buf_T *au_new_curbuf INIT(= NULL);
-// When deleting the buffer and autocmd_busy is TRUE, do not free the buffer
-// but link it in the list starting with au_pending_free_buf, using b_next.
-// Free the buffer when autocmd_busy is set to FALSE.
+// When deleting a buffer/window and autocmd_busy is TRUE, do not free the
+// buffer/window. but link it in the list starting with
+// au_pending_free_buf/ap_pending_free_win, using b_next/w_next.
+// Free the buffer/window when autocmd_busy is being set to FALSE.
EXTERN buf_T *au_pending_free_buf INIT(= NULL);
+EXTERN win_T *au_pending_free_win INIT(= NULL);
/*
* Mouse coordinates, set by check_termcode()
diff --git a/src/nvim/memory.c b/src/nvim/memory.c
index f4dffb0bcd..59edefec4a 100644
--- a/src/nvim/memory.c
+++ b/src/nvim/memory.c
@@ -1,5 +1,6 @@
// Various routines dealing with allocation and deallocation of memory.
+#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <string.h>
@@ -222,6 +223,66 @@ void *xmemdupz(const void *data, size_t len)
return memcpy(xmallocz(len), data, len);
}
+/// A version of strchr() that returns a pointer to the terminating NUL if it
+/// doesn't find `c`.
+///
+/// @param str The string to search.
+/// @param c The char to look for.
+/// @returns a pointer to the first instance of `c`, or to the NUL terminator
+/// if not found.
+char *xstrchrnul(const char *str, char c)
+ FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE
+{
+ char *p = strchr(str, c);
+ return p ? p : (char *)(str + strlen(str));
+}
+
+/// A version of memchr() that returns a pointer one past the end
+/// if it doesn't find `c`.
+///
+/// @param addr The address of the memory object.
+/// @param c The char to look for.
+/// @param size The size of the memory object.
+/// @returns a pointer to the first instance of `c`, or one past the end if not
+/// found.
+void *xmemscan(const void *addr, char c, size_t size)
+ FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE
+{
+ char *p = memchr(addr, c, size);
+ return p ? p : (char *)addr + size;
+}
+
+/// Replaces every instance of `c` with `x`.
+///
+/// @warning Will read past `str + strlen(str)` if `c == NUL`.
+///
+/// @param str A NUL-terminated string.
+/// @param c The unwanted byte.
+/// @param x The replacement.
+void strchrsub(char *str, char c, char x)
+ FUNC_ATTR_NONNULL_ALL
+{
+ assert(c != '\0');
+ while ((str = strchr(str, c))) {
+ *str++ = x;
+ }
+}
+
+/// Replaces every instance of `c` with `x`.
+///
+/// @param data An object in memory. May contain NULs.
+/// @param c The unwanted byte.
+/// @param x The replacement.
+/// @param len The length of data.
+void memchrsub(void *data, char c, char x, size_t len)
+ FUNC_ATTR_NONNULL_ALL
+{
+ char *p = data, *end = (char *)data + len;
+ while ((p = memchr(p, c, (size_t)(end - p)))) {
+ *p++ = x;
+ }
+}
+
/// 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/message.c b/src/nvim/message.c
index ef0faa35ee..58dbee8cf9 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -3173,8 +3173,7 @@ int vim_vsnprintf(char *str, size_t str_m, char *fmt, va_list ap, typval_T *tvs)
p = "";
while (*p != NUL) {
if (*p != '%') {
- char *q = strchr(p + 1, '%');
- size_t n = (q == NULL) ? STRLEN(p) : (size_t)(q - p);
+ size_t n = xstrchrnul(p + 1, '%') - p;
/* Copy up to the next '%' or NUL without any changes. */
if (str_l < str_m) {
diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c
index 770026ed88..2eea48df00 100644
--- a/src/nvim/misc1.c
+++ b/src/nvim/misc1.c
@@ -3401,13 +3401,16 @@ void fast_breakcheck(void)
/*
* Get the stdout of an external command.
+ * If "ret_len" is NULL replace NUL characters with NL. When "ret_len" is not
+ * NULL store the length there.
* Returns an allocated string, or NULL for error.
*/
char_u *
get_cmd_output (
char_u *cmd,
char_u *infile, /* optional input file name */
- int flags /* can be SHELL_SILENT */
+ int flags, // can be kShellOptSilent
+ size_t *ret_len
)
{
char_u *tempname;
@@ -3463,13 +3466,15 @@ get_cmd_output (
EMSG2(_(e_notread), tempname);
free(buffer);
buffer = NULL;
- } else {
+ } else if (ret_len == NULL) {
/* Change NUL into SOH, otherwise the string is truncated. */
for (i = 0; i < len; ++i)
if (buffer[i] == NUL)
buffer[i] = 1;
buffer[len] = NUL; /* make sure the buffer is terminated */
+ } else {
+ *ret_len = len;
}
done:
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index ad37b2374f..ed2a82d55c 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -4903,10 +4903,9 @@ static void str_to_reg(struct yankreg *y_ptr,
* Find the end of each line and save it into the array.
*/
for (start = 0; start < len + extraline; start += i + 1) {
- for (i = start; i < len; ++i) /* find the end of the line */
- if (str[i] == '\n')
- break;
- i -= start; /* i is now length of line */
+ // 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) {
diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c
index 07accb339a..36c2bb6d9b 100644
--- a/src/nvim/os/fs.c
+++ b/src/nvim/os/fs.c
@@ -134,10 +134,7 @@ static bool is_executable_in_path(const char_u *name, char_u **abspath)
// Walk through all entries in $PATH to check if "name" exists there and
// is an executable file.
for (;; ) {
- const char *e = strchr(path, ':');
- if (e == NULL) {
- e = path + STRLEN(path);
- }
+ const char *e = xstrchrnul(path, ':');
// Glue together the given directory from $PATH with name and save into
// buf.
diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c
index 398d94b606..912dc95aca 100644
--- a/src/nvim/os/shell.c
+++ b/src/nvim/os/shell.c
@@ -293,19 +293,15 @@ int os_system(const char *cmd,
if (input) {
WBuffer *input_buffer = wstream_new_buffer((char *) input, len, 1, NULL);
- // we want to be notified when the write completes
- job_write_cb(job, system_write_cb);
-
if (!job_write(job, input_buffer)) {
// couldn't write, stop the job and tell the user about it
job_stop(job);
return -1;
}
- } else {
- // close the input stream, let the process know that no input is coming
- job_close_in(job);
}
+ // close the input stream, let the process know that no more input is coming
+ job_close_in(job);
int status = job_wait(job, -1);
// prepare the out parameters if requested
@@ -353,17 +349,6 @@ static void system_data_cb(RStream *rstream, void *data, bool eof)
buf->len += nread;
}
-static void system_write_cb(WStream *wstream,
- void *data,
- size_t pending,
- int status)
-{
- if (pending == 0) {
- Job *job = data;
- job_close_in(job);
- }
-}
-
/// Parses a command string into a sequence of words, taking quotes into
/// consideration.
///
diff --git a/src/nvim/os/wstream.c b/src/nvim/os/wstream.c
index 00a53d1628..eb7de02a2f 100644
--- a/src/nvim/os/wstream.c
+++ b/src/nvim/os/wstream.c
@@ -208,15 +208,14 @@ static void write_cb(uv_write_t *req, int status)
release_wbuffer(data->buffer);
- data->wstream->pending_reqs--;
-
if (data->wstream->cb) {
data->wstream->cb(data->wstream,
data->wstream->data,
- data->wstream->pending_reqs,
status);
}
+ data->wstream->pending_reqs--;
+
if (data->wstream->freed && data->wstream->pending_reqs == 0) {
// Last pending write, free the wstream;
free(data->wstream);
diff --git a/src/nvim/os/wstream_defs.h b/src/nvim/os/wstream_defs.h
index e42481f283..cfa0bf0b60 100644
--- a/src/nvim/os/wstream_defs.h
+++ b/src/nvim/os/wstream_defs.h
@@ -10,11 +10,9 @@ typedef void (*wbuffer_data_finalizer)(void *data);
///
/// @param wstream The `WStream` instance
/// @param data User-defined data
-/// @param pending The number of write requests that are still pending
/// @param status 0 on success, anything else indicates failure
typedef void (*wstream_cb)(WStream *wstream,
void *data,
- size_t pending,
int status);
#endif // NVIM_OS_WSTREAM_DEFS_H
diff --git a/src/nvim/path.c b/src/nvim/path.c
index 6990a1817c..ff97b7774a 100644
--- a/src/nvim/path.c
+++ b/src/nvim/path.c
@@ -1172,7 +1172,7 @@ expand_backtick (
buffer = eval_to_string(cmd + 1, &p, TRUE);
else
buffer = get_cmd_output(cmd, NULL,
- (flags & EW_SILENT) ? kShellOptSilent : 0);
+ (flags & EW_SILENT) ? kShellOptSilent : 0, NULL);
free(cmd);
if (buffer == NULL)
return 0;
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index a4e38aa845..4e6123f206 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -346,7 +346,7 @@ int redraw_asap(int type)
* Note that when also inserting/deleting lines w_redraw_top and w_redraw_bot
* may become invalid and the whole window will have to be redrawn.
*/
-void
+void
redrawWinline (
linenr_T lnum,
int invalid /* window line height is invalid now */
@@ -2114,7 +2114,7 @@ static void copy_text_attr(int off, char_u *buf, int len, int attr)
* Fill the foldcolumn at "p" for window "wp".
* Only to be called when 'foldcolumn' > 0.
*/
-static void
+static void
fill_foldcolumn (
char_u *p,
win_T *wp,
@@ -2166,7 +2166,7 @@ fill_foldcolumn (
*
* Return the number of last row the line occupies.
*/
-static int
+static int
win_line (
win_T *wp,
linenr_T lnum,
@@ -2655,8 +2655,8 @@ win_line (
}
next_search_hl(wp, shl, lnum, (colnr_T)v, cur);
- // Need to get the line again, a multi-line regexp may have made it
- // invalid.
+ // Need to get the line again, a multi-line regexp may have made it
+ // invalid.
line = ml_get_buf(wp->w_buffer, lnum, false);
ptr = line + v;
@@ -2672,7 +2672,7 @@ win_line (
} else {
shl->endcol = MAXCOL;
}
- // Highlight one character for an empty match.
+ // Highlight one character for an empty match.
if (shl->startcol == shl->endcol) {
if (has_mbyte && line[shl->endcol] != NUL) {
shl->endcol += (*mb_ptr2len)(line + shl->endcol);
@@ -2680,7 +2680,7 @@ win_line (
++shl->endcol;
}
}
- if ((long)shl->startcol < v) { // match at leftcol
+ if ((long)shl->startcol < v) { // match at leftcol
shl->attr_cur = shl->attr;
search_attr = shl->attr;
}
@@ -2830,9 +2830,12 @@ win_line (
if (wp->w_p_bri && n_extra == 0 && row != startrow && filler_lines == 0) {
char_attr = 0; // was: hl_attr(HLF_AT);
- if (diff_hlf != (hlf_T)0)
+ if (diff_hlf != (hlf_T)0) {
char_attr = hl_attr(diff_hlf);
-
+ if (wp->w_p_cul && lnum == wp->w_cursor.lnum) {
+ char_attr = hl_combine_attr(char_attr, hl_attr(HLF_CUL));
+ }
+ }
p_extra = NULL;
c_extra = ' ';
n_extra = get_breakindent_win(wp, ml_get_buf(wp->w_buffer, lnum, FALSE));
@@ -2869,8 +2872,9 @@ win_line (
if (tocol == vcol)
tocol += n_extra;
/* combine 'showbreak' with 'cursorline' */
- if (wp->w_p_cul && lnum == wp->w_cursor.lnum)
- char_attr = hl_combine_attr(char_attr, HLF_CLN);
+ if (wp->w_p_cul && lnum == wp->w_cursor.lnum) {
+ char_attr = hl_combine_attr(char_attr, hl_attr(HLF_CUL));
+ }
}
}
@@ -3016,6 +3020,9 @@ win_line (
&& n_extra == 0)
diff_hlf = HLF_CHD; /* changed line */
line_attr = hl_attr(diff_hlf);
+ if (wp->w_p_cul && lnum == wp->w_cursor.lnum) {
+ line_attr = hl_combine_attr(line_attr, hl_attr(HLF_CUL));
+ }
}
/* Decide which of the highlight attributes to use. */
@@ -3620,8 +3627,12 @@ win_line (
char_attr = line_attr;
if (diff_hlf == HLF_TXD) {
diff_hlf = HLF_CHD;
- if (attr == 0 || char_attr != attr)
+ if (attr == 0 || char_attr != attr) {
char_attr = hl_attr(diff_hlf);
+ if (wp->w_p_cul && lnum == wp->w_cursor.lnum) {
+ char_attr = hl_combine_attr(char_attr, hl_attr(HLF_CUL));
+ }
+ }
}
}
}
@@ -4676,7 +4687,7 @@ static int skip_status_match_char(expand_T *xp, char_u *s)
*
* If inversion is possible we use it. Else '=' characters are used.
*/
-void
+void
win_redr_status_matches (
expand_T *xp,
int num_matches,
@@ -4962,7 +4973,7 @@ void win_redr_status(win_T *wp)
fillchar = fillchar_status(&attr, wp == curwin);
else
fillchar = fillchar_vsep(&attr);
- screen_putchar(fillchar, wp->w_winrow + wp->w_height,
+ screen_putchar(fillchar, wp->w_winrow + wp->w_height,
W_ENDCOL(wp), attr);
}
busy = FALSE;
@@ -5024,7 +5035,7 @@ int stl_connected(win_T *wp)
/*
* Get the value to show for the language mappings, active 'keymap'.
*/
-int
+int
get_keymap_str (
win_T *wp,
char_u *buf, /* buffer for the result */
@@ -5068,7 +5079,7 @@ get_keymap_str (
* Redraw the status line or ruler of window "wp".
* When "wp" is NULL redraw the tab pages line from 'tabline'.
*/
-static void
+static void
win_redr_custom (
win_T *wp,
int draw_ruler /* TRUE or FALSE */
@@ -5606,7 +5617,7 @@ static void prepare_search_hl(win_T *wp, linenr_T lnum)
if (cur != NULL) {
cur->pos.cur = 0;
}
- bool pos_inprogress = true; // mark that a position match search is
+ bool pos_inprogress = true; // mark that a position match search is
// in progress
n = 0;
while (shl->first_lnum < lnum && (shl->rm.regprog != NULL
@@ -5637,7 +5648,7 @@ static void prepare_search_hl(win_T *wp, linenr_T lnum)
* shl->lnum is zero.
* Careful: Any pointers for buffer lines will become invalid.
*/
-static void
+static void
next_search_hl (
win_T *win,
match_T *shl, /* points to search_hl or a match */
@@ -5705,9 +5716,9 @@ next_search_hl (
if (shl->rm.regprog != NULL) {
nmatched = vim_regexec_multi(&shl->rm, win, shl->buf, lnum, matchcol, &(shl->tm));
if (called_emsg || got_int) {
- // Error while handling regexp: stop using this regexp.
+ // Error while handling regexp: stop using this regexp.
if (shl == &search_hl) {
- // don't free regprog in the match list, it's a copy
+ // don't free regprog in the match list, it's a copy
vim_regfree(shl->rm.regprog);
SET_NO_HLSEARCH(TRUE);
}
@@ -5735,11 +5746,11 @@ next_search_hl (
static int
next_search_hl_pos(
- match_T *shl, // points to a match
+ match_T *shl, // points to a match
linenr_T lnum,
- posmatch_T *posmatch, // match positions
+ posmatch_T *posmatch, // match positions
colnr_T mincol // minimal column for a match
-)
+)
{
int i;
int bot = -1;
@@ -5754,8 +5765,8 @@ next_search_hl_pos(
}
if (posmatch->pos[i].lnum == lnum) {
if (shl->lnum == lnum) {
- // partially sort positions by column numbers
- // on the same line
+ // partially sort positions by column numbers
+ // on the same line
if (posmatch->pos[i].col < posmatch->pos[bot].col) {
llpos_T tmp = posmatch->pos[i];
@@ -5772,7 +5783,7 @@ next_search_hl_pos(
if (shl->lnum == lnum) {
colnr_T start = posmatch->pos[bot].col == 0
? 0: posmatch->pos[bot].col - 1;
- colnr_T end = posmatch->pos[bot].col == 0
+ colnr_T end = posmatch->pos[bot].col == 0
? MAXCOL : start + posmatch->pos[bot].len;
shl->rm.startpos[0].lnum = 0;
@@ -7091,7 +7102,7 @@ static void win_rest_invalid(win_T *wp)
*
* return FAIL for failure, OK for success.
*/
-int
+int
screen_ins_lines (
int off,
int row,
@@ -7264,7 +7275,7 @@ screen_ins_lines (
*
* Return OK for success, FAIL if the lines are not deleted.
*/
-int
+int
screen_del_lines (
int off,
int row,
@@ -7630,7 +7641,7 @@ void unshowmode(int force)
static void draw_tabline(void)
{
int tabcount = 0;
- int tabwidth;
+ int tabwidth = 0;
int col = 0;
int scol = 0;
int attr;
@@ -7676,9 +7687,13 @@ static void draw_tabline(void)
++tabcount;
}
- tabwidth = (Columns - 1 + tabcount / 2) / tabcount;
- if (tabwidth < 6)
+ if (tabcount > 0) {
+ tabwidth = (Columns - 1 + tabcount / 2) / tabcount;
+ }
+
+ if (tabwidth < 6) {
tabwidth = 6;
+ }
attr = attr_nosel;
tabcount = 0;
@@ -7720,8 +7735,7 @@ static void draw_tabline(void)
if (col + len >= Columns - 3)
break;
screen_puts_len(NameBuff, len, 0, col,
- hl_combine_attr(attr, hl_attr(HLF_T))
- );
+ hl_combine_attr(attr, hl_attr(HLF_T)));
col += len;
}
if (modified)
diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile
index 81dc49e800..9f04f880b5 100644
--- a/src/nvim/testdir/Makefile
+++ b/src/nvim/testdir/Makefile
@@ -33,6 +33,7 @@ SCRIPTS := test_autoformat_join.out \
test106.out test107.out \
test_options.out \
test_listlbr.out test_listlbr_utf8.out \
+ test_changelist.out \
test_breakindent.out \
test_insertcount.out
diff --git a/src/nvim/testdir/test_changelist.in b/src/nvim/testdir/test_changelist.in
new file mode 100644
index 0000000000..6c7c4306c3
--- /dev/null
+++ b/src/nvim/testdir/test_changelist.in
@@ -0,0 +1,22 @@
+Test changelist position after splitting window
+Set 'undolevels' to make changelist for sourced file
+
+STARTTEST
+:so small.vim
+Gkylp:set ul=100
+Gylp:set ul=100
+gg
+:vsplit
+:try
+: normal g;
+: normal ggVGcpass
+:catch
+: normal ggVGcfail
+:finally
+: %w! test.out
+:endtry
+:qa!
+ENDTEST
+
+1
+2
diff --git a/src/nvim/testdir/test_changelist.ok b/src/nvim/testdir/test_changelist.ok
new file mode 100644
index 0000000000..2ae28399f5
--- /dev/null
+++ b/src/nvim/testdir/test_changelist.ok
@@ -0,0 +1 @@
+pass
diff --git a/src/nvim/version.c b/src/nvim/version.c
index 199c034e97..47f4a2f805 100644
--- a/src/nvim/version.c
+++ b/src/nvim/version.c
@@ -184,6 +184,19 @@ static char *(features[]) = {
};
static int included_patches[] = {
+ //457,
+ //456,
+ //455,
+ 454,
+ //453,
+ //452,
+ //451,
+ //450,
+ //449,
+ //448,
+ //447,
+ //446,
+ //445,
//444,
//443,
//442,
@@ -217,7 +230,7 @@ static int included_patches[] = {
//414,
//413 NA
//412 NA
- //411,
+ 411,
410,
//409 NA
//408,
@@ -237,12 +250,12 @@ static int included_patches[] = {
//394,
//393,
392,
- //391,
+ 391,
//390,
//389,
388,
387,
- //386,
+ 386,
//385,
//384 NA
383,
@@ -308,7 +321,7 @@ static int included_patches[] = {
323,
//322 NA
//321 NA
- //320,
+ 320,
//319 NA
318,
317,
@@ -372,7 +385,7 @@ static int included_patches[] = {
//259 NA
//258 NA
//257 NA
- //256,
+ 256,
//255,
//254,
253,
@@ -380,8 +393,8 @@ static int included_patches[] = {
251,
//250 NA
//249,
- //248,
- //247,
+ 248,
+ 247,
//246,
245,
//244,
diff --git a/src/nvim/window.c b/src/nvim/window.c
index bce54eecdc..fe40cd2a95 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -364,14 +364,16 @@ newwindow:
/*FALLTHROUGH*/
case ']':
case Ctrl_RSB:
- CHECK_CMDWIN reset_VIsual_and_resel(); /* stop Visual mode */
+ CHECK_CMDWIN
+ // Keep visual mode, can select words to use as a tag.
if (Prenum)
postponed_split = Prenum;
else
postponed_split = -1;
+ g_do_tagpreview = 0;
- /* Execute the command right here, required when
- * "wincmd ]" was used in a function. */
+ // Execute the command right here, required when
+ // "wincmd ]" was used in a function.
do_nv_ident(Ctrl_RSB, NUL);
break;
@@ -457,7 +459,7 @@ wingotofile:
/*FALLTHROUGH*/
case ']':
case Ctrl_RSB:
- reset_VIsual_and_resel(); /* stop Visual mode */
+ // Keep visual mode, can select words to use as a tag.
if (Prenum)
postponed_split = Prenum;
else
@@ -948,6 +950,9 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
p_wh = size;
}
+ // Keep same changelist position in new window.
+ wp->w_changelistidx = oldwin->w_changelistidx;
+
/*
* make the new window the current window
*/
@@ -3692,7 +3697,12 @@ win_free (
if (wp != aucmd_win)
win_remove(wp, tp);
- free(wp);
+ if (autocmd_busy) {
+ wp->w_next = au_pending_free_win;
+ au_pending_free_win = wp;
+ } else {
+ free(wp);
+ }
unblock_autocmds();
}
diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua
new file mode 100644
index 0000000000..671e34e592
--- /dev/null
+++ b/test/functional/helpers.lua
@@ -0,0 +1,102 @@
+local function clear()
+ nvim_command('call BeforeEachTest()')
+end
+
+local function feed(...)
+ for _, v in ipairs({...}) do
+ nvim_feed(nvim_replace_termcodes(dedent(v)))
+ end
+end
+
+local function rawfeed(...)
+ for _, v in ipairs({...}) do
+ nvim_feed(dedent(v), 'nt')
+ end
+end
+
+local function insert(...)
+ nvim_feed('i', 'nt')
+ rawfeed(...)
+ nvim_feed(nvim_replace_termcodes('<ESC>'), 'nt')
+end
+
+local function execute(...)
+ for _, v in ipairs({...}) do
+ if v:sub(1, 1) ~= '/' then
+ -- not a search command, prefix with colon
+ nvim_feed(':', 'nt')
+ end
+ nvim_feed(v, 'nt')
+ nvim_feed(nvim_replace_termcodes('<CR>'), 'nt')
+ end
+end
+
+local function eval(expr)
+ local status, result = pcall(function() return nvim_eval(expr) end)
+ if not status then
+ error('Failed to evaluate expression "' .. expr .. '"')
+ end
+ return result
+end
+
+local function eq(expected, actual)
+ return assert.are.same(expected, actual)
+end
+
+local function neq(expected, actual)
+ return assert.are_not.same(expected, actual)
+end
+
+local function expect(contents, first, last, buffer_index)
+ return eq(dedent(contents), buffer_slice(first, last, buffer_idx))
+end
+
+rawfeed([[:function BeforeEachTest()
+ set all&
+ redir => groups
+ silent augroup
+ redir END
+ for group in split(groups)
+ exe 'augroup '.group
+ autocmd!
+ augroup END
+ endfor
+ autocmd!
+ tabnew
+ let curbufnum = eval(bufnr('%'))
+ redir => buflist
+ silent ls!
+ redir END
+ let bufnums = []
+ for buf in split(buflist, '\n')
+ let bufnum = eval(split(buf, '[ u]')[0])
+ if bufnum != curbufnum
+ call add(bufnums, bufnum)
+ endif
+ endfor
+ if len(bufnums) > 0
+ exe 'silent bwipeout! '.join(bufnums, ' ')
+ endif
+ silent tabonly
+ for k in keys(g:)
+ exe 'unlet g:'.k
+ endfor
+ filetype plugin indent off
+ mapclear
+ mapclear!
+ abclear
+ comclear
+endfunction
+]])
+
+return {
+ clear = clear,
+ rawfeed = rawfeed,
+ insert = insert,
+ feed = feed,
+ execute = execute,
+ eval = eval,
+ eq = eq,
+ neq = neq,
+ expect = expect
+}
diff --git a/test/functional/legacy/002_filename_recognition_spec.lua b/test/functional/legacy/002_filename_recognition_spec.lua
new file mode 100644
index 0000000000..569e748631
--- /dev/null
+++ b/test/functional/legacy/002_filename_recognition_spec.lua
@@ -0,0 +1,42 @@
+-- Test if URLs are recognized as filenames by commands such as "gf". Here
+-- we'll use `expand("<cfile>")` since "gf" would need to open the file.
+
+local helpers = require('test.functional.helpers')
+local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert
+local execute, expect = helpers.execute, helpers.expect
+
+describe('filename recognition', function()
+ setup(clear)
+
+ it('is working', function()
+ -- insert some lines containing URLs
+ insert([[
+ first test for URL://machine.name/tmp/vimtest2a and other text
+ second test for URL://machine.name/tmp/vimtest2b. And other text
+ third test for URL:\\machine.name\vimtest2c and other text
+ fourth test for URL:\\machine.name\tmp\vimtest2d, and other text]])
+
+ -- Go to the first URL and append it to the beginning
+ execute('/^first', '/tmp', 'call append(0, expand("<cfile>"))')
+
+ -- Repeat for the second URL
+ -- this time, navigate to the word "URL" instead of "tmp"
+ execute('/^second', '/URL', 'call append(1, expand("<cfile>"))')
+
+ -- Repeat for the remaining URLs. This time, the 'isfname' option must be
+ -- set to allow '\' in filenames
+ execute('set isf=@,48-57,/,.,-,_,+,,,$,:,~,\\')
+ execute('/^third', '/name', 'call append(2, expand("<cfile>"))')
+ execute('/^fourth', '/URL', 'call append(3, expand("<cfile>"))')
+
+ -- Delete the initial text, which now starts at line 5
+ feed('5GdG')
+
+ -- The buffer should now contain:
+ expect([[
+ URL://machine.name/tmp/vimtest2a
+ URL://machine.name/tmp/vimtest2b
+ URL:\\machine.name\vimtest2c
+ URL:\\machine.name\tmp\vimtest2d]])
+ end)
+end)
diff --git a/test/functional/shell/viml_system_spec.lua b/test/functional/shell/viml_system_spec.lua
new file mode 100644
index 0000000000..effabe715c
--- /dev/null
+++ b/test/functional/shell/viml_system_spec.lua
@@ -0,0 +1,125 @@
+-- Specs for
+-- - `system()`
+-- - `systemlist()`
+
+local helpers = require('test.functional.helpers')
+local eq, clear, eval, feed =
+ helpers.eq, helpers.clear, helpers.eval, helpers.feed
+
+
+local function create_file_with_nuls(name)
+ return function()
+ feed('ipart1<C-V>000part2<C-V>000part3<ESC>:w '..name..'<CR>')
+ end
+end
+
+local function delete_file(name)
+ return function()
+ eval("delete('"..name.."')")
+ end
+end
+
+
+describe('system()', function()
+ before_each(clear)
+
+ describe('passing no input', function()
+ it('returns the program output', function()
+ eq("echoed", eval('system("echo -n echoed")'))
+ end)
+ end)
+
+ describe('passing input', function()
+ it('returns the program output', function()
+ eq("input", eval('system("cat -", "input")'))
+ end)
+ end)
+
+ describe('passing number as input', function()
+ it('stringifies the input', function()
+ eq('1', eval('system("cat", 1)'))
+ end)
+ end)
+
+ describe('with output containing NULs', function()
+ local fname = 'Xtest'
+
+ setup(create_file_with_nuls(fname))
+ teardown(delete_file(fname))
+
+ it('replaces NULs by SOH characters', function()
+ eq('part1\001part2\001part3\n', eval('system("cat '..fname..'")'))
+ end)
+ end)
+
+ describe('passing list as input', function()
+ it('joins list items with linefeed characters', function()
+ eq('line1\nline2\nline3',
+ eval("system('cat -', ['line1', 'line2', 'line3'])"))
+ end)
+
+ -- Notice that NULs are converted to SOH when the data is read back. This
+ -- is inconsistent and is a good reason for the existence of the
+ -- `systemlist()` function, where input and output map to the same
+ -- characters(see the following tests with `systemlist()` below)
+ describe('with linefeed characters inside list items', function()
+ it('converts linefeed characters to NULs', function()
+ eq('l1\001p2\nline2\001a\001b\nl3',
+ eval([[system('cat -', ["l1\np2", "line2\na\nb", 'l3'])]]))
+ end)
+ end)
+
+ describe('with leading/trailing whitespace characters on items', function()
+ it('preserves whitespace, replacing linefeeds by NULs', function()
+ eq('line \nline2\001\n\001line3',
+ eval([[system('cat -', ['line ', "line2\n", "\nline3"])]]))
+ end)
+ end)
+ end)
+end)
+
+describe('systemlist()', function()
+ -- behavior is similar to `system()` but it returns a list instead of a
+ -- string.
+ before_each(clear)
+
+ describe('passing string with linefeed characters as input', function()
+ it('splits the output on linefeed characters', function()
+ eq({'abc', 'def', 'ghi'}, eval([[systemlist("cat -", "abc\ndef\nghi")]]))
+ end)
+ end)
+
+ describe('with output containing NULs', function()
+ local fname = 'Xtest'
+
+ setup(create_file_with_nuls(fname))
+ teardown(delete_file(fname))
+
+ it('replaces NULs by newline characters', function()
+ eq({'part1\npart2\npart3'}, eval('systemlist("cat '..fname..'")'))
+ end)
+ end)
+
+ describe('passing list as input', function()
+ it('joins list items with linefeed characters', function()
+ eq({'line1', 'line2', 'line3'},
+ eval("systemlist('cat -', ['line1', 'line2', 'line3'])"))
+ end)
+
+ -- Unlike `system()` which uses SOH to represent NULs, with `systemlist()`
+ -- input and ouput are the same
+ describe('with linefeed characters inside list items', function()
+ it('converts linefeed characters to NULs', function()
+ eq({'l1\np2', 'line2\na\nb', 'l3'},
+ eval([[systemlist('cat -', ["l1\np2", "line2\na\nb", 'l3'])]]))
+ end)
+ end)
+
+ describe('with leading/trailing whitespace characters on items', function()
+ it('preserves whitespace, replacing linefeeds by NULs', function()
+ eq({'line ', 'line2\n', '\nline3'},
+ eval([[systemlist('cat -', ['line ', "line2\n", "\nline3"])]]))
+ end)
+ end)
+ end)
+end)
diff --git a/test/legacy/test2-filename-recognition.vroom b/test/legacy/test2-filename-recognition.vroom
deleted file mode 100644
index b8169ec207..0000000000
--- a/test/legacy/test2-filename-recognition.vroom
+++ /dev/null
@@ -1,40 +0,0 @@
-Test if URLs are recognized as filenames by commands such as "gf". Here
-we'll use `expand("<cfile>")` since "gf" would need to open the file.
-
-Insert some URLs:
-
- % first test for URL://machine.name/tmp/vimtest2a and other text<cr>
- % second test for URL://machine.name/tmp/vimtest2b. And other text<cr>
- % third test for URL:\\machine.name\vimtest2c and other text<cr>
- % fourth test for URL:\\machine.name\tmp\vimtest2d, and other text
-
-Go to the first URL:
-
- :/^first<cr>/tmp
-
-Append the url to the beginning of the buffer:
-
- :call append(0, expand("<cfile>"))
-
-Repeat for the second URL, but navigate to the 'URL' word:
-
- :/^second<cr>/URL<cr>:call append(1, expand("<cfile>"))
-
-Repeat for the other two, but first the 'isfname' option must be set to
-allow '\' in filenames
-
- :set isf=@,48-57,/,.,-,_,+,,,$,:,~,\
- :/^third<cr>/name<cr>:call append(2, expand("<cfile>"))
- :/^fourth<cr>/URL<cr>:call append(3, expand("<cfile>"))
-
-Delete the initial text which starts at line 5:
-
- > 5GdG
-
-Result:
-
- URL://machine.name/tmp/vimtest2a
- URL://machine.name/tmp/vimtest2b
- URL:\\machine.name\vimtest2c
- URL:\\machine.name\tmp\vimtest2d
- @end