aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-x.ci/before_cache.sh10
-rw-r--r--.ci/common/build.sh34
-rwxr-xr-x.ci/run_tests.sh8
-rw-r--r--.travis.yml21
-rw-r--r--CMakeLists.txt1
-rw-r--r--cmake/FindLibUV.cmake20
-rw-r--r--contrib/gdb/neovim_gdb.vim (renamed from contrib/neovim_gdb/neovim_gdb.vim)0
-rw-r--r--contrib/gdb/nvim-gdb-pretty-printers.py92
-rw-r--r--runtime/doc/mbyte.txt4
-rw-r--r--runtime/doc/message.txt6
-rw-r--r--runtime/doc/options.txt28
-rw-r--r--runtime/doc/vim_diff.txt2
-rw-r--r--src/nvim/CMakeLists.txt12
-rw-r--r--src/nvim/api/buffer.c2
-rw-r--r--src/nvim/eval.c299
-rw-r--r--src/nvim/eval_defs.h14
-rw-r--r--src/nvim/event/defs.h3
-rw-r--r--src/nvim/event/libuv_process.c3
-rw-r--r--src/nvim/event/loop.c42
-rw-r--r--src/nvim/event/loop.h5
-rw-r--r--src/nvim/event/queue.c38
-rw-r--r--src/nvim/event/stream.c1
-rw-r--r--src/nvim/globals.h6
-rw-r--r--src/nvim/log.c24
-rw-r--r--src/nvim/main.c2
-rw-r--r--src/nvim/move.c4
-rw-r--r--src/nvim/msgpack_rpc/channel.c3
-rw-r--r--src/nvim/option.c17
-rw-r--r--src/nvim/testdir/test10.in1
-rw-r--r--src/nvim/testdir/test39.in14
-rw-r--r--src/nvim/testdir/test39.okbin584 -> 585 bytes
-rw-r--r--src/nvim/testdir/test69.in1
-rw-r--r--src/nvim/testdir/test83.in1
-rw-r--r--src/nvim/testdir/test_eval.in1
-rw-r--r--src/nvim/testdir/unix.vim3
-rw-r--r--src/nvim/tui/input.c (renamed from src/nvim/tui/term_input.inl)141
-rw-r--r--src/nvim/tui/input.h23
-rw-r--r--src/nvim/tui/tui.c438
-rw-r--r--src/nvim/ugrid.c137
-rw-r--r--src/nvim/ugrid.h40
-rw-r--r--src/nvim/ui.c6
-rw-r--r--src/nvim/ui.h1
-rw-r--r--src/nvim/ui_bridge.c335
-rw-r--r--src/nvim/ui_bridge.h40
-rw-r--r--src/nvim/version.c2
-rw-r--r--test/functional/ex_cmds/encoding_spec.lua40
-rw-r--r--test/functional/helpers.lua11
-rw-r--r--test/functional/legacy/044_099_regexp_multibyte_magic_spec.lua1
-rw-r--r--test/functional/legacy/075_maparg_spec.lua1
-rw-r--r--test/functional/legacy/082_string_comparison_spec.lua1
-rw-r--r--test/functional/legacy/095_regexp_multibyte_spec.lua2
-rw-r--r--test/functional/legacy/mapping_spec.lua2
-rw-r--r--test/functional/legacy/utf8_spec.lua1
-rw-r--r--third-party/CMakeLists.txt6
-rw-r--r--third-party/cmake/BuildJeMalloc.cmake1
-rw-r--r--third-party/cmake/BuildLibtermkey.cmake1
-rw-r--r--third-party/cmake/BuildLibuv.cmake1
-rw-r--r--third-party/cmake/BuildLibvterm.cmake1
-rw-r--r--third-party/cmake/BuildLuajit.cmake1
-rw-r--r--third-party/cmake/BuildLuarocks.cmake1
-rw-r--r--third-party/cmake/BuildMsgpack.cmake1
-rw-r--r--third-party/cmake/BuildUnibilium.cmake1
-rw-r--r--third-party/cmake/DownloadAndExtractFile.cmake13
63 files changed, 1439 insertions, 532 deletions
diff --git a/.ci/before_cache.sh b/.ci/before_cache.sh
index 8925da92f3..b5678bc20c 100755
--- a/.ci/before_cache.sh
+++ b/.ci/before_cache.sh
@@ -15,10 +15,8 @@ rm -f "${HOME}/.cache/pip/selfcheck.json"
# Update the third-party dependency cache only if the build was successful.
if [[ -f "${SUCCESS_MARKER}" ]]; then
- if [[ ! -f "${CACHE_MARKER}" ]] || [[ "${BUILD_NVIM_DEPS}" == true ]]; then
- echo "Updating third-party dependency cache."
- rm -rf "${HOME}/.cache/nvim-deps"
- mv -T "${DEPS_INSTALL_PREFIX}" "${HOME}/.cache/nvim-deps"
- touch "${CACHE_MARKER}"
- fi
+ rm -rf "${HOME}/.cache/nvim-deps"
+ mv -T "${DEPS_BUILD_DIR}" "${HOME}/.cache/nvim-deps"
+ touch "${CACHE_MARKER}"
+ echo "Updated third-party dependencies (timestamp: $(stat -c '%y' "${CACHE_MARKER}"))."
fi
diff --git a/.ci/common/build.sh b/.ci/common/build.sh
index 0140be3637..3d517b9b09 100644
--- a/.ci/common/build.sh
+++ b/.ci/common/build.sh
@@ -11,19 +11,21 @@ build_deps() {
DEPS_CMAKE_FLAGS="${DEPS_CMAKE_FLAGS} ${CMAKE_FLAGS_MINGW}"
fi
- rm -rf "${DEPS_INSTALL_PREFIX}"
+ rm -rf "${DEPS_BUILD_DIR}"
# If there is a valid cache and we're not forced to recompile,
# use cached third-party dependencies.
if [[ -f "${CACHE_MARKER}" ]] && [[ "${BUILD_NVIM_DEPS}" != true ]]; then
echo "Using third-party dependencies from Travis's cache (last updated: $(stat -c '%y' "${CACHE_MARKER}"))."
- mkdir -p "$(dirname "${DEPS_INSTALL_PREFIX}")"
- ln -Ts "${HOME}/.cache/nvim-deps" "${DEPS_INSTALL_PREFIX}"
- return
+ mkdir -p "$(dirname "${DEPS_BUILD_DIR}")"
+ mv -T "${HOME}/.cache/nvim-deps" "${DEPS_BUILD_DIR}"
+ else
+ mkdir -p "${DEPS_BUILD_DIR}"
fi
- mkdir -p "${DEPS_BUILD_DIR}"
+ # Even if we're using cached dependencies, run CMake and make to
+ # update CMake configuration and update to newer deps versions.
cd "${DEPS_BUILD_DIR}"
echo "Configuring with '${DEPS_CMAKE_FLAGS}'."
cmake ${DEPS_CMAKE_FLAGS} "${TRAVIS_BUILD_DIR}/third-party/"
@@ -61,19 +63,25 @@ build_nvim() {
exit 1
fi
- echo "Building libnvim."
- if ! ${MAKE_CMD} libnvim; then
- exit 1
+ if [ "$CLANG_SANITIZER" != "TSAN" ]; then
+ echo "Building libnvim."
+ if ! ${MAKE_CMD} libnvim; then
+ exit 1
+ fi
+
+ echo "Building nvim-test."
+ if ! ${MAKE_CMD} nvim-test; then
+ exit 1
+ fi
fi
- echo "Building nvim-test."
- if ! ${MAKE_CMD} nvim-test; then
+ # Invoke nvim to trigger *San early.
+ if ! (bin/nvim --version && bin/nvim -u NONE -e -c ':qall'); then
+ asan_check "${LOG_DIR}"
exit 1
fi
+ asan_check "${LOG_DIR}"
- # Invoke nvim to trigger *San early.
- bin/nvim --version
- bin/nvim -u NONE -e -c ':qall'
cd "${TRAVIS_BUILD_DIR}"
}
diff --git a/.ci/run_tests.sh b/.ci/run_tests.sh
index f3a6466c5e..656ea16e37 100755
--- a/.ci/run_tests.sh
+++ b/.ci/run_tests.sh
@@ -17,8 +17,12 @@ source "${CI_DIR}/common/test.sh"
build_nvim
-run_unittests
-run_functionaltests
+if [ "$CLANG_SANITIZER" != "TSAN" ]; then
+ # Additional threads are only created when the builtin UI starts, which
+ # doesn't happen in the unit/functional tests
+ run_unittests
+ run_functionaltests
+fi
run_oldtests
install_nvim
diff --git a/.travis.yml b/.travis.yml
index becc7e8d3d..bd2c9b3938 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,9 +3,7 @@ language: c
env:
global:
- # To build third-party dependencies, set this to 'true'.
- # TODO: Change deps caching to detect updated dependencies automatically, but
- # still don't rebuild deps every time.
+ # To force rebuilding of third-party dependencies, set this to 'true'.
- BUILD_NVIM_DEPS=false
# Travis has 1.5 virtual cores according to
# http://docs.travis-ci.com/user/speeding-up-the-build/#Paralellizing-your-build-on-one-VM
@@ -19,9 +17,9 @@ env:
# Build directory for Neovim.
- BUILD_DIR="$TRAVIS_BUILD_DIR/build"
# Build directory for third-party dependencies.
- - DEPS_BUILD_DIR="$TRAVIS_BUILD_DIR/deps-build"
- # Directory where compiled third-party dependencies are stored.
- - DEPS_INSTALL_PREFIX="$HOME/nvim-deps-install"
+ - DEPS_BUILD_DIR="$HOME/nvim-deps"
+ # Directory where third-party dependency sources are downloaded to.
+ - DEPS_DOWNLOAD_DIR="$TRAVIS_BUILD_DIR/deps-downloads"
# Install directory for Neovim.
- INSTALL_PREFIX="$HOME/nvim-install"
# Log directory for Clang sanitizers and Valgrind.
@@ -32,8 +30,8 @@ env:
-DCMAKE_INSTALL_PREFIX:PATH=$INSTALL_PREFIX
-DUSE_GCOV=ON
-DBUSTED_OUTPUT_TYPE=plainTerminal
- -DDEPS_PREFIX=$DEPS_INSTALL_PREFIX"
- - DEPS_CMAKE_FLAGS="-DDEPS_INSTALL_DIR:PATH=$DEPS_INSTALL_PREFIX"
+ -DDEPS_PREFIX=$DEPS_BUILD_DIR/usr"
+ - DEPS_CMAKE_FLAGS="-DDEPS_DOWNLOAD_DIR:PATH=$DEPS_DOWNLOAD_DIR"
# Additional CMake flags for 32-bit builds.
- CMAKE_FLAGS_32BIT="-DCMAKE_SYSTEM_LIBRARY_PATH=/lib32:/usr/lib32:/usr/local/lib32
-DCMAKE_IGNORE_PATH=/lib:/usr/lib:/usr/local/lib
@@ -77,10 +75,9 @@ matrix:
- os: linux
compiler: clang-3.6
env: GCOV=llvm-cov-3.6 CLANG_SANITIZER=MSAN
- # FIXME (tarruda): Uncomment when TSan tests don't hang anymore.
- #- os: linux
- # compiler: clang-3.6
- # env: GCOV=llvm-cov-3.6 CLANG_SANITIZER=TSAN
+ - os: linux
+ compiler: clang-3.6
+ env: CLANG_SANITIZER=TSAN
- os: osx
compiler: clang
env: GCOV=gcov
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c18d8982e9..1c637ced90 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -48,6 +48,7 @@ endif()
# Version tokens
include(GetGitRevisionDescription)
+file(TO_NATIVE_PATH ${CMAKE_CURRENT_LIST_DIR}/.git GIT_DIR)
get_git_head_revision(GIT_REFSPEC NVIM_VERSION_COMMIT)
if(NOT NVIM_VERSION_COMMIT)
set(NVIM_VERSION_COMMIT "?")
diff --git a/cmake/FindLibUV.cmake b/cmake/FindLibUV.cmake
index c2f22d557b..8542100e67 100644
--- a/cmake/FindLibUV.cmake
+++ b/cmake/FindLibUV.cmake
@@ -59,6 +59,11 @@ if(HAVE_LIBDL)
list(APPEND LIBUV_LIBRARIES dl)
endif()
+check_library_exists(iphlpapi GetAdaptersAddresses "iphlpapi.h" HAVE_LIBIPHLPAPI)
+if(HAVE_LIBIPHLPAPI)
+ list(APPEND LIBUV_LIBRARIES iphlpapi)
+endif()
+
check_library_exists(kstat kstat_lookup "kstat.h" HAVE_LIBKSTAT)
if(HAVE_LIBKSTAT)
list(APPEND LIBUV_LIBRARIES kstat)
@@ -79,6 +84,11 @@ if(HAVE_LIBPERFSTAT)
list(APPEND LIBUV_LIBRARIES perfstat)
endif()
+check_library_exists(psapi GetProcessMemoryInfo "psapi.h" HAVE_LIBPSAPI)
+if(HAVE_LIBPSAPI)
+ list(APPEND LIBUV_LIBRARIES psapi)
+endif()
+
check_library_exists(rt clock_gettime "time.h" HAVE_LIBRT)
if(HAVE_LIBRT)
list(APPEND LIBUV_LIBRARIES rt)
@@ -89,6 +99,16 @@ if(HAVE_LIBSENDFILE)
list(APPEND LIBUV_LIBRARIES sendfile)
endif()
+check_library_exists(userenv GetUserProfileDirectoryW "userenv.h" HAVE_LIBUSERENV)
+if(HAVE_LIBUSERENV)
+ list(APPEND LIBUV_LIBRARIES userenv)
+endif()
+
+check_library_exists(ws2_32 WSAStartup "winsock2.h" HAVE_LIBWS232)
+if(HAVE_LIBWS232)
+ list(APPEND LIBUV_LIBRARIES ws2_32)
+endif()
+
include(FindPackageHandleStandardArgs)
# handle the QUIETLY and REQUIRED arguments and set LIBUV_FOUND to TRUE
diff --git a/contrib/neovim_gdb/neovim_gdb.vim b/contrib/gdb/neovim_gdb.vim
index d61e7bc0cc..d61e7bc0cc 100644
--- a/contrib/neovim_gdb/neovim_gdb.vim
+++ b/contrib/gdb/neovim_gdb.vim
diff --git a/contrib/gdb/nvim-gdb-pretty-printers.py b/contrib/gdb/nvim-gdb-pretty-printers.py
new file mode 100644
index 0000000000..609ceeb7ab
--- /dev/null
+++ b/contrib/gdb/nvim-gdb-pretty-printers.py
@@ -0,0 +1,92 @@
+# Register a gdb pretty printer for UGrid instances. Usage:
+#
+# - start gdb
+# - run `source contrib/gdb/nvim-gdb-pretty-printers.py`
+# - when a `UGrid` pointer can be evaluated in the current frame, just print
+# it's value normally: `p *grid` (assuming `grid` is the variable name
+# holding the pointer)
+# - highlighting can be activated by setting the NVIM_GDB_HIGHLIGHT_UGRID
+# environment variable(only xterm-compatible terminals supported). This
+# can be done while gdb is running through the python interface:
+# `python os.environ['NVIM_GDB_HIGHLIGHT_UGRID'] = '1'`
+import os
+import gdb
+import gdb.printing
+
+
+SGR0 = '\x1b(B\x1b[m'
+
+
+def get_color_code(bg, color_num):
+ if color_num < 16:
+ prefix = 3
+ if color_num > 7:
+ prefix = 9
+ if bg:
+ prefix += 1
+ color_num %= 8
+ else:
+ prefix = '48;5;' if bg else '38;5;'
+ return '\x1b[{0}{1}m'.format(prefix, color_num)
+
+
+def highlight(attrs):
+ fg, bg = [int(attrs['foreground']), int(attrs['background'])]
+ rv = [SGR0] # start with sgr0
+ if fg != -1:
+ rv.append(get_color_code(False, fg))
+ if bg != -1:
+ rv.append(get_color_code(True, bg))
+ if bool(attrs['bold']):
+ rv.append('\x1b[1m')
+ if bool(attrs['italic']):
+ rv.append('\x1b[3m')
+ if bool(attrs['undercurl']) or bool(attrs['underline']):
+ rv.append('\x1b[4m')
+ if bool(attrs['reverse']):
+ rv.append('\x1b[7m')
+ return ''.join(rv)
+
+
+class UGridPrinter(object):
+ def __init__(self, val):
+ self.val = val
+
+ def to_string(self):
+ do_hl = (os.getenv('NVIM_GDB_HIGHLIGHT_UGRID') and
+ os.getenv('NVIM_GDB_HIGHLIGHT_UGRID') != '0')
+ grid = self.val
+ height = int(grid['height'])
+ width = int(grid['width'])
+ delimiter = '-' * (width + 2)
+ rows = [delimiter]
+ for row in range(height):
+ cols = []
+ if do_hl:
+ cols.append(SGR0)
+ curhl = None
+ for col in range(width):
+ cell = grid['cells'][row][col]
+ if do_hl:
+ hl = highlight(cell['attrs'])
+ if hl != curhl:
+ cols.append(hl)
+ curhl = hl
+ cols.append(cell['data'].string('utf-8'))
+ if do_hl:
+ cols.append(SGR0)
+ rows.append('|' + ''.join(cols) + '|')
+ rows.append(delimiter)
+ return '\n' + '\n'.join(rows)
+
+ def display_hint(self):
+ return 'hint'
+
+
+def pretty_printers():
+ pp = gdb.printing.RegexpCollectionPrettyPrinter('nvim')
+ pp.add_printer('UGrid', '^ugrid$', UGridPrinter)
+ return pp
+
+
+gdb.printing.register_pretty_printer(gdb, pretty_printers(), replace=True)
diff --git a/runtime/doc/mbyte.txt b/runtime/doc/mbyte.txt
index 8189ec5b38..c959c12fb4 100644
--- a/runtime/doc/mbyte.txt
+++ b/runtime/doc/mbyte.txt
@@ -89,8 +89,8 @@ See |encoding-values| for a list of acceptable values.
The result is that all the text that is used inside Vim will be in this
encoding. Not only the text in the buffers, but also in registers, variables,
-etc. This also means that changing the value of 'encoding' makes the existing
-text invalid! The text doesn't change, but it will be displayed wrong.
+etc. 'encoding' is read-only after startup because changing it would make the
+existing text invalid.
You can edit files in another encoding than what 'encoding' is set to. Vim
will convert the file when you read it and convert it back when you write it.
diff --git a/runtime/doc/message.txt b/runtime/doc/message.txt
index 7d674ae4d7..a17eee4976 100644
--- a/runtime/doc/message.txt
+++ b/runtime/doc/message.txt
@@ -739,6 +739,12 @@ Example: >
You tried to execute a command that is neither an Ex command nor
a user-defined command.
+ *E905* >
+ Cannot set this option after startup
+
+You tried to set an option after startup that only allows changes during
+startup.
+
==============================================================================
3. Messages *messages*
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 1393b11824..13e01a8e3f 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -2217,21 +2217,10 @@ A jump table for the options with a short description can be found at |Q_op|.
viminfo file, etc. It sets the kind of characters which Vim can work
with. See |encoding-names| for the possible values.
- NOTE: Changing this option will not change the encoding of the
- existing text in Vim. It may cause non-ASCII text to become invalid.
- It should normally be kept at its default value, or set when Vim
- starts up. See |multibyte|. To reload the menus see |:menutrans|.
-
- This option cannot be set from a |modeline|. It would most likely
- corrupt the text.
-
- NOTE: For GTK+ 2 it is highly recommended to set 'encoding' to
- "utf-8". Although care has been taken to allow different values of
- 'encoding', "utf-8" is the natural choice for the environment and
- avoids unnecessary conversion overhead. "utf-8" has not been made
- the default to prevent different behavior of the GUI and terminal
- versions, and to avoid changing the encoding of newly created files
- without your knowledge (in case 'fileencodings' is empty).
+ 'encoding' cannot be changed after startup, because (1) it causes
+ non-ASCII text inside Vim to become invalid, and (2) it complicates
+ runtime logic. The recommended 'encoding' is "utf-8". Remote plugins
+ and GUIs only support utf-8. See |multibyte|.
The character encoding of files can be different from 'encoding'.
This is specified with 'fileencoding'. The conversion is done with
@@ -2242,7 +2231,8 @@ A jump table for the options with a short description can be found at |Q_op|.
if has("multi_byte_encoding")
<
Normally 'encoding' will be equal to your current locale. This will
- be the default if Vim recognizes your environment settings.
+ be the default if Vim recognizes your environment settings, otherwise
+ "utf-8" is used.
When you set this option, it fires the |EncodingChanged| autocommand
event so that you can set up fonts if necessary.
@@ -2254,12 +2244,6 @@ A jump table for the options with a short description can be found at |Q_op|.
For example "Latin-1" becomes "latin1", "ISO_88592" becomes
"iso-8859-2" and "utf8" becomes "utf-8".
- Note: "latin1" is also used when the encoding could not be detected.
- This only works when editing files in the same encoding! When the
- actual character set is not latin1, make sure 'fileencoding' and
- 'fileencodings' are empty. When conversion is needed, switch to using
- utf-8.
-
When "unicode", "ucs-2" or "ucs-4" is used, Vim internally uses utf-8.
You don't notice this while editing, but it does matter for the
|viminfo-file|. And Vim expects the terminal to use utf-8 too. Thus
diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt
index 8f88906e38..52146cd946 100644
--- a/runtime/doc/vim_diff.txt
+++ b/runtime/doc/vim_diff.txt
@@ -74,6 +74,8 @@ are always available and may be used simultaneously in separate plugins. The
'p')) mkdir() will silently exit. In Vim this was an error.
3. mkdir() error messages now include strerror() text when mkdir fails.
+'encoding' cannot be changed after startup.
+
==============================================================================
4. New Features *nvim-features-new*
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt
index 5e547ed290..ad2642ddd4 100644
--- a/src/nvim/CMakeLists.txt
+++ b/src/nvim/CMakeLists.txt
@@ -2,12 +2,15 @@ include(CheckLibraryExists)
option(USE_GCOV "Enable gcov support" OFF)
+if(NOT CLANG_TSAN)
+# GCOV and TSAN results in false data race reports
if(USE_GCOV)
message(STATUS "Enabling gcov support")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --coverage")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --coverage")
endif()
+endif()
set(GENERATED_DIR ${PROJECT_BINARY_DIR}/src/nvim/auto)
set(DISPATCH_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/msgpack-gen.lua)
@@ -138,9 +141,16 @@ foreach(sfile ${NEOVIM_SOURCES}
set(gf1 "${GENERATED_DIR}/${r}.c.generated.h")
set(gf2 "${GENERATED_INCLUDES_DIR}/${r}.h.generated.h")
set(gf3 "${GENERATED_DIR}/${r}.i")
+
+ if(MSVC)
+ set(PREPROC_OUTPUT /P /Fi${gf3})
+ else()
+ set(PREPROC_OUTPUT -E -o ${gf3})
+ endif()
+
add_custom_command(
OUTPUT "${gf1}" "${gf2}"
- COMMAND ${CMAKE_C_COMPILER} ${sfile} -o ${gf3} ${gen_cflags} -E ${C_FLAGS_ARRAY}
+ COMMAND ${CMAKE_C_COMPILER} ${sfile} ${PREPROC_OUTPUT} ${gen_cflags} ${C_FLAGS_ARRAY}
COMMAND "${LUA_PRG}" "${HEADER_GENERATOR}" "${sfile}" "${gf1}" "${gf2}" "${gf3}"
DEPENDS "${HEADER_GENERATOR}" "${sfile}"
)
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 12c97cfffb..ce3e64def8 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -467,7 +467,7 @@ void buffer_insert(Buffer buffer,
ArrayOf(String) lines,
Error *err)
{
- buffer_set_line_slice(buffer, lnum, lnum, false, true, lines, err);
+ buffer_set_line_slice(buffer, lnum, lnum, true, false, lines, err);
}
/// Return a tuple (row,col) representing the position of the named mark
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 02cc9b8f5b..bbe45480d2 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -5570,96 +5570,97 @@ static int list_join(garray_T *gap, list_T *l, char_u *sep, int echo_style, int
* http://python.ca/nas/python/gc/
*/
-/*
- * Do garbage collection for lists and dicts.
- * Return TRUE if some memory was freed.
- */
-int garbage_collect(void)
+/// Do garbage collection for lists and dicts.
+///
+/// @returns true if some memory was freed.
+bool garbage_collect(void)
{
- int copyID;
- funccall_T *fc, **pfc;
- int did_free;
- int did_free_funccal = FALSE;
+ bool abort = false;
- /* Only do this once. */
- want_garbage_collect = FALSE;
- may_garbage_collect = FALSE;
- garbage_collect_at_exit = FALSE;
+ // Only do this once.
+ want_garbage_collect = false;
+ may_garbage_collect = false;
+ garbage_collect_at_exit = false;
- /* We advance by two because we add one for items referenced through
- * previous_funccal. */
+ // We advance by two because we add one for items referenced through
+ // previous_funccal.
current_copyID += COPYID_INC;
- copyID = current_copyID;
+ int copyID = current_copyID;
- /*
- * 1. Go through all accessible variables and mark all lists and dicts
- * with copyID.
- */
+ // 1. Go through all accessible variables and mark all lists and dicts
+ // with copyID.
- /* Don't free variables in the previous_funccal list unless they are only
- * referenced through previous_funccal. This must be first, because if
- * the item is referenced elsewhere the funccal must not be freed. */
- for (fc = previous_funccal; fc != NULL; fc = fc->caller) {
- set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID + 1);
- set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID + 1);
+ // Don't free variables in the previous_funccal list unless they are only
+ // referenced through previous_funccal. This must be first, because if
+ // the item is referenced elsewhere the funccal must not be freed.
+ for (funccall_T *fc = previous_funccal; fc != NULL; fc = fc->caller) {
+ abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID + 1, NULL);
+ abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID + 1, NULL);
}
- /* script-local variables */
- for (int i = 1; i <= ga_scripts.ga_len; ++i)
- set_ref_in_ht(&SCRIPT_VARS(i), copyID);
+ // script-local variables
+ for (int i = 1; i <= ga_scripts.ga_len; ++i) {
+ abort = abort || set_ref_in_ht(&SCRIPT_VARS(i), copyID, NULL);
+ }
- /* buffer-local variables */
+ // buffer-local variables
FOR_ALL_BUFFERS(buf) {
- set_ref_in_item(&buf->b_bufvar.di_tv, copyID);
+ abort = abort || set_ref_in_item(&buf->b_bufvar.di_tv, copyID, NULL, NULL);
}
- /* window-local variables */
+ // window-local variables
FOR_ALL_TAB_WINDOWS(tp, wp) {
- set_ref_in_item(&wp->w_winvar.di_tv, copyID);
+ abort = abort || set_ref_in_item(&wp->w_winvar.di_tv, copyID, NULL, NULL);
+ }
+ if (aucmd_win != NULL) {
+ abort = abort ||
+ set_ref_in_item(&aucmd_win->w_winvar.di_tv, copyID, NULL, NULL);
}
- if (aucmd_win != NULL)
- set_ref_in_item(&aucmd_win->w_winvar.di_tv, copyID);
- /* tabpage-local variables */
+ // tabpage-local variables
FOR_ALL_TABS(tp) {
- set_ref_in_item(&tp->tp_winvar.di_tv, copyID);
+ abort = abort || set_ref_in_item(&tp->tp_winvar.di_tv, copyID, NULL, NULL);
}
- /* global variables */
- set_ref_in_ht(&globvarht, copyID);
+ // global variables
+ abort = abort || set_ref_in_ht(&globvarht, copyID, NULL);
- /* function-local variables */
- for (fc = current_funccal; fc != NULL; fc = fc->caller) {
- set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID);
- set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID);
+ // function-local variables
+ for (funccall_T *fc = current_funccal; fc != NULL; fc = fc->caller) {
+ abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL);
+ abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL);
}
- /* v: vars */
- set_ref_in_ht(&vimvarht, copyID);
+ // v: vars
+ abort = abort || set_ref_in_ht(&vimvarht, copyID, NULL);
- /*
- * 2. Free lists and dictionaries that are not referenced.
- */
- did_free = free_unref_items(copyID);
-
- /*
- * 3. Check if any funccal can be freed now.
- */
- for (pfc = &previous_funccal; *pfc != NULL; ) {
- if (can_free_funccal(*pfc, copyID)) {
- fc = *pfc;
- *pfc = fc->caller;
- free_funccal(fc, TRUE);
- did_free = TRUE;
- did_free_funccal = TRUE;
- } else
- pfc = &(*pfc)->caller;
+ bool did_free = false;
+ if (!abort) {
+ // 2. Free lists and dictionaries that are not referenced.
+ did_free = free_unref_items(copyID);
+
+ // 3. Check if any funccal can be freed now.
+ bool did_free_funccal = false;
+ for (funccall_T **pfc = &previous_funccal; *pfc != NULL;) {
+ if (can_free_funccal(*pfc, copyID)) {
+ funccall_T *fc = *pfc;
+ *pfc = fc->caller;
+ free_funccal(fc, true);
+ did_free = true;
+ did_free_funccal = true;
+ } else {
+ pfc = &(*pfc)->caller;
+ }
+ }
+ if (did_free_funccal) {
+ // When a funccal was freed some more items might be garbage
+ // collected, so run again.
+ (void)garbage_collect();
+ }
+ } else if (p_verbose > 0) {
+ verb_msg((char_u *)_(
+ "Not enough memory to set references, garbage collection aborted!"));
}
- if (did_free_funccal)
- /* When a funccal was freed some more items might be garbage
- * collected, so run again. */
- (void)garbage_collect();
-
return did_free;
}
@@ -5711,61 +5712,143 @@ static int free_unref_items(int copyID)
return did_free;
}
-/*
- * Mark all lists and dicts referenced through hashtab "ht" with "copyID".
- */
-void set_ref_in_ht(hashtab_T *ht, int copyID)
-{
- int todo;
- hashitem_T *hi;
+/// Mark all lists and dicts referenced through hashtab "ht" with "copyID".
+///
+/// @param ht Hashtab content will be marked.
+/// @param copyID New mark for lists and dicts.
+/// @param list_stack Used to add lists to be marked. Can be NULL.
+///
+/// @returns true if setting references failed somehow.
+bool set_ref_in_ht(hashtab_T *ht, int copyID, list_stack_T **list_stack)
+{
+ bool abort = false;
+ ht_stack_T *ht_stack = NULL;
+
+ hashtab_T *cur_ht = ht;
+ for (;;) {
+ if (!abort) {
+ // Mark each item in the hashtab. If the item contains a hashtab
+ // it is added to ht_stack, if it contains a list it is added to
+ // list_stack.
+ int todo = (int)cur_ht->ht_used;
+ for (hashitem_T *hi = cur_ht->ht_array; todo > 0; ++hi) {
+ if (!HASHITEM_EMPTY(hi)) {
+ --todo;
+ abort = abort || set_ref_in_item(&HI2DI(hi)->di_tv, copyID, &ht_stack,
+ list_stack);
+ }
+ }
+ }
- todo = (int)ht->ht_used;
- for (hi = ht->ht_array; todo > 0; ++hi)
- if (!HASHITEM_EMPTY(hi)) {
- --todo;
- set_ref_in_item(&HI2DI(hi)->di_tv, copyID);
+ if (ht_stack == NULL) {
+ break;
}
+
+ // take an item from the stack
+ cur_ht = ht_stack->ht;
+ ht_stack_T *tempitem = ht_stack;
+ ht_stack = ht_stack->prev;
+ xfree(tempitem);
+ }
+
+ return abort;
}
-/*
- * Mark all lists and dicts referenced through list "l" with "copyID".
- */
-void set_ref_in_list(list_T *l, int copyID)
-{
- listitem_T *li;
+/// Mark all lists and dicts referenced through list "l" with "copyID".
+///
+/// @param l List content will be marked.
+/// @param copyID New mark for lists and dicts.
+/// @param ht_stack Used to add hashtabs to be marked. Can be NULL.
+///
+/// @returns true if setting references failed somehow.
+bool set_ref_in_list(list_T *l, int copyID, ht_stack_T **ht_stack)
+{
+ bool abort = false;
+ list_stack_T *list_stack = NULL;
+
+ list_T *cur_l = l;
+ for (;;) {
+ if (!abort) {
+ // Mark each item in the list. If the item contains a hashtab
+ // it is added to ht_stack, if it contains a list it is added to
+ // list_stack.
+ for (listitem_T *li = cur_l->lv_first; !abort && li != NULL;
+ li = li->li_next) {
+ abort = set_ref_in_item(&li->li_tv, copyID, ht_stack, &list_stack);
+ }
+ }
- for (li = l->lv_first; li != NULL; li = li->li_next)
- set_ref_in_item(&li->li_tv, copyID);
+ if (list_stack == NULL) {
+ break;
+ }
+
+ // take an item from the stack
+ cur_l = list_stack->list;
+ list_stack_T *tempitem = list_stack;
+ list_stack = list_stack->prev;
+ xfree(tempitem);
+ }
+
+ return abort;
}
-/*
- * Mark all lists and dicts referenced through typval "tv" with "copyID".
- */
-void set_ref_in_item(typval_T *tv, int copyID)
+/// Mark all lists and dicts referenced through typval "tv" with "copyID".
+///
+/// @param tv Typval content will be marked.
+/// @param copyID New mark for lists and dicts.
+/// @param ht_stack Used to add hashtabs to be marked. Can be NULL.
+/// @param list_stack Used to add lists to be marked. Can be NULL.
+///
+/// @returns true if setting references failed somehow.
+bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack,
+ list_stack_T **list_stack)
{
- dict_T *dd;
- list_T *ll;
+ bool abort = false;
switch (tv->v_type) {
- case VAR_DICT:
- dd = tv->vval.v_dict;
- if (dd != NULL && dd->dv_copyID != copyID) {
- /* Didn't see this dict yet. */
- dd->dv_copyID = copyID;
- set_ref_in_ht(&dd->dv_hashtab, copyID);
+ case VAR_DICT: {
+ dict_T *dd = tv->vval.v_dict;
+ if (dd != NULL && dd->dv_copyID != copyID) {
+ // Didn't see this dict yet.
+ dd->dv_copyID = copyID;
+ if (ht_stack == NULL) {
+ abort = set_ref_in_ht(&dd->dv_hashtab, copyID, list_stack);
+ } else {
+ ht_stack_T *newitem = try_malloc(sizeof(ht_stack_T));
+ if (newitem == NULL) {
+ abort = true;
+ } else {
+ newitem->ht = &dd->dv_hashtab;
+ newitem->prev = *ht_stack;
+ *ht_stack = newitem;
+ }
+ }
+ }
+ break;
}
- break;
- case VAR_LIST:
- ll = tv->vval.v_list;
- if (ll != NULL && ll->lv_copyID != copyID) {
- /* Didn't see this list yet. */
- ll->lv_copyID = copyID;
- set_ref_in_list(ll, copyID);
+ case VAR_LIST: {
+ list_T *ll = tv->vval.v_list;
+ if (ll != NULL && ll->lv_copyID != copyID) {
+ // Didn't see this list yet.
+ ll->lv_copyID = copyID;
+ if (list_stack == NULL) {
+ abort = set_ref_in_list(ll, copyID, ht_stack);
+ } else {
+ list_stack_T *newitem = try_malloc(sizeof(list_stack_T));
+ if (newitem == NULL) {
+ abort = true;
+ } else {
+ newitem->list = ll;
+ newitem->prev = *list_stack;
+ *list_stack = newitem;
+ }
+ }
+ }
+ break;
}
- break;
}
- return;
+ return abort;
}
/*
diff --git a/src/nvim/eval_defs.h b/src/nvim/eval_defs.h
index 0faf860588..a8a8acd048 100644
--- a/src/nvim/eval_defs.h
+++ b/src/nvim/eval_defs.h
@@ -120,4 +120,16 @@ struct dictvar_S {
// prevent garbage collection
};
-#endif // NVIM_EVAL_DEFS_H
+// structure used for explicit stack while garbage collecting hash tables
+typedef struct ht_stack_S {
+ hashtab_T *ht;
+ struct ht_stack_S *prev;
+} ht_stack_T;
+
+// structure used for explicit stack while garbage collecting lists
+typedef struct list_stack_S {
+ list_T *list;
+ struct list_stack_S *prev;
+} list_stack_T;
+
+#endif // NVIM_EVAL_DEFS_H
diff --git a/src/nvim/event/defs.h b/src/nvim/event/defs.h
index 5126d52241..b802866a3d 100644
--- a/src/nvim/event/defs.h
+++ b/src/nvim/event/defs.h
@@ -4,7 +4,7 @@
#include <assert.h>
#include <stdarg.h>
-#define EVENT_HANDLER_MAX_ARGC 4
+#define EVENT_HANDLER_MAX_ARGC 6
typedef void (*argv_callback)(void **argv);
typedef struct message {
@@ -12,6 +12,7 @@ typedef struct message {
argv_callback handler;
void *argv[EVENT_HANDLER_MAX_ARGC];
} Event;
+typedef void(*event_scheduler)(Event event, void *data);
#define VA_EVENT_INIT(event, p, h, a) \
do { \
diff --git a/src/nvim/event/libuv_process.c b/src/nvim/event/libuv_process.c
index e0a108a6e4..44305c69bc 100644
--- a/src/nvim/event/libuv_process.c
+++ b/src/nvim/event/libuv_process.c
@@ -19,7 +19,8 @@ bool libuv_process_spawn(LibuvProcess *uvproc)
Process *proc = (Process *)uvproc;
uvproc->uvopts.file = proc->argv[0];
uvproc->uvopts.args = proc->argv;
- uvproc->uvopts.flags = UV_PROCESS_WINDOWS_HIDE;
+ uvproc->uvopts.flags = UV_PROCESS_WINDOWS_HIDE
+ | UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS;
uvproc->uvopts.exit_cb = exit_cb;
uvproc->uvopts.cwd = NULL;
uvproc->uvopts.env = NULL;
diff --git a/src/nvim/event/loop.c b/src/nvim/event/loop.c
index 3d3288f858..088e059d19 100644
--- a/src/nvim/event/loop.c
+++ b/src/nvim/event/loop.c
@@ -10,20 +10,19 @@
# include "event/loop.c.generated.h"
#endif
-typedef struct idle_event {
- uv_idle_t idle;
- Event event;
-} IdleEvent;
-
void loop_init(Loop *loop, void *data)
{
uv_loop_init(&loop->uv);
+ loop->recursive = 0;
loop->uv.data = loop;
loop->children = kl_init(WatcherPtr);
loop->children_stop_requests = 0;
loop->events = queue_new_parent(loop_on_put, loop);
loop->fast_events = queue_new_child(loop->events);
+ loop->thread_events = queue_new_parent(NULL, NULL);
+ uv_mutex_init(&loop->mutex);
+ uv_async_init(&loop->uv, &loop->async, async_cb);
uv_signal_init(&loop->uv, &loop->children_watcher);
uv_timer_init(&loop->uv, &loop->children_kill_timer);
uv_timer_init(&loop->uv, &loop->poll_timer);
@@ -31,9 +30,7 @@ void loop_init(Loop *loop, void *data)
void loop_poll_events(Loop *loop, int ms)
{
- static int recursive = 0;
-
- if (recursive++) {
+ if (loop->recursive++) {
abort(); // Should not re-enter uv_run
}
@@ -55,10 +52,19 @@ void loop_poll_events(Loop *loop, int ms)
uv_timer_stop(&loop->poll_timer);
}
- recursive--; // Can re-enter uv_run now
+ loop->recursive--; // Can re-enter uv_run now
queue_process_events(loop->fast_events);
}
+// Schedule an event from another thread
+void loop_schedule(Loop *loop, Event event)
+{
+ uv_mutex_lock(&loop->mutex);
+ queue_put_event(loop->thread_events, event);
+ uv_async_send(&loop->async);
+ uv_mutex_unlock(&loop->mutex);
+}
+
void loop_on_put(Queue *queue, void *data)
{
Loop *loop = data;
@@ -72,14 +78,32 @@ void loop_on_put(Queue *queue, void *data)
void loop_close(Loop *loop)
{
+ uv_mutex_destroy(&loop->mutex);
uv_close((uv_handle_t *)&loop->children_watcher, NULL);
uv_close((uv_handle_t *)&loop->children_kill_timer, NULL);
uv_close((uv_handle_t *)&loop->poll_timer, NULL);
+ uv_close((uv_handle_t *)&loop->async, NULL);
do {
uv_run(&loop->uv, UV_RUN_DEFAULT);
} while (uv_loop_close(&loop->uv));
+ queue_free(loop->events);
+ queue_free(loop->fast_events);
+ queue_free(loop->thread_events);
+ kl_destroy(WatcherPtr, loop->children);
+}
+
+static void async_cb(uv_async_t *handle)
+{
+ Loop *l = handle->loop->data;
+ uv_mutex_lock(&l->mutex);
+ while (!queue_empty(l->thread_events)) {
+ Event ev = queue_get(l->thread_events);
+ queue_put_event(l->fast_events, ev);
+ }
+ uv_mutex_unlock(&l->mutex);
}
static void timer_cb(uv_timer_t *handle)
{
}
+
diff --git a/src/nvim/event/loop.h b/src/nvim/event/loop.h
index 9212a45aa4..0c1fcb5ed9 100644
--- a/src/nvim/event/loop.h
+++ b/src/nvim/event/loop.h
@@ -16,11 +16,14 @@ KLIST_INIT(WatcherPtr, WatcherPtr, _noop)
typedef struct loop {
uv_loop_t uv;
- Queue *events, *fast_events;
+ Queue *events, *fast_events, *thread_events;
klist_t(WatcherPtr) *children;
uv_signal_t children_watcher;
uv_timer_t children_kill_timer, poll_timer;
size_t children_stop_requests;
+ uv_async_t async;
+ uv_mutex_t mutex;
+ int recursive;
} Loop;
#define CREATE_EVENT(queue, handler, argc, ...) \
diff --git a/src/nvim/event/queue.c b/src/nvim/event/queue.c
index 19eca14144..c5ef22d426 100644
--- a/src/nvim/event/queue.c
+++ b/src/nvim/event/queue.c
@@ -105,16 +105,15 @@ static Queue *queue_new(Queue *parent, put_callback put_cb, void *data)
void queue_free(Queue *queue)
{
assert(queue);
- if (queue->parent) {
- while (!QUEUE_EMPTY(&queue->headtail)) {
- QUEUE *q = QUEUE_HEAD(&queue->headtail);
- QueueItem *item = queue_node_data(q);
- assert(!item->link);
+ while (!QUEUE_EMPTY(&queue->headtail)) {
+ QUEUE *q = QUEUE_HEAD(&queue->headtail);
+ QueueItem *item = queue_node_data(q);
+ if (queue->parent) {
QUEUE_REMOVE(&item->data.item.parent->node);
xfree(item->data.item.parent);
- QUEUE_REMOVE(q);
- xfree(item);
}
+ QUEUE_REMOVE(q);
+ xfree(item);
}
xfree(queue);
@@ -128,9 +127,8 @@ Event queue_get(Queue *queue)
void queue_put_event(Queue *queue, Event event)
{
assert(queue);
- assert(queue->parent); // don't push directly to the parent queue
queue_push(queue, event);
- if (queue->parent->put_cb) {
+ if (queue->parent && queue->parent->put_cb) {
queue->parent->put_cb(queue->parent, queue->parent->data);
}
}
@@ -177,11 +175,11 @@ static Event queue_remove(Queue *queue)
rv = child->data.item.event;
xfree(child);
} else {
- assert(queue->parent);
- assert(!queue_empty(queue->parent));
- // remove the corresponding link node in the parent queue
- QUEUE_REMOVE(&item->data.item.parent->node);
- xfree(item->data.item.parent);
+ if (queue->parent) {
+ // remove the corresponding link node in the parent queue
+ QUEUE_REMOVE(&item->data.item.parent->node);
+ xfree(item->data.item.parent);
+ }
rv = item->data.item.event;
}
@@ -195,11 +193,13 @@ static void queue_push(Queue *queue, Event event)
item->link = false;
item->data.item.event = event;
QUEUE_INSERT_TAIL(&queue->headtail, &item->node);
- // push link node to the parent queue
- item->data.item.parent = xmalloc(sizeof(QueueItem));
- item->data.item.parent->link = true;
- item->data.item.parent->data.queue = queue;
- QUEUE_INSERT_TAIL(&queue->parent->headtail, &item->data.item.parent->node);
+ if (queue->parent) {
+ // push link node to the parent queue
+ item->data.item.parent = xmalloc(sizeof(QueueItem));
+ item->data.item.parent->link = true;
+ item->data.item.parent->data.queue = queue;
+ QUEUE_INSERT_TAIL(&queue->parent->headtail, &item->data.item.parent->node);
+ }
}
static QueueItem *queue_node_data(QUEUE *q)
diff --git a/src/nvim/event/stream.c b/src/nvim/event/stream.c
index 6caad6fdcc..376eb9fce7 100644
--- a/src/nvim/event/stream.c
+++ b/src/nvim/event/stream.c
@@ -56,7 +56,6 @@ void stream_init(Loop *loop, Stream *stream, int fd, uv_stream_t *uvstream,
if (stream->uvstream) {
stream->uvstream->data = stream;
- loop = stream->uvstream->loop->data;
}
stream->data = data;
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index e5a993aa5a..183e8fcff2 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -633,6 +633,10 @@ EXTERN int silent_mode INIT(= FALSE);
/* set to TRUE when "-s" commandline argument
* used for ex */
+// Set to true when sourcing of startup scripts (nvimrc) is done.
+// Used for options that cannot be changed after startup scripts.
+EXTERN bool did_source_startup_scripts INIT(= false);
+
EXTERN pos_T VIsual; /* start position of active Visual selection */
EXTERN int VIsual_active INIT(= FALSE);
/* whether Visual mode is active */
@@ -1078,6 +1082,8 @@ EXTERN garray_T error_ga
* Excluded are errors that are only used once and debugging messages.
*/
EXTERN char_u e_abort[] INIT(= N_("E470: Command aborted"));
+EXTERN char_u e_afterinit[] INIT(= N_(
+ "E905: Cannot set this option after startup"));
EXTERN char_u e_api_spawn_failed[] INIT(= N_("E903: Could not spawn API job"));
EXTERN char_u e_argreq[] INIT(= N_("E471: Argument required"));
EXTERN char_u e_backslash[] INIT(= N_("E10: \\ should be followed by /, ? or &"));
diff --git a/src/nvim/log.c b/src/nvim/log.c
index 3575f49e78..08b6d0483e 100644
--- a/src/nvim/log.c
+++ b/src/nvim/log.c
@@ -16,29 +16,49 @@
#define USR_LOG_FILE "$HOME/.nvimlog"
+static uv_mutex_t mutex;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "log.c.generated.h"
#endif
+void log_init(void)
+{
+ uv_mutex_init(&mutex);
+}
+
+void log_lock(void)
+{
+ uv_mutex_lock(&mutex);
+}
+
+void log_unlock(void)
+{
+ uv_mutex_unlock(&mutex);
+}
+
bool do_log(int log_level, const char *func_name, int line_num, bool eol,
const char* fmt, ...) FUNC_ATTR_UNUSED
{
+ log_lock();
+ bool ret = false;
FILE *log_file = open_log_file();
if (log_file == NULL) {
- return false;
+ goto end;
}
va_list args;
va_start(args, fmt);
- bool ret = v_do_log_to_file(log_file, log_level, func_name, line_num, eol,
+ ret = v_do_log_to_file(log_file, log_level, func_name, line_num, eol,
fmt, args);
va_end(args);
if (log_file != stderr && log_file != stdout) {
fclose(log_file);
}
+end:
+ log_unlock();
return ret;
}
diff --git a/src/nvim/main.c b/src/nvim/main.c
index 3e096ee190..27f8340ec7 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -177,6 +177,7 @@ void event_teardown(void)
/// Needed for unit tests. Must be called after `time_init()`.
void early_init(void)
{
+ log_init();
fs_init();
handle_init();
@@ -1910,6 +1911,7 @@ static void source_startup_scripts(mparm_T *parmp)
need_wait_return = TRUE;
secure = 0;
}
+ did_source_startup_scripts = true;
TIME_MSG("sourcing vimrc file(s)");
}
diff --git a/src/nvim/move.c b/src/nvim/move.c
index 2a1f722e95..3831004703 100644
--- a/src/nvim/move.c
+++ b/src/nvim/move.c
@@ -1317,7 +1317,9 @@ void scroll_cursor_top(int min_scroll, int always)
* set new_topline and advance "top" and "bot" to include more lines.
*/
while (top > 0) {
- int i = (hasFolding(top, &top, NULL)) ? 1 : plines(top);
+ int i = hasFolding(top, &top, NULL)
+ ? 1 // count one logical line for a sequence of folded lines
+ : plines(top);
used += i;
if (extra + i <= off && bot < curbuf->b_ml.ml_line_count) {
if (hasFolding(bot, NULL, &bot))
diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c
index 45e78d6e79..34ff7c6374 100644
--- a/src/nvim/msgpack_rpc/channel.c
+++ b/src/nvim/msgpack_rpc/channel.c
@@ -825,6 +825,7 @@ static void log_server_msg(uint64_t channel_id,
msgpack_unpack_next(&unpacked, packed->data, packed->size, NULL);
uint64_t type = unpacked.data.via.array.ptr[0].via.u64;
DLOGN("[msgpack-rpc] nvim -> client(%" PRIu64 ") ", channel_id);
+ log_lock();
FILE *f = open_log_file();
fprintf(f, type ? (type == 1 ? RES : NOT) : REQ);
log_msg_close(f, unpacked.data);
@@ -836,6 +837,7 @@ static void log_client_msg(uint64_t channel_id,
msgpack_object msg)
{
DLOGN("[msgpack-rpc] client(%" PRIu64 ") -> nvim ", channel_id);
+ log_lock();
FILE *f = open_log_file();
fprintf(f, is_request ? REQ : RES);
log_msg_close(f, msg);
@@ -847,6 +849,7 @@ static void log_msg_close(FILE *f, msgpack_object msg)
fputc('\n', f);
fflush(f);
fclose(f);
+ log_unlock();
}
#endif
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 6e82c45edf..c47616620c 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -575,6 +575,12 @@ void set_init_1(void)
mb_init();
}
+ // Don't change &encoding when resetting to defaults with ":set all&".
+ opt_idx = findoption((char_u *)"encoding");
+ if (opt_idx >= 0) {
+ options[opt_idx].flags |= P_NODEFAULT;
+ }
+
/* Set the default for 'helplang'. */
set_helplang_default(get_mess_lang());
}
@@ -2271,10 +2277,11 @@ did_set_string_option (
else if (varp == &p_ei) {
if (check_ei() == FAIL)
errmsg = e_invarg;
- }
/* 'encoding' and 'fileencoding' */
- else if (varp == &p_enc || gvarp == &p_fenc) {
- if (gvarp == &p_fenc) {
+ } else if (varp == &p_enc || gvarp == &p_fenc) {
+ if (varp == &p_enc && did_source_startup_scripts) {
+ errmsg = e_afterinit;
+ } else if (gvarp == &p_fenc) {
if (!MODIFIABLE(curbuf) && opt_flags != OPT_GLOBAL)
errmsg = e_modifiable;
else if (vim_strchr(*varp, ',') != NULL)
@@ -2305,10 +2312,6 @@ did_set_string_option (
* (with another encoding). */
if (varp == &p_enc && *curbuf->b_p_keymap != NUL)
(void)keymap_init();
-
- if (varp == &p_enc) {
- ui_update_encoding();
- }
}
} else if (varp == &p_penc) {
/* Canonize printencoding if VIM standard one */
diff --git a/src/nvim/testdir/test10.in b/src/nvim/testdir/test10.in
index 2d0d546606..769d690acb 100644
--- a/src/nvim/testdir/test10.in
+++ b/src/nvim/testdir/test10.in
@@ -4,7 +4,6 @@ STARTTEST
:so small.vim
:" Also test a BOM is ignored.
:so mbyte.vim
-:set encoding=utf-8
:7/start of errorfile/,/end of errorfile/w! Xerrorfile1
:7/start of errorfile/,/end of errorfile/-1w! Xerrorfile2
:/start of testfile/,/end of testfile/w! Xtestfile
diff --git a/src/nvim/testdir/test39.in b/src/nvim/testdir/test39.in
index c4e46fff26..ebbcbd6d0d 100644
--- a/src/nvim/testdir/test39.in
+++ b/src/nvim/testdir/test39.in
@@ -5,8 +5,6 @@ And test "U" in Visual mode, also on German sharp S.
STARTTEST
:so small.vim
:so mbyte.vim
-:" This only works when 'encoding' is "latin1", don't depend on the environment
-:set enc=latin1
/^abcde
:" Test shift-right of a block
jlllljj>wlljlll>
@@ -39,18 +37,18 @@ G$khhhhhkkcmno
:exe ":norm! l\<C-V>j$hhAab\<Esc>"
:.,/^$/w >> test.out
:" Test for Visual block insert when virtualedit=all and utf-8 encoding
-:set ve=all enc=utf-8
+:set ve=all
:/\t\tline
:exe ":norm! 07l\<C-V>jjIx\<Esc>"
:.,/^$/w >> test.out
:" Test for Visual block append when virtualedit=all
:exe ":norm! 012l\<C-v>jjAx\<Esc>"
-:set ve= enc=latin1
+:set ve=
:.,/^$/w >> test.out
-:" gUe must uppercase a whole word, also when changes to SS
-Gothe youtueuu endYpk0wgUe
+:" gUe must uppercase a whole word, also when ß changes to SS
+Gothe youtußeuu endYpk0wgUe
:" gUfx must uppercase until x, inclusive.
-O- youtuexu -0fogUfx
+O- youßtußexu -0fogUfx
:" VU must uppercase a whole line
YpkVU
:" same, when it's the last line in the buffer
@@ -89,7 +87,7 @@ cccc
dddd
yaaa
-
+¿¿¿
bbb
A23
diff --git a/src/nvim/testdir/test39.ok b/src/nvim/testdir/test39.ok
index 5c517e2223..198e5b14dc 100644
--- a/src/nvim/testdir/test39.ok
+++ b/src/nvim/testdir/test39.ok
Binary files differ
diff --git a/src/nvim/testdir/test69.in b/src/nvim/testdir/test69.in
index 26f41e8a29..f583947dfb 100644
--- a/src/nvim/testdir/test69.in
+++ b/src/nvim/testdir/test69.in
@@ -5,7 +5,6 @@ Also test byteidx() and byteidxcomp()
STARTTEST
:so mbyte.vim
-:set encoding=utf-8
ENDTEST
Results of test69:
diff --git a/src/nvim/testdir/test83.in b/src/nvim/testdir/test83.in
index 297d560d2f..d54b1bcddd 100644
--- a/src/nvim/testdir/test83.in
+++ b/src/nvim/testdir/test83.in
@@ -2,7 +2,6 @@ Tests for tag search with !_TAG_FILE_ENCODING.
STARTTEST
:so mbyte.vim
-:set enc=utf8
:if !has('iconv') || iconv("\x82\x60", "cp932", "utf-8") != "\uff21"
: e! test.ok
: w! test.out
diff --git a/src/nvim/testdir/test_eval.in b/src/nvim/testdir/test_eval.in
index 328ee2e127..b2b982a434 100644
--- a/src/nvim/testdir/test_eval.in
+++ b/src/nvim/testdir/test_eval.in
@@ -4,7 +4,6 @@ Note: system clipboard is saved, changed and restored.
STARTTEST
:so small.vim
-:set encoding=latin1
:set noswapfile
:lang C
:fun AppendRegContents(reg)
diff --git a/src/nvim/testdir/unix.vim b/src/nvim/testdir/unix.vim
index f766e74c30..aa1f6a92bc 100644
--- a/src/nvim/testdir/unix.vim
+++ b/src/nvim/testdir/unix.vim
@@ -1,3 +1,6 @@
" Settings for test script execution
" Always use "sh", don't use the value of "$SHELL".
set shell=sh
+
+" Don't depend on system locale, always use utf-8
+set encoding=utf-8
diff --git a/src/nvim/tui/term_input.inl b/src/nvim/tui/input.c
index c396557160..6c362540d0 100644
--- a/src/nvim/tui/term_input.inl
+++ b/src/nvim/tui/input.c
@@ -1,21 +1,81 @@
-#include <termkey.h>
+#include "nvim/tui/input.h"
+#include "nvim/vim.h"
+#include "nvim/api/vim.h"
+#include "nvim/api/private/helpers.h"
#include "nvim/ascii.h"
#include "nvim/misc2.h"
#include "nvim/os/os.h"
#include "nvim/os/input.h"
#include "nvim/event/rstream.h"
-#include "nvim/event/time.h"
#define PASTETOGGLE_KEY "<f37>"
-struct term_input {
- int in_fd;
- bool paste_enabled;
- TermKey *tk;
- TimeWatcher timer_handle;
- Stream read_stream;
-};
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "tui/input.c.generated.h"
+#endif
+
+void term_input_init(TermInput *input, Loop *loop)
+{
+ input->loop = loop;
+ input->paste_enabled = false;
+ input->in_fd = 0;
+
+ const char *term = os_getenv("TERM");
+ if (!term) {
+ term = ""; // termkey_new_abstract assumes non-null (#2745)
+ }
+ int enc_flag = enc_utf8 ? TERMKEY_FLAG_UTF8 : TERMKEY_FLAG_RAW;
+ input->tk = termkey_new_abstract(term, enc_flag);
+
+ int curflags = termkey_get_canonflags(input->tk);
+ termkey_set_canonflags(input->tk, curflags | TERMKEY_CANON_DELBS);
+ // setup input handle
+ rstream_init_fd(loop, &input->read_stream, input->in_fd, 0xfff, input);
+ // initialize a timer handle for handling ESC with libtermkey
+ time_watcher_init(loop, &input->timer_handle, input);
+ // Set the pastetoggle option to a special key that will be sent when
+ // \e[20{0,1}~/ are received
+ Error err = ERROR_INIT;
+ vim_set_option(cstr_as_string("pastetoggle"),
+ STRING_OBJ(cstr_as_string(PASTETOGGLE_KEY)), &err);
+}
+
+void term_input_destroy(TermInput *input)
+{
+ time_watcher_close(&input->timer_handle, NULL);
+ stream_close(&input->read_stream, NULL);
+ termkey_destroy(input->tk);
+}
+
+void term_input_start(TermInput *input)
+{
+ rstream_start(&input->read_stream, read_cb);
+}
+
+void term_input_stop(TermInput *input)
+{
+ rstream_stop(&input->read_stream);
+ time_watcher_stop(&input->timer_handle);
+}
+
+static void input_enqueue_event(void **argv)
+{
+ char *buf = argv[0];
+ input_enqueue(cstr_as_string(buf));
+ xfree(buf);
+}
+
+static void input_done_event(void **argv)
+{
+ input_done();
+}
+
+static void enqueue_input(char *buf, size_t size)
+{
+ loop_schedule(&loop, event_create(1, input_enqueue_event, 1,
+ xstrndup(buf, size)));
+}
static void forward_simple_utf8(TermKeyKey *key)
{
@@ -33,7 +93,7 @@ static void forward_simple_utf8(TermKeyKey *key)
}
buf[len] = 0;
- input_enqueue((String){.data = buf, .size = len});
+ enqueue_input(buf, len);
}
static void forward_modified_utf8(TermKey *tk, TermKeyKey *key)
@@ -48,7 +108,7 @@ static void forward_modified_utf8(TermKey *tk, TermKeyKey *key)
len = termkey_strfkey(tk, buf, sizeof(buf), key, TERMKEY_FORMAT_VIM);
}
- input_enqueue((String){.data = buf, .size = len});
+ enqueue_input(buf, len);
}
static void forward_mouse_event(TermKey *tk, TermKeyKey *key)
@@ -99,7 +159,7 @@ static void forward_mouse_event(TermKey *tk, TermKeyKey *key)
}
len += (size_t)snprintf(buf + len, sizeof(buf) - len, "><%d,%d>", col, row);
- input_enqueue((String){.data = buf, .size = len});
+ enqueue_input(buf, len);
}
static TermKeyResult tk_getkey(TermKey *tk, TermKeyKey *key, bool force)
@@ -175,16 +235,16 @@ static bool handle_bracketed_paste(TermInput *input)
int state = get_real_state();
if (state & NORMAL) {
// Enter insert mode
- input_enqueue(cstr_as_string("i"));
+ enqueue_input("i", 1);
} else if (state & VISUAL) {
// Remove the selected text and enter insert mode
- input_enqueue(cstr_as_string("c"));
+ enqueue_input("c", 1);
} else if (!(state & INSERT)) {
// Don't mess with the paste option
return true;
}
}
- input_enqueue(cstr_as_string(PASTETOGGLE_KEY));
+ enqueue_input(PASTETOGGLE_KEY, sizeof(PASTETOGGLE_KEY) - 1);
input->paste_enabled = enable;
return true;
}
@@ -227,9 +287,9 @@ static void read_cb(Stream *stream, RBuffer *buf, size_t c, void *data,
// ls *.md | xargs nvim
input->in_fd = 2;
stream_close(&input->read_stream, NULL);
- queue_put(loop.fast_events, restart_reading, 1, input);
+ queue_put(input->loop->fast_events, restart_reading, 1, input);
} else {
- input_done();
+ loop_schedule(&loop, event_create(1, input_done_event, 0));
}
return;
}
@@ -275,51 +335,6 @@ static void read_cb(Stream *stream, RBuffer *buf, size_t c, void *data,
static void restart_reading(void **argv)
{
TermInput *input = argv[0];
- rstream_init_fd(&loop, &input->read_stream, input->in_fd, 0xfff, input);
+ rstream_init_fd(input->loop, &input->read_stream, input->in_fd, 0xfff, input);
rstream_start(&input->read_stream, read_cb);
}
-
-static TermInput *term_input_new(void)
-{
- TermInput *rv = xmalloc(sizeof(TermInput));
- rv->paste_enabled = false;
- rv->in_fd = 0;
-
- const char *term = os_getenv("TERM");
- if (!term) {
- term = ""; // termkey_new_abstract assumes non-null (#2745)
- }
- rv->tk = termkey_new_abstract(term, 0);
- int curflags = termkey_get_canonflags(rv->tk);
- termkey_set_canonflags(rv->tk, curflags | TERMKEY_CANON_DELBS);
- // setup input handle
- rstream_init_fd(&loop, &rv->read_stream, rv->in_fd, 0xfff, rv);
- rstream_start(&rv->read_stream, read_cb);
- // initialize a timer handle for handling ESC with libtermkey
- time_watcher_init(&loop, &rv->timer_handle, rv);
- // Set the pastetoggle option to a special key that will be sent when
- // \e[20{0,1}~/ are received
- Error err = ERROR_INIT;
- vim_set_option(cstr_as_string("pastetoggle"),
- STRING_OBJ(cstr_as_string(PASTETOGGLE_KEY)), &err);
- return rv;
-}
-
-static void term_input_destroy(TermInput *input)
-{
- time_watcher_stop(&input->timer_handle);
- time_watcher_close(&input->timer_handle, NULL);
- rstream_stop(&input->read_stream);
- stream_close(&input->read_stream, NULL);
- termkey_destroy(input->tk);
- // Run once to remove references to input/timer handles
- loop_poll_events(&loop, 0);
- xfree(input);
-}
-
-static void term_input_set_encoding(TermInput *input, char* enc)
-{
- int enc_flag = strcmp(enc, "utf-8") == 0 ? TERMKEY_FLAG_UTF8
- : TERMKEY_FLAG_RAW;
- termkey_set_flags(input->tk, enc_flag);
-}
diff --git a/src/nvim/tui/input.h b/src/nvim/tui/input.h
new file mode 100644
index 0000000000..033f53b4e2
--- /dev/null
+++ b/src/nvim/tui/input.h
@@ -0,0 +1,23 @@
+#ifndef NVIM_TUI_INPUT_H
+#define NVIM_TUI_INPUT_H
+
+#include <stdbool.h>
+
+#include <termkey.h>
+#include "nvim/event/stream.h"
+#include "nvim/event/time.h"
+
+typedef struct term_input {
+ int in_fd;
+ bool paste_enabled;
+ TermKey *tk;
+ TimeWatcher timer_handle;
+ Loop *loop;
+ Stream read_stream;
+} TermInput;
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "tui/input.h.generated.h"
+#endif
+
+#endif // NVIM_TUI_INPUT_H
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index acc2ccc682..4c8e88c383 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -17,46 +17,45 @@
#include "nvim/event/loop.h"
#include "nvim/event/signal.h"
#include "nvim/tui/tui.h"
+#include "nvim/tui/input.h"
+#include "nvim/os/input.h"
+#include "nvim/os/os.h"
#include "nvim/strings.h"
+#include "nvim/ugrid.h"
+#include "nvim/ui_bridge.h"
// Space reserved in the output buffer to restore the cursor to normal when
// flushing. No existing terminal will require 32 bytes to do that.
#define CNORM_COMMAND_MAX_SIZE 32
#define OUTBUF_SIZE 0xffff
-typedef struct term_input TermInput;
-
-#include "term_input.inl"
-
typedef struct {
int top, bot, left, right;
} Rect;
typedef struct {
- char data[7];
- HlAttrs attrs;
-} Cell;
-
-typedef struct {
+ UIBridgeData *bridge;
+ Loop *loop;
+ bool stop;
unibi_var_t params[9];
char buf[OUTBUF_SIZE];
size_t bufpos, bufsize;
- TermInput *input;
- uv_loop_t *write_loop;
+ TermInput input;
+ uv_loop_t write_loop;
unibi_term *ut;
uv_tty_t output_handle;
- SignalWatcher winch_handle;
- Rect scroll_region;
+ SignalWatcher winch_handle, cont_handle;
+ bool cont_received;
+ // Event scheduled by the ui bridge. Since the main thread suspends until
+ // the event is handled, it is fine to use a single field instead of a queue
+ Event scheduled_event;
+ UGrid grid;
kvec_t(Rect) invalid_regions;
- int row, col;
- int bg, fg;
int out_fd;
- int old_height;
bool can_use_terminal_scroll;
bool mouse_enabled;
bool busy;
- HlAttrs attrs, print_attrs;
- Cell **screen;
+ HlAttrs print_attrs;
int showing_mode;
struct {
int enable_mouse, disable_mouse;
@@ -72,32 +71,40 @@ static bool volatile got_winch = false;
# include "tui/tui.c.generated.h"
#endif
-#define EMPTY_ATTRS ((HlAttrs){false, false, false, false, false, -1, -1})
-
-#define FOREACH_CELL(ui, top, bot, left, right, go, code) \
- do { \
- TUIData *data = ui->data; \
- for (int row = top; row <= bot; ++row) { \
- Cell *cells = data->screen[row]; \
- if (go) { \
- unibi_goto(ui, row, left); \
- } \
- for (int col = left; col <= right; ++col) { \
- Cell *cell = cells + col; \
- (void)(cell); \
- code; \
- } \
- } \
- } while (0)
-
UI *tui_start(void)
{
- TUIData *data = xcalloc(1, sizeof(TUIData));
UI *ui = xcalloc(1, sizeof(UI));
- ui->data = data;
- data->attrs = data->print_attrs = EMPTY_ATTRS;
- data->fg = data->bg = -1;
+ ui->stop = tui_stop;
+ ui->rgb = os_getenv("NVIM_TUI_ENABLE_TRUE_COLOR") != NULL;
+ ui->resize = tui_resize;
+ ui->clear = tui_clear;
+ ui->eol_clear = tui_eol_clear;
+ ui->cursor_goto = tui_cursor_goto;
+ ui->update_menu = tui_update_menu;
+ ui->busy_start = tui_busy_start;
+ ui->busy_stop = tui_busy_stop;
+ ui->mouse_on = tui_mouse_on;
+ ui->mouse_off = tui_mouse_off;
+ ui->mode_change = tui_mode_change;
+ ui->set_scroll_region = tui_set_scroll_region;
+ ui->scroll = tui_scroll;
+ ui->highlight_set = tui_highlight_set;
+ ui->put = tui_put;
+ ui->bell = tui_bell;
+ ui->visual_bell = tui_visual_bell;
+ ui->update_fg = tui_update_fg;
+ ui->update_bg = tui_update_bg;
+ ui->flush = tui_flush;
+ ui->suspend = tui_suspend;
+ ui->set_title = tui_set_title;
+ ui->set_icon = tui_set_icon;
+ return ui_bridge_attach(ui, tui_main, tui_scheduler);
+}
+
+static void terminfo_start(UI *ui)
+{
+ TUIData *data = ui->data;
data->can_use_terminal_scroll = true;
data->bufpos = 0;
data->bufsize = sizeof(data->buf) - CNORM_COMMAND_MAX_SIZE;
@@ -109,12 +116,8 @@ UI *tui_start(void)
data->unibi_ext.enter_insert_mode = -1;
data->unibi_ext.enter_replace_mode = -1;
data->unibi_ext.exit_insert_mode = -1;
-
// write output to stderr if stdout is not a tty
data->out_fd = os_isatty(1) ? 1 : (os_isatty(2) ? 2 : 1);
- kv_init(data->invalid_regions);
- // setup term input
- data->input = term_input_new();
// setup unibilium
data->ut = unibi_from_env();
if (!data->ut) {
@@ -128,62 +131,14 @@ UI *tui_start(void)
unibi_out(ui, unibi_clear_screen);
// Enable bracketed paste
unibi_out(ui, data->unibi_ext.enable_bracketed_paste);
- // setup output handle in a separate event loop(we wanna do synchronous
- // write to the tty)
- data->write_loop = xmalloc(sizeof(uv_loop_t));
- uv_loop_init(data->write_loop);
- uv_tty_init(data->write_loop, &data->output_handle, data->out_fd, 0);
+ uv_loop_init(&data->write_loop);
+ uv_tty_init(&data->write_loop, &data->output_handle, data->out_fd, 0);
uv_tty_set_mode(&data->output_handle, UV_TTY_MODE_RAW);
-
- // Obtain screen dimensions
- update_size(ui);
-
- // listen for SIGWINCH
- signal_watcher_init(&loop, &data->winch_handle, ui);
- data->winch_handle.events = queue_new_child(loop.events);
- signal_watcher_start(&data->winch_handle, sigwinch_cb, SIGWINCH);
-
- ui->stop = tui_stop;
- ui->rgb = os_getenv("NVIM_TUI_ENABLE_TRUE_COLOR") != NULL;
- ui->data = data;
- ui->resize = tui_resize;
- ui->clear = tui_clear;
- ui->eol_clear = tui_eol_clear;
- ui->cursor_goto = tui_cursor_goto;
- ui->update_menu = tui_update_menu;
- ui->busy_start = tui_busy_start;
- ui->busy_stop = tui_busy_stop;
- ui->mouse_on = tui_mouse_on;
- ui->mouse_off = tui_mouse_off;
- ui->mode_change = tui_mode_change;
- ui->set_scroll_region = tui_set_scroll_region;
- ui->scroll = tui_scroll;
- ui->highlight_set = tui_highlight_set;
- ui->put = tui_put;
- ui->bell = tui_bell;
- ui->visual_bell = tui_visual_bell;
- ui->update_fg = tui_update_fg;
- ui->update_bg = tui_update_bg;
- ui->flush = tui_flush;
- ui->suspend = tui_suspend;
- ui->set_title = tui_set_title;
- ui->set_icon = tui_set_icon;
- ui->set_encoding = tui_set_encoding;
- // Attach
- ui_attach(ui);
- return ui;
}
-static void tui_stop(UI *ui)
+static void terminfo_stop(UI *ui)
{
TUIData *data = ui->data;
- // Destroy common stuff
- kv_destroy(data->invalid_regions);
- signal_watcher_stop(&data->winch_handle);
- queue_free(data->winch_handle.events);
- signal_watcher_close(&data->winch_handle, NULL);
- // Destroy input stuff
- term_input_destroy(data->input);
// Destroy output stuff
tui_mode_change(ui, NORMAL);
tui_mouse_off(ui);
@@ -196,24 +151,99 @@ static void tui_stop(UI *ui)
flush_buf(ui);
uv_tty_reset_mode();
uv_close((uv_handle_t *)&data->output_handle, NULL);
- uv_run(data->write_loop, UV_RUN_DEFAULT);
- if (uv_loop_close(data->write_loop)) {
+ uv_run(&data->write_loop, UV_RUN_DEFAULT);
+ if (uv_loop_close(&data->write_loop)) {
abort();
}
- xfree(data->write_loop);
unibi_destroy(data->ut);
- destroy_screen(data);
+}
+
+static void tui_terminal_start(UI *ui)
+{
+ TUIData *data = ui->data;
+ data->print_attrs = EMPTY_ATTRS;
+ ugrid_init(&data->grid);
+ terminfo_start(ui);
+ update_size(ui);
+ signal_watcher_start(&data->winch_handle, sigwinch_cb, SIGWINCH);
+ term_input_start(&data->input);
+}
+
+static void tui_terminal_stop(UI *ui)
+{
+ TUIData *data = ui->data;
+ term_input_stop(&data->input);
+ signal_watcher_stop(&data->winch_handle);
+ terminfo_stop(ui);
+ ugrid_free(&data->grid);
+}
+
+static void tui_stop(UI *ui)
+{
+ tui_terminal_stop(ui);
+ TUIData *data = ui->data;
+ data->stop = true;
+}
+
+// Main function of the TUI thread
+static void tui_main(UIBridgeData *bridge, UI *ui)
+{
+ Loop tui_loop;
+ loop_init(&tui_loop, NULL);
+ TUIData *data = xcalloc(1, sizeof(TUIData));
+ ui->data = data;
+ data->bridge = bridge;
+ data->loop = &tui_loop;
+ kv_init(data->invalid_regions);
+ signal_watcher_init(data->loop, &data->winch_handle, ui);
+ signal_watcher_init(data->loop, &data->cont_handle, data);
+ signal_watcher_start(&data->cont_handle, sigcont_cb, SIGCONT);
+ // initialize input reading structures
+ term_input_init(&data->input, &tui_loop);
+ tui_terminal_start(ui);
+ data->stop = false;
+ // allow the main thread to continue, we are ready to start handling UI
+ // callbacks
+ CONTINUE(bridge);
+
+ while (!data->stop) {
+ loop_poll_events(&tui_loop, -1);
+ }
+
+ term_input_destroy(&data->input);
+ signal_watcher_stop(&data->cont_handle);
+ signal_watcher_close(&data->cont_handle, NULL);
+ signal_watcher_close(&data->winch_handle, NULL);
+ loop_close(&tui_loop);
+ kv_destroy(data->invalid_regions);
xfree(data);
- ui_detach(ui);
xfree(ui);
}
+static void tui_scheduler(Event event, void *d)
+{
+ UI *ui = d;
+ TUIData *data = ui->data;
+ loop_schedule(data->loop, event);
+}
+
+static void refresh_event(void **argv)
+{
+ ui_refresh();
+}
+
+static void sigcont_cb(SignalWatcher *watcher, int signum, void *data)
+{
+ ((TUIData *)data)->cont_received = true;
+}
+
static void sigwinch_cb(SignalWatcher *watcher, int signum, void *data)
{
got_winch = true;
UI *ui = data;
update_size(ui);
- ui_refresh();
+ // run refresh_event in nvim main loop
+ loop_schedule(&loop, event_create(1, refresh_event, 0));
}
static bool attrs_differ(HlAttrs a1, HlAttrs a2)
@@ -234,9 +264,10 @@ static void update_attrs(UI *ui, HlAttrs attrs)
data->print_attrs = attrs;
unibi_out(ui, unibi_exit_attribute_mode);
+ UGrid *grid = &data->grid;
- int fg = attrs.foreground != -1 ? attrs.foreground : data->fg;
- int bg = attrs.background != -1 ? attrs.background : data->bg;
+ int fg = attrs.foreground != -1 ? attrs.foreground : grid->fg;
+ int bg = attrs.background != -1 ? attrs.background : grid->bg;
if (ui->rgb) {
if (fg != -1) {
@@ -278,25 +309,25 @@ static void update_attrs(UI *ui, HlAttrs attrs)
}
}
-static void print_cell(UI *ui, Cell *ptr)
+static void print_cell(UI *ui, UCell *ptr)
{
update_attrs(ui, ptr->attrs);
out(ui, ptr->data, strlen(ptr->data));
}
-static void clear_region(UI *ui, int top, int bot, int left, int right,
- bool refresh)
+static void clear_region(UI *ui, int top, int bot, int left, int right)
{
TUIData *data = ui->data;
- HlAttrs clear_attrs = EMPTY_ATTRS;
- clear_attrs.foreground = data->fg;
- clear_attrs.background = data->bg;
- update_attrs(ui, clear_attrs);
+ UGrid *grid = &data->grid;
bool cleared = false;
- if (refresh && data->bg == -1 && right == ui->width -1) {
+ if (grid->bg == -1 && right == ui->width -1) {
// Background is set to the default color and the right edge matches the
// screen end, try to use terminal codes for clearing the requested area.
+ HlAttrs clear_attrs = EMPTY_ATTRS;
+ clear_attrs.foreground = grid->fg;
+ clear_attrs.background = grid->bg;
+ update_attrs(ui, clear_attrs);
if (left == 0) {
if (bot == ui->height - 1) {
if (top == 0) {
@@ -319,36 +350,26 @@ static void clear_region(UI *ui, int top, int bot, int left, int right,
}
}
- bool clear = refresh && !cleared;
- FOREACH_CELL(ui, top, bot, left, right, clear, {
- cell->data[0] = ' ';
- cell->data[1] = 0;
- cell->attrs = clear_attrs;
- if (clear) {
+ if (!cleared) {
+ // could not clear using faster terminal codes, refresh the whole region
+ int currow = -1;
+ UGRID_FOREACH_CELL(grid, top, bot, left, right, {
+ if (currow != row) {
+ unibi_goto(ui, row, col);
+ currow = row;
+ }
print_cell(ui, cell);
- }
- });
+ });
+ }
// restore cursor
- unibi_goto(ui, data->row, data->col);
+ unibi_goto(ui, grid->row, grid->col);
}
static void tui_resize(UI *ui, int width, int height)
{
TUIData *data = ui->data;
- destroy_screen(data);
-
- data->screen = xmalloc((size_t)height * sizeof(Cell *));
- for (int i = 0; i < height; i++) {
- data->screen[i] = xcalloc((size_t)width, sizeof(Cell));
- }
-
- data->old_height = height;
- data->scroll_region.top = 0;
- data->scroll_region.bot = height - 1;
- data->scroll_region.left = 0;
- data->scroll_region.right = width - 1;
- data->row = data->col = 0;
+ ugrid_resize(&data->grid, width, height);
if (!got_winch) { // Try to resize the terminal window.
char r[16]; // enough for 9999x9999
@@ -362,22 +383,23 @@ static void tui_resize(UI *ui, int width, int height)
static void tui_clear(UI *ui)
{
TUIData *data = ui->data;
- clear_region(ui, data->scroll_region.top, data->scroll_region.bot,
- data->scroll_region.left, data->scroll_region.right, true);
+ UGrid *grid = &data->grid;
+ ugrid_clear(grid);
+ clear_region(ui, grid->top, grid->bot, grid->left, grid->right);
}
static void tui_eol_clear(UI *ui)
{
TUIData *data = ui->data;
- clear_region(ui, data->row, data->row, data->col,
- data->scroll_region.right, true);
+ UGrid *grid = &data->grid;
+ ugrid_eol_clear(grid);
+ clear_region(ui, grid->row, grid->row, grid->col, grid->right);
}
static void tui_cursor_goto(UI *ui, int row, int col)
{
TUIData *data = ui->data;
- data->row = row;
- data->col = col;
+ ugrid_goto(&data->grid, row, col);
unibi_goto(ui, row, col);
}
@@ -435,11 +457,7 @@ static void tui_set_scroll_region(UI *ui, int top, int bot, int left,
int right)
{
TUIData *data = ui->data;
- data->scroll_region.top = top;
- data->scroll_region.bot = bot;
- data->scroll_region.left = left;
- data->scroll_region.right = right;
-
+ ugrid_set_scroll_region(&data->grid, top, bot, left, right);
data->can_use_terminal_scroll =
left == 0 && right == ui->width - 1
&& ((top == 0 && bot == ui->height - 1)
@@ -449,31 +467,24 @@ static void tui_set_scroll_region(UI *ui, int top, int bot, int left,
static void tui_scroll(UI *ui, int count)
{
TUIData *data = ui->data;
- int top = data->scroll_region.top;
- int bot = data->scroll_region.bot;
- int left = data->scroll_region.left;
- int right = data->scroll_region.right;
+ UGrid *grid = &data->grid;
+ int clear_top, clear_bot;
+ ugrid_scroll(grid, count, &clear_top, &clear_bot);
if (data->can_use_terminal_scroll) {
// Change terminal scroll region and move cursor to the top
- data->params[0].i = top;
- data->params[1].i = bot;
+ data->params[0].i = grid->top;
+ data->params[1].i = grid->bot;
unibi_out(ui, unibi_change_scroll_region);
- unibi_goto(ui, top, left);
+ unibi_goto(ui, grid->top, grid->left);
// also set default color attributes or some terminals can become funny
HlAttrs clear_attrs = EMPTY_ATTRS;
- clear_attrs.foreground = data->fg;
- clear_attrs.background = data->bg;
+ clear_attrs.foreground = grid->fg;
+ clear_attrs.background = grid->bg;
update_attrs(ui, clear_attrs);
}
- // Compute start/stop/step for the loop below, also use terminal scroll
- // if possible
- int start, stop, step;
if (count > 0) {
- start = top;
- stop = bot - count + 1;
- step = 1;
if (data->can_use_terminal_scroll) {
if (count == 1) {
unibi_out(ui, unibi_delete_line);
@@ -484,9 +495,6 @@ static void tui_scroll(UI *ui, int count)
}
} else {
- start = bot;
- stop = top - count - 1;
- step = -1;
if (data->can_use_terminal_scroll) {
if (count == -1) {
unibi_out(ui, unibi_insert_line);
@@ -502,52 +510,30 @@ static void tui_scroll(UI *ui, int count)
data->params[0].i = 0;
data->params[1].i = ui->height - 1;
unibi_out(ui, unibi_change_scroll_region);
- unibi_goto(ui, data->row, data->col);
- }
-
- int i;
- // Scroll internal screen
- for (i = start; i != stop; i += step) {
- Cell *target_row = data->screen[i] + left;
- Cell *source_row = data->screen[i + count] + left;
- memcpy(target_row, source_row, sizeof(Cell) * (size_t)(right - left + 1));
- }
-
- // clear emptied region, updating the terminal if its builtin scrolling
- // facility was used. This is done when the background color is not the
- // default, since scrolling may leave wrong background in the cleared area.
- bool update_clear = data->bg != -1 && data->can_use_terminal_scroll;
- if (count > 0) {
- clear_region(ui, stop, stop + count - 1, left, right, update_clear);
+ unibi_goto(ui, grid->row, grid->col);
+
+ if (grid->bg != -1) {
+ // Update the cleared area of the terminal if its builtin scrolling
+ // facility was used and the background color is not the default. This is
+ // required because scrolling may leave wrong background in the cleared
+ // area.
+ clear_region(ui, clear_top, clear_bot, grid->left, grid->right);
+ }
} else {
- clear_region(ui, stop + count + 1, stop, left, right, update_clear);
- }
-
- if (!data->can_use_terminal_scroll) {
// Mark the entire scroll region as invalid for redrawing later
- invalidate(ui, data->scroll_region.top, data->scroll_region.bot,
- data->scroll_region.left, data->scroll_region.right);
+ invalidate(ui, grid->top, grid->bot, grid->left, grid->right);
}
}
static void tui_highlight_set(UI *ui, HlAttrs attrs)
{
- ((TUIData *)ui->data)->attrs = attrs;
+ ((TUIData *)ui->data)->grid.attrs = attrs;
}
static void tui_put(UI *ui, uint8_t *text, size_t size)
{
TUIData *data = ui->data;
- Cell *cell = data->screen[data->row] + data->col;
- cell->data[size] = 0;
- cell->attrs = data->attrs;
-
- if (text) {
- memcpy(cell->data, text, size);
- }
-
- print_cell(ui, cell);
- data->col += 1;
+ print_cell(ui, ugrid_put(&data->grid, text, size));
}
static void tui_bell(UI *ui)
@@ -562,40 +548,64 @@ static void tui_visual_bell(UI *ui)
static void tui_update_fg(UI *ui, int fg)
{
- ((TUIData *)ui->data)->fg = fg;
+ ((TUIData *)ui->data)->grid.fg = fg;
}
static void tui_update_bg(UI *ui, int bg)
{
- ((TUIData *)ui->data)->bg = bg;
+ ((TUIData *)ui->data)->grid.bg = bg;
}
static void tui_flush(UI *ui)
{
TUIData *data = ui->data;
+ UGrid *grid = &data->grid;
while (kv_size(data->invalid_regions)) {
Rect r = kv_pop(data->invalid_regions);
- FOREACH_CELL(ui, r.top, r.bot, r.left, r.right, true, {
+ int currow = -1;
+ UGRID_FOREACH_CELL(grid, r.top, r.bot, r.left, r.right, {
+ if (currow != row) {
+ unibi_goto(ui, row, col);
+ currow = row;
+ }
print_cell(ui, cell);
});
}
- unibi_goto(ui, data->row, data->col);
+ unibi_goto(ui, grid->row, grid->col);
flush_buf(ui);
}
-static void tui_suspend(UI *ui)
+static void suspend_event(void **argv)
{
+ UI *ui = argv[0];
TUIData *data = ui->data;
bool enable_mouse = data->mouse_enabled;
- tui_stop(ui);
+ tui_terminal_stop(ui);
+ data->cont_received = false;
kill(0, SIGTSTP);
- ui = tui_start();
+ while (!data->cont_received) {
+ // poll the event loop until SIGCONT is received
+ loop_poll_events(data->loop, -1);
+ }
+ tui_terminal_start(ui);
if (enable_mouse) {
tui_mouse_on(ui);
}
+ // resume the main thread
+ CONTINUE(data->bridge);
+}
+
+static void tui_suspend(UI *ui)
+{
+ TUIData *data = ui->data;
+ // kill(0, SIGTSTP) won't stop the UI thread, so we must poll for SIGCONT
+ // before continuing. This is done in another callback to avoid
+ // loop_poll_events recursion
+ queue_put_event(data->loop->fast_events,
+ event_create(1, suspend_event, 1, ui));
}
static void tui_set_title(UI *ui, char *title)
@@ -614,12 +624,6 @@ static void tui_set_icon(UI *ui, char *icon)
{
}
-static void tui_set_encoding(UI *ui, char* enc)
-{
- TUIData *data = ui->data;
- term_input_set_encoding(data->input, enc);
-}
-
static void invalidate(UI *ui, int top, int bot, int left, int right)
{
TUIData *data = ui->data;
@@ -698,8 +702,8 @@ end:
height = DFLT_ROWS;
}
- ui->width = width;
- ui->height = height;
+ data->bridge->bridge.width = ui->width = width;
+ data->bridge->bridge.height = ui->height = height;
}
static void unibi_goto(UI *ui, int row, int col)
@@ -882,7 +886,7 @@ static void flush_buf(UI *ui)
buf.base = data->buf;
buf.len = data->bufpos;
uv_write(&req, (uv_stream_t *)&data->output_handle, &buf, 1, NULL);
- uv_run(data->write_loop, UV_RUN_DEFAULT);
+ uv_run(&data->write_loop, UV_RUN_DEFAULT);
data->bufpos = 0;
if (!data->busy) {
@@ -891,13 +895,3 @@ static void flush_buf(UI *ui)
unibi_out(ui, unibi_cursor_invisible);
}
}
-
-static void destroy_screen(TUIData *data)
-{
- if (data->screen) {
- for (int i = 0; i < data->old_height; i++) {
- xfree(data->screen[i]);
- }
- xfree(data->screen);
- }
-}
diff --git a/src/nvim/ugrid.c b/src/nvim/ugrid.c
new file mode 100644
index 0000000000..127b18feb6
--- /dev/null
+++ b/src/nvim/ugrid.c
@@ -0,0 +1,137 @@
+#include <assert.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <limits.h>
+
+#include "nvim/vim.h"
+#include "nvim/ui.h"
+#include "nvim/ugrid.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "ugrid.c.generated.h"
+#endif
+
+void ugrid_init(UGrid *grid)
+{
+ grid->attrs = EMPTY_ATTRS;
+ grid->fg = grid->bg = -1;
+ grid->cells = NULL;
+}
+
+void ugrid_free(UGrid *grid)
+{
+ destroy_cells(grid);
+}
+
+void ugrid_resize(UGrid *grid, int width, int height)
+{
+ destroy_cells(grid);
+ grid->cells = xmalloc((size_t)height * sizeof(UCell *));
+ for (int i = 0; i < height; i++) {
+ grid->cells[i] = xcalloc((size_t)width, sizeof(UCell));
+ }
+
+ grid->top = 0;
+ grid->bot = height - 1;
+ grid->left = 0;
+ grid->right = width - 1;
+ grid->row = grid->col = 0;
+ grid->width = width;
+ grid->height = height;
+}
+
+void ugrid_clear(UGrid *grid)
+{
+ clear_region(grid, grid->top, grid->bot, grid->left, grid->right);
+}
+
+void ugrid_eol_clear(UGrid *grid)
+{
+ clear_region(grid, grid->row, grid->row, grid->col, grid->right);
+}
+
+void ugrid_goto(UGrid *grid, int row, int col)
+{
+ grid->row = row;
+ grid->col = col;
+}
+
+void ugrid_set_scroll_region(UGrid *grid, int top, int bot, int left, int right)
+{
+ grid->top = top;
+ grid->bot = bot;
+ grid->left = left;
+ grid->right = right;
+}
+
+void ugrid_scroll(UGrid *grid, int count, int *clear_top, int *clear_bot)
+{
+ // Compute start/stop/step for the loop below
+ int start, stop, step;
+ if (count > 0) {
+ start = grid->top;
+ stop = grid->bot - count + 1;
+ step = 1;
+ } else {
+ start = grid->bot;
+ stop = grid->top - count - 1;
+ step = -1;
+ }
+
+ int i;
+
+ // Copy cell data
+ for (i = start; i != stop; i += step) {
+ UCell *target_row = grid->cells[i] + grid->left;
+ UCell *source_row = grid->cells[i + count] + grid->left;
+ memcpy(target_row, source_row,
+ sizeof(UCell) * (size_t)(grid->right - grid->left + 1));
+ }
+
+ // clear cells in the emptied region,
+ if (count > 0) {
+ *clear_top = stop;
+ *clear_bot = stop + count - 1;
+ } else {
+ *clear_bot = stop;
+ *clear_top = stop + count + 1;
+ }
+ clear_region(grid, *clear_top, *clear_bot, grid->left, grid->right);
+}
+
+UCell *ugrid_put(UGrid *grid, uint8_t *text, size_t size)
+{
+ UCell *cell = grid->cells[grid->row] + grid->col;
+ cell->data[size] = 0;
+ cell->attrs = grid->attrs;
+
+ if (text) {
+ memcpy(cell->data, text, size);
+ }
+
+ grid->col += 1;
+ return cell;
+}
+
+static void clear_region(UGrid *grid, int top, int bot, int left, int right)
+{
+ HlAttrs clear_attrs = EMPTY_ATTRS;
+ clear_attrs.foreground = grid->fg;
+ clear_attrs.background = grid->bg;
+ UGRID_FOREACH_CELL(grid, top, bot, left, right, {
+ cell->data[0] = ' ';
+ cell->data[1] = 0;
+ cell->attrs = clear_attrs;
+ });
+}
+
+static void destroy_cells(UGrid *grid)
+{
+ if (grid->cells) {
+ for (int i = 0; i < grid->height; i++) {
+ xfree(grid->cells[i]);
+ }
+ xfree(grid->cells);
+ }
+}
+
diff --git a/src/nvim/ugrid.h b/src/nvim/ugrid.h
new file mode 100644
index 0000000000..e41461fa16
--- /dev/null
+++ b/src/nvim/ugrid.h
@@ -0,0 +1,40 @@
+#ifndef NVIM_UGRID_H
+#define NVIM_UGRID_H
+
+#include "nvim/ui.h"
+
+typedef struct ucell UCell;
+typedef struct ugrid UGrid;
+
+struct ucell {
+ char data[7];
+ HlAttrs attrs;
+};
+
+struct ugrid {
+ int top, bot, left, right;
+ int row, col;
+ int bg, fg;
+ int width, height;
+ HlAttrs attrs;
+ UCell **cells;
+};
+
+#define EMPTY_ATTRS ((HlAttrs){false, false, false, false, false, -1, -1})
+
+#define UGRID_FOREACH_CELL(grid, top, bot, left, right, code) \
+ do { \
+ for (int row = top; row <= bot; ++row) { \
+ UCell *row_cells = (grid)->cells[row]; \
+ for (int col = left; col <= right; ++col) { \
+ UCell *cell = row_cells + col; \
+ (void)(cell); \
+ code; \
+ } \
+ } \
+ } while (0)
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "ugrid.h.generated.h"
+#endif
+#endif // NVIM_UGRID_H
diff --git a/src/nvim/ui.c b/src/nvim/ui.c
index e1bbcdb193..786f6026de 100644
--- a/src/nvim/ui.c
+++ b/src/nvim/ui.c
@@ -113,11 +113,6 @@ void ui_set_icon(char *icon)
UI_CALL(flush);
}
-void ui_update_encoding(void)
-{
- UI_CALL(set_encoding, (char*)p_enc);
-}
-
// May update the shape of the cursor.
void ui_cursor_shape(void)
{
@@ -188,7 +183,6 @@ void ui_attach(UI *ui)
}
uis[ui_count++] = ui;
- ui_update_encoding();
ui_refresh();
}
diff --git a/src/nvim/ui.h b/src/nvim/ui.h
index c87d7f0c55..4c051fcfbf 100644
--- a/src/nvim/ui.h
+++ b/src/nvim/ui.h
@@ -38,7 +38,6 @@ struct ui_t {
void (*suspend)(UI *ui);
void (*set_title)(UI *ui, char *title);
void (*set_icon)(UI *ui, char *icon);
- void (*set_encoding)(UI *ui, char *enc);
void (*stop)(UI *ui);
};
diff --git a/src/nvim/ui_bridge.c b/src/nvim/ui_bridge.c
new file mode 100644
index 0000000000..2ec31de5e1
--- /dev/null
+++ b/src/nvim/ui_bridge.c
@@ -0,0 +1,335 @@
+// FIXME(tarruda): This module is very repetitive. It might be a good idea to
+// automatically generate it with a lua script during build
+#include <assert.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <limits.h>
+
+#include "nvim/vim.h"
+#include "nvim/ui.h"
+#include "nvim/memory.h"
+#include "nvim/ui_bridge.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "ui_bridge.c.generated.h"
+#endif
+
+#define UI(b) (((UIBridgeData *)b)->ui)
+
+// Call a function in the UI thread
+#define UI_CALL(ui, name, argc, ...) \
+ ((UIBridgeData *)ui)->scheduler( \
+ event_create(1, ui_bridge_##name##_event, argc, __VA_ARGS__), UI(ui))
+
+#define INT2PTR(i) ((void *)(uintptr_t)i)
+#define PTR2INT(p) ((int)(uintptr_t)p)
+
+UI *ui_bridge_attach(UI *ui, ui_main_fn ui_main, event_scheduler scheduler)
+{
+ UIBridgeData *rv = xcalloc(1, sizeof(UIBridgeData));
+ rv->ui = ui;
+ rv->bridge.rgb = ui->rgb;
+ rv->bridge.stop = ui_bridge_stop;
+ rv->bridge.resize = ui_bridge_resize;
+ rv->bridge.clear = ui_bridge_clear;
+ rv->bridge.eol_clear = ui_bridge_eol_clear;
+ rv->bridge.cursor_goto = ui_bridge_cursor_goto;
+ rv->bridge.update_menu = ui_bridge_update_menu;
+ rv->bridge.busy_start = ui_bridge_busy_start;
+ rv->bridge.busy_stop = ui_bridge_busy_stop;
+ rv->bridge.mouse_on = ui_bridge_mouse_on;
+ rv->bridge.mouse_off = ui_bridge_mouse_off;
+ rv->bridge.mode_change = ui_bridge_mode_change;
+ rv->bridge.set_scroll_region = ui_bridge_set_scroll_region;
+ rv->bridge.scroll = ui_bridge_scroll;
+ rv->bridge.highlight_set = ui_bridge_highlight_set;
+ rv->bridge.put = ui_bridge_put;
+ rv->bridge.bell = ui_bridge_bell;
+ rv->bridge.visual_bell = ui_bridge_visual_bell;
+ rv->bridge.update_fg = ui_bridge_update_fg;
+ rv->bridge.update_bg = ui_bridge_update_bg;
+ rv->bridge.flush = ui_bridge_flush;
+ rv->bridge.suspend = ui_bridge_suspend;
+ rv->bridge.set_title = ui_bridge_set_title;
+ rv->bridge.set_icon = ui_bridge_set_icon;
+ rv->scheduler = scheduler;
+
+ rv->ui_main = ui_main;
+ uv_mutex_init(&rv->mutex);
+ uv_cond_init(&rv->cond);
+ uv_mutex_lock(&rv->mutex);
+ rv->ready = false;
+
+ if (uv_thread_create(&rv->ui_thread, ui_thread_run, rv)) {
+ abort();
+ }
+
+ while (!rv->ready) {
+ uv_cond_wait(&rv->cond, &rv->mutex);
+ }
+ uv_mutex_unlock(&rv->mutex);
+
+ ui_attach(&rv->bridge);
+ return &rv->bridge;
+}
+
+static void ui_thread_run(void *data)
+{
+ UIBridgeData *bridge = data;
+ bridge->ui_main(bridge, bridge->ui);
+}
+
+static void ui_bridge_stop(UI *b)
+{
+ UI_CALL(b, stop, 1, b);
+ UIBridgeData *bridge = (UIBridgeData *)b;
+ uv_thread_join(&bridge->ui_thread);
+ uv_mutex_destroy(&bridge->mutex);
+ uv_cond_destroy(&bridge->cond);
+ ui_detach(b);
+ xfree(b);
+}
+static void ui_bridge_stop_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->stop(ui);
+}
+
+static void ui_bridge_resize(UI *b, int width, int height)
+{
+ UI_CALL(b, resize, 3, b, INT2PTR(width), INT2PTR(height));
+}
+static void ui_bridge_resize_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->resize(ui, PTR2INT(argv[1]), PTR2INT(argv[2]));
+}
+
+static void ui_bridge_clear(UI *b)
+{
+ UI_CALL(b, clear, 1, b);
+}
+static void ui_bridge_clear_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->clear(ui);
+}
+
+static void ui_bridge_eol_clear(UI *b)
+{
+ UI_CALL(b, eol_clear, 1, b);
+}
+static void ui_bridge_eol_clear_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->eol_clear(ui);
+}
+
+static void ui_bridge_cursor_goto(UI *b, int row, int col)
+{
+ UI_CALL(b, cursor_goto, 3, b, INT2PTR(row), INT2PTR(col));
+}
+static void ui_bridge_cursor_goto_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->cursor_goto(ui, PTR2INT(argv[1]), PTR2INT(argv[2]));
+}
+
+static void ui_bridge_update_menu(UI *b)
+{
+ UI_CALL(b, update_menu, 1, b);
+}
+static void ui_bridge_update_menu_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->update_menu(ui);
+}
+
+static void ui_bridge_busy_start(UI *b)
+{
+ UI_CALL(b, busy_start, 1, b);
+}
+static void ui_bridge_busy_start_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->busy_start(ui);
+}
+
+static void ui_bridge_busy_stop(UI *b)
+{
+ UI_CALL(b, busy_stop, 1, b);
+}
+static void ui_bridge_busy_stop_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->busy_stop(ui);
+}
+
+static void ui_bridge_mouse_on(UI *b)
+{
+ UI_CALL(b, mouse_on, 1, b);
+}
+static void ui_bridge_mouse_on_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->mouse_on(ui);
+}
+
+static void ui_bridge_mouse_off(UI *b)
+{
+ UI_CALL(b, mouse_off, 1, b);
+}
+static void ui_bridge_mouse_off_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->mouse_off(ui);
+}
+
+static void ui_bridge_mode_change(UI *b, int mode)
+{
+ UI_CALL(b, mode_change, 2, b, INT2PTR(mode));
+}
+static void ui_bridge_mode_change_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->mode_change(ui, PTR2INT(argv[1]));
+}
+
+static void ui_bridge_set_scroll_region(UI *b, int top, int bot, int left,
+ int right)
+{
+ UI_CALL(b, set_scroll_region, 5, b, INT2PTR(top), INT2PTR(bot),
+ INT2PTR(left), INT2PTR(right));
+}
+static void ui_bridge_set_scroll_region_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->set_scroll_region(ui, PTR2INT(argv[1]), PTR2INT(argv[2]),
+ PTR2INT(argv[3]), PTR2INT(argv[4]));
+}
+
+static void ui_bridge_scroll(UI *b, int count)
+{
+ UI_CALL(b, scroll, 2, b, INT2PTR(count));
+}
+static void ui_bridge_scroll_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->scroll(ui, PTR2INT(argv[1]));
+}
+
+static void ui_bridge_highlight_set(UI *b, HlAttrs attrs)
+{
+ HlAttrs *a = xmalloc(sizeof(HlAttrs));
+ *a = attrs;
+ UI_CALL(b, highlight_set, 2, b, a);
+}
+static void ui_bridge_highlight_set_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->highlight_set(ui, *((HlAttrs *)argv[1]));
+ xfree(argv[1]);
+}
+
+static void ui_bridge_put(UI *b, uint8_t *text, size_t size)
+{
+ uint8_t *t = xmalloc(8);
+ memcpy(t, text, size);
+ UI_CALL(b, put, 3, b, t, INT2PTR(size));
+}
+static void ui_bridge_put_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->put(ui, (uint8_t *)argv[1], (size_t)(uintptr_t)argv[2]);
+ xfree(argv[1]);
+}
+
+static void ui_bridge_bell(UI *b)
+{
+ UI_CALL(b, bell, 1, b);
+}
+static void ui_bridge_bell_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->bell(ui);
+}
+
+static void ui_bridge_visual_bell(UI *b)
+{
+ UI_CALL(b, visual_bell, 1, b);
+}
+static void ui_bridge_visual_bell_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->visual_bell(ui);
+}
+
+static void ui_bridge_update_fg(UI *b, int fg)
+{
+ UI_CALL(b, update_fg, 2, b, INT2PTR(fg));
+}
+static void ui_bridge_update_fg_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->update_fg(ui, PTR2INT(argv[1]));
+}
+
+static void ui_bridge_update_bg(UI *b, int bg)
+{
+ UI_CALL(b, update_bg, 2, b, INT2PTR(bg));
+}
+static void ui_bridge_update_bg_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->update_bg(ui, PTR2INT(argv[1]));
+}
+
+static void ui_bridge_flush(UI *b)
+{
+ UI_CALL(b, flush, 1, b);
+}
+static void ui_bridge_flush_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->flush(ui);
+}
+
+static void ui_bridge_suspend(UI *b)
+{
+ UIBridgeData *data = (UIBridgeData *)b;
+ uv_mutex_lock(&data->mutex);
+ UI_CALL(b, suspend, 1, b);
+ data->ready = false;
+ // suspend the main thread until CONTINUE is called by the UI thread
+ while (!data->ready) {
+ uv_cond_wait(&data->cond, &data->mutex);
+ }
+ uv_mutex_unlock(&data->mutex);
+}
+static void ui_bridge_suspend_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->suspend(ui);
+}
+
+static void ui_bridge_set_title(UI *b, char *title)
+{
+ UI_CALL(b, set_title, 2, b, title ? xstrdup(title) : NULL);
+}
+static void ui_bridge_set_title_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->set_title(ui, argv[1]);
+ xfree(argv[1]);
+}
+
+static void ui_bridge_set_icon(UI *b, char *icon)
+{
+ UI_CALL(b, set_icon, 2, b, icon ? xstrdup(icon) : NULL);
+}
+static void ui_bridge_set_icon_event(void **argv)
+{
+ UI *ui = UI(argv[0]);
+ ui->set_icon(ui, argv[1]);
+ xfree(argv[1]);
+}
diff --git a/src/nvim/ui_bridge.h b/src/nvim/ui_bridge.h
new file mode 100644
index 0000000000..76e9e27989
--- /dev/null
+++ b/src/nvim/ui_bridge.h
@@ -0,0 +1,40 @@
+// Bridge used for communication between a builtin UI thread and nvim core
+#ifndef NVIM_UI_BRIDGE_H
+#define NVIM_UI_BRIDGE_H
+
+#include <uv.h>
+
+#include "nvim/ui.h"
+#include "nvim/event/defs.h"
+
+typedef struct ui_bridge_data UIBridgeData;
+typedef void(*ui_main_fn)(UIBridgeData *bridge, UI *ui);
+struct ui_bridge_data {
+ UI bridge; // actual UI passed to ui_attach
+ UI *ui; // UI pointer that will have it's callback called in
+ // another thread
+ event_scheduler scheduler;
+ uv_thread_t ui_thread;
+ ui_main_fn ui_main;
+ uv_mutex_t mutex;
+ uv_cond_t cond;
+ // When the UI thread is called, the main thread will suspend until
+ // the call returns. This flag is used as a condition for the main
+ // thread to continue.
+ bool ready;
+};
+
+#define CONTINUE(b) \
+ do { \
+ UIBridgeData *d = (UIBridgeData *)b; \
+ uv_mutex_lock(&d->mutex); \
+ d->ready = true; \
+ uv_cond_signal(&d->cond); \
+ uv_mutex_unlock(&d->mutex); \
+ } while (0)
+
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "ui_bridge.h.generated.h"
+#endif
+#endif // NVIM_UI_BRIDGE_H
diff --git a/src/nvim/version.c b/src/nvim/version.c
index 8f0e6ccfff..072084de40 100644
--- a/src/nvim/version.c
+++ b/src/nvim/version.c
@@ -312,7 +312,7 @@ static int included_patches[] = {
// 612,
// 611 NA
// 610 NA
- // 609,
+ 609,
// 608,
// 607,
606,
diff --git a/test/functional/ex_cmds/encoding_spec.lua b/test/functional/ex_cmds/encoding_spec.lua
new file mode 100644
index 0000000000..997776ca25
--- /dev/null
+++ b/test/functional/ex_cmds/encoding_spec.lua
@@ -0,0 +1,40 @@
+local helpers = require('test.functional.helpers')
+local clear, execute, feed = helpers.clear, helpers.execute, helpers.feed
+local eq, neq, eval = helpers.eq, helpers.neq, helpers.eval
+
+describe('&encoding', function()
+
+ before_each(function()
+ clear()
+ -- sanity check: tests should run with encoding=utf-8
+ eq('utf-8', eval('&encoding'))
+ eq(3, eval('strwidth("Bär")'))
+ end)
+
+ it('cannot be changed after setup', function()
+ execute('set encoding=latin1')
+ -- error message expected
+ feed('<cr>')
+ neq(nil, string.find(eval('v:errmsg'), '^E905:'))
+ eq('utf-8', eval('&encoding'))
+ -- check nvim is still in utf-8 mode
+ eq(3, eval('strwidth("Bär")'))
+ end)
+
+ it('is not changed by `set all&`', function()
+ -- we need to set &encoding to something non-default
+ -- use 'latin1' when enc&vi is 'utf-8', 'utf-8' otherwise
+ execute('set fenc=default')
+ local enc_default, enc_other, width = eval('&fenc'), 'utf-8', 3
+ if enc_default == 'utf-8' then
+ enc_other = 'latin1'
+ width = 4 -- utf-8 string 'Bär' will count as 4 latin1 chars
+ end
+
+ clear('set enc=' .. enc_other)
+ execute('set all&')
+ eq(enc_other, eval('&encoding'))
+ eq(width, eval('strwidth("Bär")'))
+ end)
+
+end)
diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua
index f228f6290c..6055cc3c59 100644
--- a/test/functional/helpers.lua
+++ b/test/functional/helpers.lua
@@ -7,7 +7,7 @@ local Session = require('nvim.session')
local nvim_prog = os.getenv('NVIM_PROG') or 'build/bin/nvim'
local nvim_argv = {nvim_prog, '-u', 'NONE', '-i', 'NONE', '-N',
- '--cmd', 'set shortmess+=I background=light noswapfile noautoindent laststatus=1',
+ '--cmd', 'set shortmess+=I background=light noswapfile noautoindent laststatus=1 encoding=utf-8',
'--embed'}
-- Formulate a path to the directory containing nvim. We use this to
@@ -183,11 +183,16 @@ local function spawn(argv)
return session
end
-local function clear()
+local function clear(extra_cmd)
if session then
session:exit(0)
end
- session = spawn(nvim_argv)
+ local args = {unpack(nvim_argv)}
+ if extra_cmd ~= nil then
+ table.insert(args, '--cmd')
+ table.insert(args, extra_cmd)
+ end
+ session = spawn(args)
end
local function insert(...)
diff --git a/test/functional/legacy/044_099_regexp_multibyte_magic_spec.lua b/test/functional/legacy/044_099_regexp_multibyte_magic_spec.lua
index 58838e9d6e..efe61aa354 100644
--- a/test/functional/legacy/044_099_regexp_multibyte_magic_spec.lua
+++ b/test/functional/legacy/044_099_regexp_multibyte_magic_spec.lua
@@ -33,7 +33,6 @@ local function run_test_with_regexpengine(regexpengine)
j 0123❤x
k combinations]])
- execute('set encoding=utf-8')
execute('set re=' .. regexpengine)
-- Lines 1-8. Exercise regexp search with various magic settings. On each
diff --git a/test/functional/legacy/075_maparg_spec.lua b/test/functional/legacy/075_maparg_spec.lua
index dac8940314..418abb14d4 100644
--- a/test/functional/legacy/075_maparg_spec.lua
+++ b/test/functional/legacy/075_maparg_spec.lua
@@ -10,7 +10,6 @@ describe('maparg()', function()
it('is working', function()
execute('set cpo-=<')
- execute('set encoding=utf8')
-- Test maparg() with a string result
execute('map foo<C-V> is<F4>foo')
diff --git a/test/functional/legacy/082_string_comparison_spec.lua b/test/functional/legacy/082_string_comparison_spec.lua
index bd4e8a4d79..1615828ca0 100644
--- a/test/functional/legacy/082_string_comparison_spec.lua
+++ b/test/functional/legacy/082_string_comparison_spec.lua
@@ -9,7 +9,6 @@ describe('case-insensitive string comparison in UTF-8', function()
setup(clear)
it('is working', function()
- execute('set enc=utf8')
feed('ggdG<cr>')
source([[
function! Ch(a, op, b, expected)
diff --git a/test/functional/legacy/095_regexp_multibyte_spec.lua b/test/functional/legacy/095_regexp_multibyte_spec.lua
index a72fb669d2..559222e2ff 100644
--- a/test/functional/legacy/095_regexp_multibyte_spec.lua
+++ b/test/functional/legacy/095_regexp_multibyte_spec.lua
@@ -15,7 +15,7 @@ describe('regex with multi-byte', function()
Results of test95:]])
source([=[
- set encoding=utf-8 nomore
+ set nomore
let tl = []
call add(tl, [2, '[[:alpha:][=a=]]\+', '879 aiaãâaiuvna ', 'aiaãâaiuvna'])
diff --git a/test/functional/legacy/mapping_spec.lua b/test/functional/legacy/mapping_spec.lua
index 0843506827..899f7423d0 100644
--- a/test/functional/legacy/mapping_spec.lua
+++ b/test/functional/legacy/mapping_spec.lua
@@ -12,8 +12,6 @@ describe('mapping', function()
test starts here:
]])
- execute('set encoding=utf-8')
-
-- Abbreviations with р (0x80) should work.
execute('inoreab чкпр vim')
feed('GAчкпр <esc>')
diff --git a/test/functional/legacy/utf8_spec.lua b/test/functional/legacy/utf8_spec.lua
index d26f436057..ef717042d0 100644
--- a/test/functional/legacy/utf8_spec.lua
+++ b/test/functional/legacy/utf8_spec.lua
@@ -10,7 +10,6 @@ describe('utf8', function()
it('is working', function()
insert('start:')
- execute('set encoding=utf-8')
execute('new')
execute('call setline(1, ["aaa", "あああ", "bbb"])')
diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt
index ed4eaaa6a7..b56f07adc4 100644
--- a/third-party/CMakeLists.txt
+++ b/third-party/CMakeLists.txt
@@ -22,6 +22,8 @@ option(USE_BUNDLED_MSGPACK "Use the bundled msgpack." ${USE_BUNDLED})
option(USE_BUNDLED_LUAJIT "Use the bundled version of luajit." ${USE_BUNDLED})
option(USE_BUNDLED_LUAROCKS "Use the bundled version of luarocks." ${USE_BUNDLED})
+option(USE_EXISTING_SRC_DIR "Skip download of deps sources in case of existing source directory." OFF)
+
if(UNIX)
find_program(MAKE_PRG NAMES gmake make)
if(MAKE_PRG)
@@ -81,8 +83,8 @@ set(LUAJIT_SHA256 620fa4eb12375021bef6e4f237cbd2dd5d49e56beb414bee052c746beef180
set(LUAROCKS_URL https://github.com/keplerproject/luarocks/archive/5d8a16526573b36d5b22aa74866120c998466697.tar.gz)
set(LUAROCKS_SHA256 cae709111c5701235770047dfd7169f66b82ae1c7b9b79207f9df0afb722bfd9)
-set(UNIBILIUM_URL https://github.com/mauke/unibilium/archive/v1.1.4.tar.gz)
-set(UNIBILIUM_SHA256 8b8948266eb370eef8100f401d530451d627a17c068a3f85cd5d62a57517aaa7)
+set(UNIBILIUM_URL https://github.com/mauke/unibilium/archive/v1.2.0.tar.gz)
+set(UNIBILIUM_SHA256 623af1099515e673abfd3cae5f2fa808a09ca55dda1c65a7b5c9424eb304ead8)
set(LIBTERMKEY_URL https://github.com/neovim/libtermkey/archive/8c0cb7108cc63218ea19aa898968eede19e19603.tar.gz)
set(LIBTERMKEY_SHA256 21846369081e6c9a0b615f4b3889c4cb809321c5ccc6e6c1640eb138f1590072)
diff --git a/third-party/cmake/BuildJeMalloc.cmake b/third-party/cmake/BuildJeMalloc.cmake
index b02bff166a..5aaad2f25c 100644
--- a/third-party/cmake/BuildJeMalloc.cmake
+++ b/third-party/cmake/BuildJeMalloc.cmake
@@ -13,6 +13,7 @@ ExternalProject_Add(jemalloc
-DURL=${JEMALLOC_URL}
-DEXPECTED_SHA256=${JEMALLOC_SHA256}
-DTARGET=jemalloc
+ -DUSE_EXISTING_SRC_DIR=${USE_EXISTING_SRC_DIR}
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/DownloadAndExtractFile.cmake
BUILD_IN_SOURCE 1
CONFIGURE_COMMAND ${DEPS_BUILD_DIR}/src/jemalloc/configure --enable-cc-silence
diff --git a/third-party/cmake/BuildLibtermkey.cmake b/third-party/cmake/BuildLibtermkey.cmake
index 874b7df104..57e14d36d3 100644
--- a/third-party/cmake/BuildLibtermkey.cmake
+++ b/third-party/cmake/BuildLibtermkey.cmake
@@ -14,6 +14,7 @@ ExternalProject_Add(libtermkey
-DURL=${LIBTERMKEY_URL}
-DEXPECTED_SHA256=${LIBTERMKEY_SHA256}
-DTARGET=libtermkey
+ -DUSE_EXISTING_SRC_DIR=${USE_EXISTING_SRC_DIR}
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/DownloadAndExtractFile.cmake
CONFIGURE_COMMAND ""
BUILD_IN_SOURCE 1
diff --git a/third-party/cmake/BuildLibuv.cmake b/third-party/cmake/BuildLibuv.cmake
index 982c8f8067..2919d289cc 100644
--- a/third-party/cmake/BuildLibuv.cmake
+++ b/third-party/cmake/BuildLibuv.cmake
@@ -28,6 +28,7 @@ function(BuildLibuv)
-DURL=${LIBUV_URL}
-DEXPECTED_SHA256=${LIBUV_SHA256}
-DTARGET=${_libuv_TARGET}
+ -DUSE_EXISTING_SRC_DIR=${USE_EXISTING_SRC_DIR}
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/DownloadAndExtractFile.cmake
CONFIGURE_COMMAND "${_libuv_CONFIGURE_COMMAND}"
BUILD_COMMAND "${_libuv_BUILD_COMMAND}"
diff --git a/third-party/cmake/BuildLibvterm.cmake b/third-party/cmake/BuildLibvterm.cmake
index e5963cf6c4..ec9ba0d741 100644
--- a/third-party/cmake/BuildLibvterm.cmake
+++ b/third-party/cmake/BuildLibvterm.cmake
@@ -13,6 +13,7 @@ ExternalProject_Add(libvterm
-DURL=${LIBVTERM_URL}
-DEXPECTED_SHA256=${LIBVTERM_SHA256}
-DTARGET=libvterm
+ -DUSE_EXISTING_SRC_DIR=${USE_EXISTING_SRC_DIR}
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/DownloadAndExtractFile.cmake
CONFIGURE_COMMAND ""
BUILD_IN_SOURCE 1
diff --git a/third-party/cmake/BuildLuajit.cmake b/third-party/cmake/BuildLuajit.cmake
index 33753fd350..3598b00c04 100644
--- a/third-party/cmake/BuildLuajit.cmake
+++ b/third-party/cmake/BuildLuajit.cmake
@@ -27,6 +27,7 @@ function(BuildLuajit)
-DURL=${LUAJIT_URL}
-DEXPECTED_SHA256=${LUAJIT_SHA256}
-DTARGET=${_luajit_TARGET}
+ -DUSE_EXISTING_SRC_DIR=${USE_EXISTING_SRC_DIR}
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/DownloadAndExtractFile.cmake
CONFIGURE_COMMAND "${_luajit_CONFIGURE_COMMAND}"
BUILD_IN_SOURCE 1
diff --git a/third-party/cmake/BuildLuarocks.cmake b/third-party/cmake/BuildLuarocks.cmake
index 14454fa5a0..df38b29f37 100644
--- a/third-party/cmake/BuildLuarocks.cmake
+++ b/third-party/cmake/BuildLuarocks.cmake
@@ -34,6 +34,7 @@ function(BuildLuarocks)
-DURL=${LUAROCKS_URL}
-DEXPECTED_SHA256=${LUAROCKS_SHA256}
-DTARGET=luarocks
+ -DUSE_EXISTING_SRC_DIR=${USE_EXISTING_SRC_DIR}
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/DownloadAndExtractFile.cmake
BUILD_IN_SOURCE 1
CONFIGURE_COMMAND "${_luarocks_CONFIGURE_COMMAND}"
diff --git a/third-party/cmake/BuildMsgpack.cmake b/third-party/cmake/BuildMsgpack.cmake
index 44f5259655..f0d5fab676 100644
--- a/third-party/cmake/BuildMsgpack.cmake
+++ b/third-party/cmake/BuildMsgpack.cmake
@@ -25,6 +25,7 @@ function(BuildMsgpack)
-DURL=${MSGPACK_URL}
-DEXPECTED_SHA256=${MSGPACK_SHA256}
-DTARGET=msgpack
+ -DUSE_EXISTING_SRC_DIR=${USE_EXISTING_SRC_DIR}
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/DownloadAndExtractFile.cmake
CONFIGURE_COMMAND "${_msgpack_CONFIGURE_COMMAND}"
BUILD_COMMAND "${_msgpack_BUILD_COMMAND}"
diff --git a/third-party/cmake/BuildUnibilium.cmake b/third-party/cmake/BuildUnibilium.cmake
index 4cb0ebfa2f..a5ec6c01eb 100644
--- a/third-party/cmake/BuildUnibilium.cmake
+++ b/third-party/cmake/BuildUnibilium.cmake
@@ -13,6 +13,7 @@ ExternalProject_Add(unibilium
-DURL=${UNIBILIUM_URL}
-DEXPECTED_SHA256=${UNIBILIUM_SHA256}
-DTARGET=unibilium
+ -DUSE_EXISTING_SRC_DIR=${USE_EXISTING_SRC_DIR}
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/DownloadAndExtractFile.cmake
CONFIGURE_COMMAND ""
BUILD_IN_SOURCE 1
diff --git a/third-party/cmake/DownloadAndExtractFile.cmake b/third-party/cmake/DownloadAndExtractFile.cmake
index 305e37e7dd..24e431b5e5 100644
--- a/third-party/cmake/DownloadAndExtractFile.cmake
+++ b/third-party/cmake/DownloadAndExtractFile.cmake
@@ -22,12 +22,15 @@ set(SRC_DIR ${PREFIX}/src/${TARGET})
# Check whether the source has been downloaded. If true, skip it.
# Useful for external downloads like homebrew.
-if(EXISTS "${SRC_DIR}" AND IS_DIRECTORY "${SRC_DIR}")
- file(GLOB EXISTED_FILES "${SRC_DIR}/*")
- if(EXISTED_FILES)
- message(STATUS "${SRC_DIR} is found and not empty, skipping download and extraction. ")
- return()
+if(USE_EXISTING_SRC_DIR)
+ if(EXISTS "${SRC_DIR}" AND IS_DIRECTORY "${SRC_DIR}")
+ file(GLOB EXISTED_FILES "${SRC_DIR}/*")
+ if(EXISTED_FILES)
+ message(STATUS "${SRC_DIR} is found and not empty, skipping download and extraction. ")
+ return()
+ endif()
endif()
+ message(FATAL_ERROR "USE_EXISTING_SRC_DIR set to ON, but '${SRC_DIR}' does not exist or is empty.")
endif()
# Taken from ExternalProject_Add. Let's hope we can drop this one day when