aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt13
-rw-r--r--Makefile11
-rw-r--r--ci/build.ps12
-rw-r--r--cmake/FindLibLUV.cmake6
-rw-r--r--cmake/FindLibTermkey.cmake6
-rw-r--r--cmake/FindLibUV.cmake9
-rw-r--r--cmake/FindLibVterm.cmake33
-rw-r--r--cmake/FindLuaJit.cmake7
-rw-r--r--cmake/FindMsgpack.cmake7
-rw-r--r--config/CMakeLists.txt1
-rw-r--r--config/config.h.in1
-rw-r--r--contrib/local.mk.example12
-rw-r--r--runtime/doc/api.txt19
-rw-r--r--runtime/doc/if_lua.txt18
-rw-r--r--runtime/doc/job_control.txt2
-rw-r--r--src/nvim/api/buffer.c35
-rw-r--r--src/nvim/buffer.c1
-rw-r--r--src/nvim/buffer_defs.h20
-rw-r--r--src/nvim/buffer_updates.c18
-rw-r--r--src/nvim/change.c1798
-rw-r--r--src/nvim/change.h11
-rw-r--r--src/nvim/cursor.c1
-rw-r--r--src/nvim/diff.c1
-rw-r--r--src/nvim/edit.c1
-rw-r--r--src/nvim/eval.c10
-rw-r--r--src/nvim/event/libuv_process.c6
-rw-r--r--src/nvim/event/process.c6
-rw-r--r--src/nvim/event/process.h1
-rw-r--r--src/nvim/ex_cmds.c1
-rw-r--r--src/nvim/ex_cmds2.c1
-rw-r--r--src/nvim/ex_docmd.c8
-rw-r--r--src/nvim/fileio.c124
-rw-r--r--src/nvim/fold.c1
-rw-r--r--src/nvim/getchar.c1
-rw-r--r--src/nvim/globals.h12
-rw-r--r--src/nvim/iconv.h46
-rw-r--r--src/nvim/indent.c104
-rw-r--r--src/nvim/lua/executor.c66
-rw-r--r--src/nvim/main.c3
-rw-r--r--src/nvim/mbyte.c231
-rw-r--r--src/nvim/mbyte.h2
-rw-r--r--src/nvim/memline.c58
-rw-r--r--src/nvim/misc1.c1738
-rw-r--r--src/nvim/normal.c1
-rw-r--r--src/nvim/ops.c1
-rw-r--r--src/nvim/os/pty_process_unix.c2
-rw-r--r--src/nvim/os/pty_process_win.c2
-rw-r--r--src/nvim/search.c1
-rw-r--r--src/nvim/spell.c7
-rw-r--r--src/nvim/terminal.c3
-rw-r--r--src/nvim/testdir/test_spell.vim54
-rw-r--r--src/nvim/undo.c1
-rw-r--r--src/nvim/version.c23
-rw-r--r--test/functional/core/job_spec.lua21
-rw-r--r--test/functional/fixtures/shell-test.c19
-rw-r--r--test/functional/helpers.lua365
-rw-r--r--test/functional/lua/buffer_updates_spec.lua162
-rw-r--r--test/functional/lua/utility_functions_spec.lua55
-rw-r--r--test/functional/plugin/shada_spec.lua11
-rw-r--r--test/functional/shada/buffers_spec.lua42
-rw-r--r--test/functional/shada/helpers.lua59
-rw-r--r--test/functional/shada/marks_spec.lua34
-rw-r--r--test/functional/shada/registers_spec.lua11
-rw-r--r--test/functional/shada/shada_spec.lua4
-rw-r--r--test/functional/shada/variables_spec.lua19
-rw-r--r--test/functional/ui/output_spec.lua15
-rw-r--r--test/unit/helpers.lua4
67 files changed, 2727 insertions, 2641 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 947de61988..1edaa63de6 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -250,7 +250,7 @@ check_c_source_compiles("
int main(void)
{
void *trace[1];
- int trace_size = backtrace(trace, 1);
+ backtrace(trace, 1);
return 0;
}
" HAVE_EXECINFO_BACKTRACE)
@@ -258,7 +258,7 @@ int main(void)
check_c_source_compiles("
int main(void)
{
- int a;
+ int a = 42;
__builtin_add_overflow(a, a, &a);
__builtin_sub_overflow(a, a, &a);
return 0;
@@ -668,15 +668,6 @@ else()
COMMENT "lualint: LUACHECK_PRG not defined")
endif()
-if(FLAKE8_PRG)
- add_custom_target(pylint
- COMMAND ${FLAKE8_PRG} contrib/ scripts/ src/ test/
- WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
-else()
- add_custom_target(pylint false
- COMMENT "flake8: FLAKE8_PRG not defined")
-endif()
-
set(CPACK_PACKAGE_NAME "Neovim")
set(CPACK_PACKAGE_VENDOR "neovim.io")
set(CPACK_PACKAGE_VERSION ${NVIM_VERSION_MEDIUM})
diff --git a/Makefile b/Makefile
index e2cf4a3b29..fff0ffef28 100644
--- a/Makefile
+++ b/Makefile
@@ -138,8 +138,13 @@ functionaltest-lua: | nvim
lualint: | build/.ran-cmake deps
$(BUILD_CMD) -C build lualint
-pylint: | build/.ran-cmake deps
- $(BUILD_CMD) -C build pylint
+pylint:
+ flake8 contrib/ scripts/ src/ test/
+
+# Run pylint only if flake8 is installed.
+_opt_pylint:
+ @command -v flake8 && { $(MAKE) pylint; exit $$?; } \
+ || echo "SKIP: pylint (flake8 not found)"
unittest: | nvim
+$(BUILD_CMD) -C build unittest
@@ -182,7 +187,7 @@ appimage:
appimage-%:
bash scripts/genappimage.sh $*
-lint: check-single-includes clint lualint pylint
+lint: check-single-includes clint lualint _opt_pylint
# Generic pattern rules, allowing for `make build/bin/nvim` etc.
# Does not work with "Unix Makefiles".
diff --git a/ci/build.ps1 b/ci/build.ps1
index da1ad2f4b6..42066c462b 100644
--- a/ci/build.ps1
+++ b/ci/build.ps1
@@ -137,8 +137,10 @@ if ($uploadToCodecov) {
# Old tests
# Add MSYS to path, required for e.g. `find` used in test scripts.
# But would break functionaltests, where its `more` would be used then.
+$OldPath = $env:PATH
$env:PATH = "C:\msys64\usr\bin;$env:PATH"
& "C:\msys64\mingw$bits\bin\mingw32-make.exe" -C $(Convert-Path ..\src\nvim\testdir) VERBOSE=1
+$env:PATH = $OldPath
if ($uploadToCodecov) {
bash -l /c/projects/neovim/ci/common/submit_coverage.sh oldtest
diff --git a/cmake/FindLibLUV.cmake b/cmake/FindLibLUV.cmake
index 66f827214e..784e3fd249 100644
--- a/cmake/FindLibLUV.cmake
+++ b/cmake/FindLibLUV.cmake
@@ -14,12 +14,6 @@ set(LIBLUV_DEFINITIONS ${PC_LIBLUV_CFLAGS_OTHER})
find_path(LIBLUV_INCLUDE_DIR luv/luv.h
PATHS ${PC_LIBLUV_INCLUDEDIR} ${PC_LIBLUV_INCLUDE_DIRS})
-# If we're asked to use static linkage, add libluv.a as a preferred library name.
-if(LIBLUV_USE_STATIC)
- list(APPEND LIBLUV_NAMES
- "${CMAKE_STATIC_LIBRARY_PREFIX}luv${CMAKE_STATIC_LIBRARY_SUFFIX}")
-endif()
-
list(APPEND LIBLUV_NAMES luv)
find_library(LIBLUV_LIBRARY NAMES ${LIBLUV_NAMES}
diff --git a/cmake/FindLibTermkey.cmake b/cmake/FindLibTermkey.cmake
index 6e09a692c8..3e0c7f1bfd 100644
--- a/cmake/FindLibTermkey.cmake
+++ b/cmake/FindLibTermkey.cmake
@@ -14,12 +14,6 @@ set(LIBTERMKEY_DEFINITIONS ${PC_LIBTERMKEY_CFLAGS_OTHER})
find_path(LIBTERMKEY_INCLUDE_DIR termkey.h
PATHS ${PC_LIBTERMKEY_INCLUDEDIR} ${PC_LIBTERMKEY_INCLUDE_DIRS})
-# If we're asked to use static linkage, add libuv.a as a preferred library name.
-if(LIBTERMKEY_USE_STATIC)
- list(APPEND LIBTERMKEY_NAMES
- "${CMAKE_STATIC_LIBRARY_PREFIX}termkey${CMAKE_STATIC_LIBRARY_SUFFIX}")
-endif()
-
list(APPEND LIBTERMKEY_NAMES termkey)
find_library(LIBTERMKEY_LIBRARY NAMES ${LIBTERMKEY_NAMES}
diff --git a/cmake/FindLibUV.cmake b/cmake/FindLibUV.cmake
index e94a243ec6..951fb0435e 100644
--- a/cmake/FindLibUV.cmake
+++ b/cmake/FindLibUV.cmake
@@ -4,9 +4,6 @@
# LIBUV_FOUND - system has libuv
# LIBUV_INCLUDE_DIRS - the libuv include directories
# LIBUV_LIBRARIES - link these to use libuv
-#
-# Set the LIBUV_USE_STATIC variable to specify if static libraries should
-# be preferred to shared ones.
find_package(PkgConfig)
if (PKG_CONFIG_FOUND)
@@ -16,12 +13,6 @@ endif()
find_path(LIBUV_INCLUDE_DIR uv.h
HINTS ${PC_LIBUV_INCLUDEDIR} ${PC_LIBUV_INCLUDE_DIRS})
-# If we're asked to use static linkage, add libuv.a as a preferred library name.
-if(LIBUV_USE_STATIC)
- list(APPEND LIBUV_NAMES
- "${CMAKE_STATIC_LIBRARY_PREFIX}uv${CMAKE_STATIC_LIBRARY_SUFFIX}")
-endif(LIBUV_USE_STATIC)
-
list(APPEND LIBUV_NAMES uv)
find_library(LIBUV_LIBRARY NAMES ${LIBUV_NAMES}
diff --git a/cmake/FindLibVterm.cmake b/cmake/FindLibVterm.cmake
index e11d1caddc..469494ddfd 100644
--- a/cmake/FindLibVterm.cmake
+++ b/cmake/FindLibVterm.cmake
@@ -4,34 +4,7 @@
# LIBVTERM_INCLUDE_DIRS - The libvterm include directories
# LIBVTERM_LIBRARIES - The libraries needed to use libvterm
-find_package(PkgConfig)
-if (PKG_CONFIG_FOUND)
- pkg_check_modules(PC_LIBVTERM QUIET vterm)
-endif()
+include(LibFindMacros)
-set(LIBVTERM_DEFINITIONS ${PC_LIBVTERM_CFLAGS_OTHER})
-
-find_path(LIBVTERM_INCLUDE_DIR vterm.h
- PATHS ${PC_LIBVTERM_INCLUDEDIR} ${PC_LIBVTERM_INCLUDE_DIRS})
-
-# If we're asked to use static linkage, add libuv.a as a preferred library name.
-if(LIBVTERM_USE_STATIC)
- list(APPEND LIBVTERM_NAMES
- "${CMAKE_STATIC_LIBRARY_PREFIX}vterm${CMAKE_STATIC_LIBRARY_SUFFIX}")
-endif()
-
-list(APPEND LIBVTERM_NAMES vterm)
-
-find_library(LIBVTERM_LIBRARY NAMES ${LIBVTERM_NAMES}
- HINTS ${PC_LIBVTERM_LIBDIR} ${PC_LIBVTERM_LIBRARY_DIRS})
-
-set(LIBVTERM_LIBRARIES ${LIBVTERM_LIBRARY})
-set(LIBVTERM_INCLUDE_DIRS ${LIBVTERM_INCLUDE_DIR})
-
-include(FindPackageHandleStandardArgs)
-# handle the QUIETLY and REQUIRED arguments and set LIBVTERM_FOUND to TRUE
-# if all listed variables are TRUE
-find_package_handle_standard_args(LibVterm DEFAULT_MSG
- LIBVTERM_LIBRARY LIBVTERM_INCLUDE_DIR)
-
-mark_as_advanced(LIBVTERM_INCLUDE_DIR LIBVTERM_LIBRARY)
+libfind_pkg_detect(LIBVTERM vterm FIND_PATH vterm.h FIND_LIBRARY vterm)
+libfind_process(LIBVTERM REQUIRED)
diff --git a/cmake/FindLuaJit.cmake b/cmake/FindLuaJit.cmake
index d60b6f09be..72795afefd 100644
--- a/cmake/FindLuaJit.cmake
+++ b/cmake/FindLuaJit.cmake
@@ -15,13 +15,6 @@ find_path(LUAJIT_INCLUDE_DIR luajit.h
PATHS ${PC_LUAJIT_INCLUDEDIR} ${PC_LUAJIT_INCLUDE_DIRS}
PATH_SUFFIXES luajit-2.0 luajit-2.1)
-# If we're asked to use static linkage, add libluajit-5.1.a as a preferred
-# library name.
-if(LUAJIT_USE_STATIC)
- list(APPEND LUAJIT_NAMES
- "${CMAKE_STATIC_LIBRARY_PREFIX}luajit-5.1${CMAKE_STATIC_LIBRARY_SUFFIX}")
-endif()
-
if(MSVC)
list(APPEND LUAJIT_NAMES lua51)
elseif(MINGW)
diff --git a/cmake/FindMsgpack.cmake b/cmake/FindMsgpack.cmake
index df4efa9c41..26eb19d498 100644
--- a/cmake/FindMsgpack.cmake
+++ b/cmake/FindMsgpack.cmake
@@ -26,13 +26,6 @@ else()
set(MSGPACK_VERSION_STRING)
endif()
-# If we're asked to use static linkage, add libmsgpack{,c}.a as a preferred library name.
-if(MSGPACK_USE_STATIC)
- list(APPEND MSGPACK_NAMES
- "${CMAKE_STATIC_LIBRARY_PREFIX}msgpackc${CMAKE_STATIC_LIBRARY_SUFFIX}"
- "${CMAKE_STATIC_LIBRARY_PREFIX}msgpack${CMAKE_STATIC_LIBRARY_SUFFIX}")
-endif()
-
if(MSVC)
# The import library for the msgpack DLL has a different name
list(APPEND MSGPACK_NAMES msgpackc_import)
diff --git a/config/CMakeLists.txt b/config/CMakeLists.txt
index 82cb4d31ad..0ca41d5dfd 100644
--- a/config/CMakeLists.txt
+++ b/config/CMakeLists.txt
@@ -20,7 +20,6 @@ endif()
check_symbol_exists(_NSGetEnviron crt_externs.h HAVE__NSGETENVIRON)
# Headers
-check_include_files(iconv.h HAVE_ICONV_H)
check_include_files(langinfo.h HAVE_LANGINFO_H)
check_include_files(locale.h HAVE_LOCALE_H)
check_include_files(pwd.h HAVE_PWD_H)
diff --git a/config/config.h.in b/config/config.h.in
index 40baff95e8..3216ab7556 100644
--- a/config/config.h.in
+++ b/config/config.h.in
@@ -22,7 +22,6 @@
#cmakedefine HAVE_GETPWNAM
#cmakedefine HAVE_GETPWUID
#cmakedefine HAVE_ICONV
-#cmakedefine HAVE_ICONV_H
#cmakedefine HAVE_LANGINFO_H
#cmakedefine HAVE_LOCALE_H
#cmakedefine HAVE_NL_LANGINFO_CODESET
diff --git a/contrib/local.mk.example b/contrib/local.mk.example
index a8c8e9cefb..5a31ded59b 100644
--- a/contrib/local.mk.example
+++ b/contrib/local.mk.example
@@ -47,18 +47,6 @@
#
# DEPS_CMAKE_FLAGS += -DUSE_BUNDLED=OFF
-# By default, bundled libraries are statically linked to nvim.
-# This has no effect for non-bundled deps, which are always dynamically linked.
-# Uncomment these entries to instead use dynamic linking.
-#
-# CMAKE_EXTRA_FLAGS += -DLIBTERMKEY_USE_STATIC=OFF
-# CMAKE_EXTRA_FLAGS += -DLIBUNIBILIUM_USE_STATIC=OFF
-# CMAKE_EXTRA_FLAGS += -DLIBUV_USE_STATIC=OFF
-# CMAKE_EXTRA_FLAGS += -DLIBVTERM_USE_STATIC=OFF
-# CMAKE_EXTRA_FLAGS += -DLUAJIT_USE_STATIC=OFF
-# CMAKE_EXTRA_FLAGS += -DMSGPACK_USE_STATIC=OFF
-#
-#
# .DEFAULT_GOAL := nvim
#
# Run doxygen over the source code.
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt
index 709e5885e4..2c6b053994 100644
--- a/runtime/doc/api.txt
+++ b/runtime/doc/api.txt
@@ -200,17 +200,26 @@ User reloads the buffer with ":edit", emits: >
nvim_buf_detach_event[{buf}]
*api-buffer-updates-lua*
-In-process lua plugins can also recieve buffer updates, in the form of lua
+In-process lua plugins can also receive buffer updates, in the form of lua
callbacks. These callbacks are called frequently in various contexts, buffer
contents or window layout should not be changed inside these |textlock|.
|vim.schedule| can be used to defer these operations to the main loop, where
they are allowed.
|nvim_buf_attach| will take keyword args for the callbacks. "on_lines" will
-receive parameters ("lines", {buf}, {changedtick}, {firstline}, {lastline}, {new_lastline}).
-Unlike remote channels the text contents are not passed. The new text can be
-accessed inside the callback as
-`vim.api.nvim_buf_get_lines(buf, firstline, new_lastline, true)`
+receive parameters ("lines", {buf}, {changedtick}, {firstline}, {lastline},
+{new_lastline}, {old_byte_size}[, {old_utf32_size}, {old_utf16_size}]).
+Unlike remote channel events the text contents are not passed. The new text can
+be accessed inside the callback as
+
+ `vim.api.nvim_buf_get_lines(buf, firstline, new_lastline, true)`
+
+{old_byte_size} is the total size of the replaced region {firstline} to
+{lastline} in bytes, including the final newline after {lastline}. if
+`utf_sizes` is set to true in |nvim_buf_attach()| keyword args, then the
+UTF-32 and UTF-16 sizes of the deleted region is also passed as additional
+arguments {old_utf32_size} and {old_utf16_size}.
+
"on_changedtick" is invoked when |b:changedtick| was incremented but no text
was changed. The parameters recieved are ("changedtick", {buf}, {changedtick}).
diff --git a/runtime/doc/if_lua.txt b/runtime/doc/if_lua.txt
index 7ddcb6cc92..a9b8c5fae8 100644
--- a/runtime/doc/if_lua.txt
+++ b/runtime/doc/if_lua.txt
@@ -459,6 +459,24 @@ vim.stricmp({a}, {b}) *vim.stricmp()*
are equal, {a} is greater than {b} or {a} is lesser than {b},
respectively.
+vim.str_utfindex({str}[, {index}]) *vim.str_utfindex()*
+ Convert byte index to UTF-32 and UTF-16 indicies. If {index} is not
+ supplied, the length of the string is used. All indicies are zero-based.
+ Returns two values: the UTF-32 and UTF-16 indicies respectively.
+
+ Embedded NUL bytes are treated as terminating the string. Invalid
+ UTF-8 bytes, and embedded surrogates are counted as one code
+ point each. An {index} in the middle of a UTF-8 sequence is rounded
+ upwards to the end of that sequence.
+
+vim.str_byteindex({str}, {index}[, {use_utf16}]) *vim.str_byteindex()*
+ Convert UTF-32 or UTF-16 {index} to byte index. If {use_utf16} is not
+ supplied, it defaults to false (use UTF-32). Returns the byte index.
+
+ Invalid UTF-8 and NUL is treated like by |vim.str_byteindex()|. An {index}
+ in the middle of a UTF-16 sequence is rounded upwards to the end of that
+ sequence.
+
vim.schedule({callback}) *vim.schedule()*
Schedules {callback} to be invoked soon by the main event-loop. Useful
to avoid |textlock| or other temporary restrictions.
diff --git a/runtime/doc/job_control.txt b/runtime/doc/job_control.txt
index e5cd765e83..ae208c0755 100644
--- a/runtime/doc/job_control.txt
+++ b/runtime/doc/job_control.txt
@@ -67,7 +67,7 @@ For |on_stdout| and |on_stderr| see |channel-callback|.
*on_exit*
Arguments passed to on_exit callback:
0: |job-id|
- 1: Exit-code of the process.
+ 1: Exit-code of the process, or 128+SIGNUM if by signal (e.g. 143 on SIGTERM).
2: Event type: "exit"
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index b0b65545ab..41d7d8ba6b 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -15,6 +15,7 @@
#include "nvim/lua/executor.h"
#include "nvim/vim.h"
#include "nvim/buffer.h"
+#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/getchar.h"
@@ -109,9 +110,11 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err)
/// `nvim_buf_lines_event`. Otherwise, the first notification will be
/// a `nvim_buf_changedtick_event`. Not used for lua callbacks.
/// @param opts Optional parameters.
-/// `on_lines`: lua callback received on change.
+/// `on_lines`: lua callback received on change.
/// `on_changedtick`: lua callback received on changedtick
/// increment without text change.
+/// `utf_sizes`: include UTF-32 and UTF-16 size of
+/// the replaced region.
/// See |api-buffer-updates-lua| for more information
/// @param[out] err Error details, if any
/// @return False when updates couldn't be enabled because the buffer isn't
@@ -156,6 +159,12 @@ Boolean nvim_buf_attach(uint64_t channel_id,
}
cb.on_detach = v->data.luaref;
v->data.integer = LUA_NOREF;
+ } else if (is_lua && strequal("utf_sizes", k.data)) {
+ if (v->type != kObjectTypeBoolean) {
+ api_set_error(err, kErrorTypeValidation, "utf_sizes must be boolean");
+ goto error;
+ }
+ cb.utf_sizes = v->data.boolean;
} else {
api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
goto error;
@@ -1176,6 +1185,30 @@ free_exit:
return 0;
}
+Dictionary nvim__buf_stats(Buffer buffer, Error *err)
+{
+ Dictionary rv = ARRAY_DICT_INIT;
+
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ return rv;
+ }
+
+ // Number of times the cached line was flushed.
+ // This should generally not increase while editing the same
+ // line in the same mode.
+ PUT(rv, "flush_count", INTEGER_OBJ(buf->flush_count));
+ // lnum of current line
+ PUT(rv, "current_lnum", INTEGER_OBJ(buf->b_ml.ml_line_lnum));
+ // whether the line has unflushed changes.
+ PUT(rv, "line_dirty", BOOLEAN_OBJ(buf->b_ml.ml_flags & ML_LINE_DIRTY));
+ // NB: this should be zero at any time API functions are called,
+ // this exists to debug issues
+ PUT(rv, "dirty_bytes", INTEGER_OBJ((Integer)buf->deleted_bytes));
+
+ return rv;
+}
+
// Check if deleting lines made the cursor position invalid.
// Changed lines from `lo` to `hi`; added `extra` lines (negative if deleted).
static void fix_cursor(linenr_T lo, linenr_T hi, linenr_T extra)
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index 89f1e33a86..a545112360 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -32,6 +32,7 @@
#include "nvim/channel.h"
#include "nvim/vim.h"
#include "nvim/buffer.h"
+#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/diff.h"
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index 143737b478..b11eaefdd0 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -459,8 +459,9 @@ typedef struct {
LuaRef on_lines;
LuaRef on_changedtick;
LuaRef on_detach;
+ bool utf_sizes;
} BufUpdateCallbacks;
-#define BUF_UPDATE_CALLBACKS_INIT { LUA_NOREF, LUA_NOREF, LUA_NOREF }
+#define BUF_UPDATE_CALLBACKS_INIT { LUA_NOREF, LUA_NOREF, LUA_NOREF, false }
#define BUF_HAS_QF_ENTRY 1
#define BUF_HAS_LL_ENTRY 2
@@ -802,11 +803,26 @@ struct file_buffer {
kvec_t(BufhlLine *) b_bufhl_move_space; // temporary space for highlights
- // array of channelids which have asked to receive updates for this
+ // array of channel_id:s which have asked to receive updates for this
// buffer.
kvec_t(uint64_t) update_channels;
+ // array of lua callbacks for buffer updates.
kvec_t(BufUpdateCallbacks) update_callbacks;
+ // whether an update callback has requested codepoint size of deleted regions.
+ bool update_need_codepoints;
+
+ // Measurements of the deleted or replaced region since the last update
+ // event. Some consumers of buffer changes need to know the byte size (like
+ // tree-sitter) or the corresponding UTF-32/UTF-16 size (like LSP) of the
+ // deleted text.
+ size_t deleted_bytes;
+ size_t deleted_codepoints;
+ size_t deleted_codeunits;
+
+ // The number for times the current line has been flushed in the memline.
+ int flush_count;
+
int b_diff_failed; // internal diff failed for this buffer
};
diff --git a/src/nvim/buffer_updates.c b/src/nvim/buffer_updates.c
index 21efda9fd9..3604578b50 100644
--- a/src/nvim/buffer_updates.c
+++ b/src/nvim/buffer_updates.c
@@ -26,6 +26,9 @@ bool buf_updates_register(buf_T *buf, uint64_t channel_id,
if (channel_id == LUA_INTERNAL_CALL) {
kv_push(buf->update_callbacks, cb);
+ if (cb.utf_sizes) {
+ buf->update_need_codepoints = true;
+ }
return true;
}
@@ -169,6 +172,10 @@ void buf_updates_send_changes(buf_T *buf,
int64_t num_removed,
bool send_tick)
{
+ size_t deleted_codepoints, deleted_codeunits;
+ size_t deleted_bytes = ml_flush_deleted_bytes(buf, &deleted_codepoints,
+ &deleted_codeunits);
+
if (!buf_updates_active(buf)) {
return;
}
@@ -231,8 +238,8 @@ void buf_updates_send_changes(buf_T *buf,
bool keep = true;
if (cb.on_lines != LUA_NOREF) {
Array args = ARRAY_DICT_INIT;
- Object items[5];
- args.size = 5;
+ Object items[8];
+ args.size = 6; // may be increased to 8 below
args.items = items;
// the first argument is always the buffer handle
@@ -250,6 +257,13 @@ void buf_updates_send_changes(buf_T *buf,
// the last line in the updated range
args.items[4] = INTEGER_OBJ(firstline - 1 + num_added);
+ // byte count of previous contents
+ args.items[5] = INTEGER_OBJ((Integer)deleted_bytes);
+ if (cb.utf_sizes) {
+ args.size = 8;
+ args.items[6] = INTEGER_OBJ((Integer)deleted_codepoints);
+ args.items[7] = INTEGER_OBJ((Integer)deleted_codeunits);
+ }
textlock++;
Object res = executor_exec_lua_cb(cb.on_lines, "lines", args, true);
textlock--;
diff --git a/src/nvim/change.c b/src/nvim/change.c
new file mode 100644
index 0000000000..2363578139
--- /dev/null
+++ b/src/nvim/change.c
@@ -0,0 +1,1798 @@
+// This is an open source non-commercial project. Dear PVS-Studio, please check
+// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+
+/// change.c: functions related to changing text
+
+#include "nvim/assert.h"
+#include "nvim/buffer.h"
+#include "nvim/buffer_updates.h"
+#include "nvim/change.h"
+#include "nvim/charset.h"
+#include "nvim/cursor.h"
+#include "nvim/diff.h"
+#include "nvim/edit.h"
+#include "nvim/eval.h"
+#include "nvim/fileio.h"
+#include "nvim/fold.h"
+#include "nvim/indent.h"
+#include "nvim/indent_c.h"
+#include "nvim/mark.h"
+#include "nvim/memline.h"
+#include "nvim/misc1.h"
+#include "nvim/move.h"
+#include "nvim/option.h"
+#include "nvim/screen.h"
+#include "nvim/search.h"
+#include "nvim/state.h"
+#include "nvim/ui.h"
+#include "nvim/undo.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "change.c.generated.h"
+#endif
+
+/// If the file is readonly, give a warning message with the first change.
+/// Don't do this for autocommands.
+/// Doesn't use emsg(), because it flushes the macro buffer.
+/// If we have undone all changes b_changed will be false, but "b_did_warn"
+/// will be true.
+/// "col" is the column for the message; non-zero when in insert mode and
+/// 'showmode' is on.
+/// Careful: may trigger autocommands that reload the buffer.
+void change_warning(int col)
+{
+ static char *w_readonly = N_("W10: Warning: Changing a readonly file");
+
+ if (curbuf->b_did_warn == false
+ && curbufIsChanged() == 0
+ && !autocmd_busy
+ && curbuf->b_p_ro) {
+ curbuf_lock++;
+ apply_autocmds(EVENT_FILECHANGEDRO, NULL, NULL, false, curbuf);
+ curbuf_lock--;
+ if (!curbuf->b_p_ro) {
+ return;
+ }
+ // Do what msg() does, but with a column offset if the warning should
+ // be after the mode message.
+ msg_start();
+ if (msg_row == Rows - 1) {
+ msg_col = col;
+ }
+ msg_source(HL_ATTR(HLF_W));
+ msg_ext_set_kind("wmsg");
+ MSG_PUTS_ATTR(_(w_readonly), HL_ATTR(HLF_W) | MSG_HIST);
+ set_vim_var_string(VV_WARNINGMSG, _(w_readonly), -1);
+ msg_clr_eos();
+ (void)msg_end();
+ if (msg_silent == 0 && !silent_mode && ui_active()) {
+ ui_flush();
+ os_delay(1000L, true); // give the user time to think about it
+ }
+ curbuf->b_did_warn = true;
+ redraw_cmdline = false; // don't redraw and erase the message
+ if (msg_row < Rows - 1) {
+ showmode();
+ }
+ }
+}
+
+/// Call this function when something in the current buffer is changed.
+///
+/// Most often called through changed_bytes() and changed_lines(), which also
+/// mark the area of the display to be redrawn.
+///
+/// Careful: may trigger autocommands that reload the buffer.
+void changed(void)
+{
+ if (!curbuf->b_changed) {
+ int save_msg_scroll = msg_scroll;
+
+ // Give a warning about changing a read-only file. This may also
+ // check-out the file, thus change "curbuf"!
+ change_warning(0);
+
+ // Create a swap file if that is wanted.
+ // Don't do this for "nofile" and "nowrite" buffer types.
+ if (curbuf->b_may_swap
+ && !bt_dontwrite(curbuf)
+ ) {
+ int save_need_wait_return = need_wait_return;
+
+ need_wait_return = false;
+ ml_open_file(curbuf);
+
+ // The ml_open_file() can cause an ATTENTION message.
+ // Wait two seconds, to make sure the user reads this unexpected
+ // message. Since we could be anywhere, call wait_return() now,
+ // and don't let the emsg() set msg_scroll.
+ if (need_wait_return && emsg_silent == 0) {
+ ui_flush();
+ os_delay(2000L, true);
+ wait_return(true);
+ msg_scroll = save_msg_scroll;
+ } else {
+ need_wait_return = save_need_wait_return;
+ }
+ }
+ changed_internal();
+ }
+ buf_inc_changedtick(curbuf);
+
+ // If a pattern is highlighted, the position may now be invalid.
+ highlight_match = false;
+}
+
+/// Internal part of changed(), no user interaction.
+/// Also used for recovery.
+void changed_internal(void)
+{
+ curbuf->b_changed = true;
+ ml_setflags(curbuf);
+ check_status(curbuf);
+ redraw_tabline = true;
+ need_maketitle = true; // set window title later
+}
+
+/// Common code for when a change was made.
+/// See changed_lines() for the arguments.
+/// Careful: may trigger autocommands that reload the buffer.
+static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume,
+ long xtra)
+{
+ int i;
+ int cols;
+ pos_T *p;
+ int add;
+
+ // mark the buffer as modified
+ changed();
+
+ if (curwin->w_p_diff && diff_internal()) {
+ curtab->tp_diff_update = true;
+ }
+
+ // set the '. mark
+ if (!cmdmod.keepjumps) {
+ RESET_FMARK(&curbuf->b_last_change, ((pos_T) { lnum, col, 0 }), 0);
+
+ // Create a new entry if a new undo-able change was started or we
+ // don't have an entry yet.
+ if (curbuf->b_new_change || curbuf->b_changelistlen == 0) {
+ if (curbuf->b_changelistlen == 0) {
+ add = true;
+ } else {
+ // Don't create a new entry when the line number is the same
+ // as the last one and the column is not too far away. Avoids
+ // creating many entries for typing "xxxxx".
+ p = &curbuf->b_changelist[curbuf->b_changelistlen - 1].mark;
+ if (p->lnum != lnum) {
+ add = true;
+ } else {
+ cols = comp_textwidth(false);
+ if (cols == 0) {
+ cols = 79;
+ }
+ add = (p->col + cols < col || col + cols < p->col);
+ }
+ }
+ if (add) {
+ // This is the first of a new sequence of undo-able changes
+ // and it's at some distance of the last change. Use a new
+ // position in the changelist.
+ curbuf->b_new_change = false;
+
+ if (curbuf->b_changelistlen == JUMPLISTSIZE) {
+ // changelist is full: remove oldest entry
+ curbuf->b_changelistlen = JUMPLISTSIZE - 1;
+ memmove(curbuf->b_changelist, curbuf->b_changelist + 1,
+ sizeof(curbuf->b_changelist[0]) * (JUMPLISTSIZE - 1));
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ // Correct position in changelist for other windows on
+ // this buffer.
+ if (wp->w_buffer == curbuf && wp->w_changelistidx > 0) {
+ wp->w_changelistidx--;
+ }
+ }
+ }
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ // For other windows, if the position in the changelist is
+ // at the end it stays at the end.
+ if (wp->w_buffer == curbuf
+ && wp->w_changelistidx == curbuf->b_changelistlen) {
+ wp->w_changelistidx++;
+ }
+ }
+ curbuf->b_changelistlen++;
+ }
+ }
+ curbuf->b_changelist[curbuf->b_changelistlen - 1] =
+ curbuf->b_last_change;
+ // The current window is always after the last change, so that "g,"
+ // takes you back to it.
+ curwin->w_changelistidx = curbuf->b_changelistlen;
+ }
+
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ if (wp->w_buffer == curbuf) {
+ // Mark this window to be redrawn later.
+ if (wp->w_redr_type < VALID) {
+ wp->w_redr_type = VALID;
+ }
+
+ // Check if a change in the buffer has invalidated the cached
+ // values for the cursor.
+ // Update the folds for this window. Can't postpone this, because
+ // a following operator might work on the whole fold: ">>dd".
+ foldUpdate(wp, lnum, lnume + xtra - 1);
+
+ // The change may cause lines above or below the change to become
+ // included in a fold. Set lnum/lnume to the first/last line that
+ // might be displayed differently.
+ // Set w_cline_folded here as an efficient way to update it when
+ // inserting lines just above a closed fold. */
+ bool folded = hasFoldingWin(wp, lnum, &lnum, NULL, false, NULL);
+ if (wp->w_cursor.lnum == lnum) {
+ wp->w_cline_folded = folded;
+ }
+ folded = hasFoldingWin(wp, lnume, NULL, &lnume, false, NULL);
+ if (wp->w_cursor.lnum == lnume) {
+ wp->w_cline_folded = folded;
+ }
+
+ // If the changed line is in a range of previously folded lines,
+ // compare with the first line in that range.
+ if (wp->w_cursor.lnum <= lnum) {
+ i = find_wl_entry(wp, lnum);
+ if (i >= 0 && wp->w_cursor.lnum > wp->w_lines[i].wl_lnum) {
+ changed_line_abv_curs_win(wp);
+ }
+ }
+
+ if (wp->w_cursor.lnum > lnum) {
+ changed_line_abv_curs_win(wp);
+ } else if (wp->w_cursor.lnum == lnum && wp->w_cursor.col >= col) {
+ changed_cline_bef_curs_win(wp);
+ }
+ if (wp->w_botline >= lnum) {
+ // Assume that botline doesn't change (inserted lines make
+ // other lines scroll down below botline).
+ approximate_botline_win(wp);
+ }
+
+ // Check if any w_lines[] entries have become invalid.
+ // For entries below the change: Correct the lnums for
+ // inserted/deleted lines. Makes it possible to stop displaying
+ // after the change.
+ for (i = 0; i < wp->w_lines_valid; i++) {
+ if (wp->w_lines[i].wl_valid) {
+ if (wp->w_lines[i].wl_lnum >= lnum) {
+ if (wp->w_lines[i].wl_lnum < lnume) {
+ // line included in change
+ wp->w_lines[i].wl_valid = false;
+ } else if (xtra != 0) {
+ // line below change
+ wp->w_lines[i].wl_lnum += xtra;
+ wp->w_lines[i].wl_lastlnum += xtra;
+ }
+ } else if (wp->w_lines[i].wl_lastlnum >= lnum) {
+ // change somewhere inside this range of folded lines,
+ // may need to be redrawn
+ wp->w_lines[i].wl_valid = false;
+ }
+ }
+ }
+
+ // Take care of side effects for setting w_topline when folds have
+ // changed. Esp. when the buffer was changed in another window.
+ if (hasAnyFolding(wp)) {
+ set_topline(wp, wp->w_topline);
+ }
+
+ // relative numbering may require updating more
+ if (wp->w_p_rnu) {
+ redraw_win_later(wp, SOME_VALID);
+ }
+ }
+ }
+
+ // Call update_screen() later, which checks out what needs to be redrawn,
+ // since it notices b_mod_set and then uses b_mod_*.
+ if (must_redraw < VALID) {
+ must_redraw = VALID;
+ }
+
+ // when the cursor line is changed always trigger CursorMoved
+ if (lnum <= curwin->w_cursor.lnum
+ && lnume + (xtra < 0 ? -xtra : xtra) > curwin->w_cursor.lnum) {
+ curwin->w_last_cursormoved.lnum = 0;
+ }
+}
+
+static void changedOneline(buf_T *buf, linenr_T lnum)
+{
+ if (buf->b_mod_set) {
+ // find the maximum area that must be redisplayed
+ if (lnum < buf->b_mod_top) {
+ buf->b_mod_top = lnum;
+ } else if (lnum >= buf->b_mod_bot) {
+ buf->b_mod_bot = lnum + 1;
+ }
+ } else {
+ // set the area that must be redisplayed to one line
+ buf->b_mod_set = true;
+ buf->b_mod_top = lnum;
+ buf->b_mod_bot = lnum + 1;
+ buf->b_mod_xlines = 0;
+ }
+}
+
+/// Changed bytes within a single line for the current buffer.
+/// - marks the windows on this buffer to be redisplayed
+/// - marks the buffer changed by calling changed()
+/// - invalidates cached values
+/// Careful: may trigger autocommands that reload the buffer.
+void changed_bytes(linenr_T lnum, colnr_T col)
+{
+ changedOneline(curbuf, lnum);
+ changed_common(lnum, col, lnum + 1, 0L);
+ // notify any channels that are watching
+ buf_updates_send_changes(curbuf, lnum, 1, 1, true);
+
+ // Diff highlighting in other diff windows may need to be updated too.
+ if (curwin->w_p_diff) {
+ linenr_T wlnum;
+
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (wp->w_p_diff && wp != curwin) {
+ redraw_win_later(wp, VALID);
+ wlnum = diff_lnum_win(lnum, wp);
+ if (wlnum > 0) {
+ changedOneline(wp->w_buffer, wlnum);
+ }
+ }
+ }
+ }
+}
+
+/// Appended "count" lines below line "lnum" in the current buffer.
+/// Must be called AFTER the change and after mark_adjust().
+/// Takes care of marking the buffer to be redrawn and sets the changed flag.
+void appended_lines(linenr_T lnum, long count)
+{
+ changed_lines(lnum + 1, 0, lnum + 1, count, true);
+}
+
+/// Like appended_lines(), but adjust marks first.
+void appended_lines_mark(linenr_T lnum, long count)
+{
+ // Skip mark_adjust when adding a line after the last one, there can't
+ // be marks there. But it's still needed in diff mode.
+ if (lnum + count < curbuf->b_ml.ml_line_count || curwin->w_p_diff) {
+ mark_adjust(lnum + 1, (linenr_T)MAXLNUM, count, 0L, false);
+ }
+ changed_lines(lnum + 1, 0, lnum + 1, count, true);
+}
+
+/// Deleted "count" lines at line "lnum" in the current buffer.
+/// Must be called AFTER the change and after mark_adjust().
+/// Takes care of marking the buffer to be redrawn and sets the changed flag.
+void deleted_lines(linenr_T lnum, long count)
+{
+ changed_lines(lnum, 0, lnum + count, -count, true);
+}
+
+/// Like deleted_lines(), but adjust marks first.
+/// Make sure the cursor is on a valid line before calling, a GUI callback may
+/// be triggered to display the cursor.
+void deleted_lines_mark(linenr_T lnum, long count)
+{
+ mark_adjust(lnum, (linenr_T)(lnum + count - 1), (long)MAXLNUM, -count, false);
+ changed_lines(lnum, 0, lnum + count, -count, true);
+}
+
+/// Marks the area to be redrawn after a change.
+///
+/// @param buf the buffer where lines were changed
+/// @param lnum first line with change
+/// @param lnume line below last changed line
+/// @param xtra number of extra lines (negative when deleting)
+void changed_lines_buf(buf_T *buf, linenr_T lnum, linenr_T lnume, long xtra)
+{
+ if (buf->b_mod_set) {
+ // find the maximum area that must be redisplayed
+ if (lnum < buf->b_mod_top) {
+ buf->b_mod_top = lnum;
+ }
+ if (lnum < buf->b_mod_bot) {
+ // adjust old bot position for xtra lines
+ buf->b_mod_bot += xtra;
+ if (buf->b_mod_bot < lnum) {
+ buf->b_mod_bot = lnum;
+ }
+ }
+ if (lnume + xtra > buf->b_mod_bot) {
+ buf->b_mod_bot = lnume + xtra;
+ }
+ buf->b_mod_xlines += xtra;
+ } else {
+ // set the area that must be redisplayed
+ buf->b_mod_set = true;
+ buf->b_mod_top = lnum;
+ buf->b_mod_bot = lnume + xtra;
+ buf->b_mod_xlines = xtra;
+ }
+}
+
+/// Changed lines for the current buffer.
+/// Must be called AFTER the change and after mark_adjust().
+/// - mark the buffer changed by calling changed()
+/// - mark the windows on this buffer to be redisplayed
+/// - invalidate cached values
+/// "lnum" is the first line that needs displaying, "lnume" the first line
+/// below the changed lines (BEFORE the change).
+/// When only inserting lines, "lnum" and "lnume" are equal.
+/// Takes care of calling changed() and updating b_mod_*.
+/// Careful: may trigger autocommands that reload the buffer.
+void
+changed_lines(
+ linenr_T lnum, // first line with change
+ colnr_T col, // column in first line with change
+ linenr_T lnume, // line below last changed line
+ long xtra, // number of extra lines (negative when deleting)
+ bool do_buf_event // some callers like undo/redo call changed_lines()
+ // and then increment changedtick *again*. This flag
+ // allows these callers to send the nvim_buf_lines_event
+ // events after they're done modifying changedtick.
+)
+{
+ changed_lines_buf(curbuf, lnum, lnume, xtra);
+
+ if (xtra == 0 && curwin->w_p_diff && !diff_internal()) {
+ // When the number of lines doesn't change then mark_adjust() isn't
+ // called and other diff buffers still need to be marked for
+ // displaying.
+ linenr_T wlnum;
+
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (wp->w_p_diff && wp != curwin) {
+ redraw_win_later(wp, VALID);
+ wlnum = diff_lnum_win(lnum, wp);
+ if (wlnum > 0) {
+ changed_lines_buf(wp->w_buffer, wlnum,
+ lnume - lnum + wlnum, 0L);
+ }
+ }
+ }
+ }
+
+ changed_common(lnum, col, lnume, xtra);
+
+ if (do_buf_event) {
+ int64_t num_added = (int64_t)(lnume + xtra - lnum);
+ int64_t num_removed = lnume - lnum;
+ buf_updates_send_changes(curbuf, lnum, num_added, num_removed, true);
+ }
+}
+
+/// Called when the changed flag must be reset for buffer "buf".
+/// When "ff" is true also reset 'fileformat'.
+void unchanged(buf_T *buf, int ff)
+{
+ if (buf->b_changed || (ff && file_ff_differs(buf, false))) {
+ buf->b_changed = false;
+ ml_setflags(buf);
+ if (ff) {
+ save_file_ff(buf);
+ }
+ check_status(buf);
+ redraw_tabline = true;
+ need_maketitle = true; // set window title later
+ }
+ buf_inc_changedtick(buf);
+}
+
+/// Insert string "p" at the cursor position. Stops at a NUL byte.
+/// Handles Replace mode and multi-byte characters.
+void ins_bytes(char_u *p)
+{
+ ins_bytes_len(p, STRLEN(p));
+}
+
+/// Insert string "p" with length "len" at the cursor position.
+/// Handles Replace mode and multi-byte characters.
+void ins_bytes_len(char_u *p, size_t len)
+{
+ size_t n;
+ for (size_t i = 0; i < len; i += n) {
+ if (enc_utf8) {
+ // avoid reading past p[len]
+ n = (size_t)utfc_ptr2len_len(p + i, (int)(len - i));
+ } else {
+ n = (size_t)(*mb_ptr2len)(p + i);
+ }
+ ins_char_bytes(p + i, n);
+ }
+}
+
+/// Insert or replace a single character at the cursor position.
+/// When in REPLACE or VREPLACE mode, replace any existing character.
+/// Caller must have prepared for undo.
+/// For multi-byte characters we get the whole character, the caller must
+/// convert bytes to a character.
+void ins_char(int c)
+{
+ char_u buf[MB_MAXBYTES + 1];
+ size_t n = (size_t)utf_char2bytes(c, buf);
+
+ // When "c" is 0x100, 0x200, etc. we don't want to insert a NUL byte.
+ // Happens for CTRL-Vu9900.
+ if (buf[0] == 0) {
+ buf[0] = '\n';
+ }
+ ins_char_bytes(buf, n);
+}
+
+void ins_char_bytes(char_u *buf, size_t charlen)
+{
+ // Break tabs if needed.
+ if (virtual_active() && curwin->w_cursor.coladd > 0) {
+ coladvance_force(getviscol());
+ }
+
+ size_t col = (size_t)curwin->w_cursor.col;
+ linenr_T lnum = curwin->w_cursor.lnum;
+ char_u *oldp = ml_get(lnum);
+ size_t linelen = STRLEN(oldp) + 1; // length of old line including NUL
+
+ // The lengths default to the values for when not replacing.
+ size_t oldlen = 0; // nr of bytes inserted
+ size_t newlen = charlen; // nr of bytes deleted (0 when not replacing)
+
+ if (State & REPLACE_FLAG) {
+ if (State & VREPLACE_FLAG) {
+ // Disable 'list' temporarily, unless 'cpo' contains the 'L' flag.
+ // Returns the old value of list, so when finished,
+ // curwin->w_p_list should be set back to this.
+ int old_list = curwin->w_p_list;
+ if (old_list && vim_strchr(p_cpo, CPO_LISTWM) == NULL) {
+ curwin->w_p_list = false;
+ }
+ // In virtual replace mode each character may replace one or more
+ // characters (zero if it's a TAB). Count the number of bytes to
+ // be deleted to make room for the new character, counting screen
+ // cells. May result in adding spaces to fill a gap.
+ colnr_T vcol;
+ getvcol(curwin, &curwin->w_cursor, NULL, &vcol, NULL);
+ colnr_T new_vcol = vcol + chartabsize(buf, vcol);
+ while (oldp[col + oldlen] != NUL && vcol < new_vcol) {
+ vcol += chartabsize(oldp + col + oldlen, vcol);
+ // Don't need to remove a TAB that takes us to the right
+ // position.
+ if (vcol > new_vcol && oldp[col + oldlen] == TAB) {
+ break;
+ }
+ oldlen += (size_t)(*mb_ptr2len)(oldp + col + oldlen);
+ // Deleted a bit too much, insert spaces.
+ if (vcol > new_vcol) {
+ newlen += (size_t)(vcol - new_vcol);
+ }
+ }
+ curwin->w_p_list = old_list;
+ } else if (oldp[col] != NUL) {
+ // normal replace
+ oldlen = (size_t)(*mb_ptr2len)(oldp + col);
+ }
+
+
+ // Push the replaced bytes onto the replace stack, so that they can be
+ // put back when BS is used. The bytes of a multi-byte character are
+ // done the other way around, so that the first byte is popped off
+ // first (it tells the byte length of the character).
+ replace_push(NUL);
+ for (size_t i = 0; i < oldlen; i++) {
+ i += (size_t)replace_push_mb(oldp + col + i) - 1;
+ }
+ }
+
+ char_u *newp = xmalloc((size_t)(linelen + newlen - oldlen));
+
+ // Copy bytes before the cursor.
+ if (col > 0) {
+ memmove(newp, oldp, (size_t)col);
+ }
+
+ // Copy bytes after the changed character(s).
+ char_u *p = newp + col;
+ if (linelen > col + oldlen) {
+ memmove(p + newlen, oldp + col + oldlen,
+ (size_t)(linelen - col - oldlen));
+ }
+
+ // Insert or overwrite the new character.
+ memmove(p, buf, charlen);
+
+ // Fill with spaces when necessary.
+ for (size_t i = charlen; i < newlen; i++) {
+ p[i] = ' ';
+ }
+
+ // Replace the line in the buffer.
+ ml_replace(lnum, newp, false);
+
+ // mark the buffer as changed and prepare for displaying
+ changed_bytes(lnum, (colnr_T)col);
+
+ // If we're in Insert or Replace mode and 'showmatch' is set, then briefly
+ // show the match for right parens and braces.
+ if (p_sm && (State & INSERT)
+ && msg_silent == 0
+ && !ins_compl_active()
+ ) {
+ showmatch(utf_ptr2char(buf));
+ }
+
+ if (!p_ri || (State & REPLACE_FLAG)) {
+ // Normal insert: move cursor right
+ curwin->w_cursor.col += (int)charlen;
+ }
+ // TODO(Bram): should try to update w_row here, to avoid recomputing it later.
+}
+
+/// Insert a string at the cursor position.
+/// Note: Does NOT handle Replace mode.
+/// Caller must have prepared for undo.
+void ins_str(char_u *s)
+{
+ char_u *oldp, *newp;
+ int newlen = (int)STRLEN(s);
+ int oldlen;
+ colnr_T col;
+ linenr_T lnum = curwin->w_cursor.lnum;
+
+ if (virtual_active() && curwin->w_cursor.coladd > 0) {
+ coladvance_force(getviscol());
+ }
+
+ col = curwin->w_cursor.col;
+ oldp = ml_get(lnum);
+ oldlen = (int)STRLEN(oldp);
+
+ newp = (char_u *)xmalloc((size_t)oldlen + (size_t)newlen + 1);
+ if (col > 0) {
+ memmove(newp, oldp, (size_t)col);
+ }
+ memmove(newp + col, s, (size_t)newlen);
+ int bytes = oldlen - col + 1;
+ assert(bytes >= 0);
+ memmove(newp + col + newlen, oldp + col, (size_t)bytes);
+ ml_replace(lnum, newp, false);
+ changed_bytes(lnum, col);
+ curwin->w_cursor.col += newlen;
+}
+
+// Delete one character under the cursor.
+// If "fixpos" is true, don't leave the cursor on the NUL after the line.
+// Caller must have prepared for undo.
+//
+// return FAIL for failure, OK otherwise
+int del_char(bool fixpos)
+{
+ // Make sure the cursor is at the start of a character.
+ mb_adjust_cursor();
+ if (*get_cursor_pos_ptr() == NUL) {
+ return FAIL;
+ }
+ return del_chars(1L, fixpos);
+}
+
+/// Like del_bytes(), but delete characters instead of bytes.
+int del_chars(long count, int fixpos)
+{
+ int bytes = 0;
+ long i;
+ char_u *p;
+ int l;
+
+ p = get_cursor_pos_ptr();
+ for (i = 0; i < count && *p != NUL; i++) {
+ l = (*mb_ptr2len)(p);
+ bytes += l;
+ p += l;
+ }
+ return del_bytes(bytes, fixpos, true);
+}
+
+/// Delete "count" bytes under the cursor.
+/// If "fixpos" is true, don't leave the cursor on the NUL after the line.
+/// Caller must have prepared for undo.
+///
+/// @param count number of bytes to be deleted
+/// @param fixpos_arg leave the cursor on the NUL after the line
+/// @param use_delcombine 'delcombine' option applies
+///
+/// @return FAIL for failure, OK otherwise
+int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine)
+{
+ linenr_T lnum = curwin->w_cursor.lnum;
+ colnr_T col = curwin->w_cursor.col;
+ bool fixpos = fixpos_arg;
+ char_u *oldp = ml_get(lnum);
+ colnr_T oldlen = (colnr_T)STRLEN(oldp);
+
+ // Can't do anything when the cursor is on the NUL after the line.
+ if (col >= oldlen) {
+ return FAIL;
+ }
+ // If "count" is zero there is nothing to do.
+ if (count == 0) {
+ return OK;
+ }
+ // If "count" is negative the caller must be doing something wrong.
+ if (count < 1) {
+ IEMSGN("E950: Invalid count for del_bytes(): %ld", count);
+ return FAIL;
+ }
+
+ // If 'delcombine' is set and deleting (less than) one character, only
+ // delete the last combining character.
+ if (p_deco && use_delcombine && enc_utf8
+ && utfc_ptr2len(oldp + col) >= count) {
+ int cc[MAX_MCO];
+ int n;
+
+ (void)utfc_ptr2char(oldp + col, cc);
+ if (cc[0] != NUL) {
+ // Find the last composing char, there can be several.
+ n = col;
+ do {
+ col = n;
+ count = utf_ptr2len(oldp + n);
+ n += count;
+ } while (UTF_COMPOSINGLIKE(oldp + col, oldp + n));
+ fixpos = false;
+ }
+ }
+
+ // When count is too big, reduce it.
+ int movelen = oldlen - col - count + 1; // includes trailing NUL
+ if (movelen <= 1) {
+ // If we just took off the last character of a non-blank line, and
+ // fixpos is TRUE, we don't want to end up positioned at the NUL,
+ // unless "restart_edit" is set or 'virtualedit' contains "onemore".
+ if (col > 0 && fixpos && restart_edit == 0
+ && (ve_flags & VE_ONEMORE) == 0
+ ) {
+ curwin->w_cursor.col--;
+ curwin->w_cursor.coladd = 0;
+ curwin->w_cursor.col -= utf_head_off(oldp, oldp + curwin->w_cursor.col);
+ }
+ count = oldlen - col;
+ movelen = 1;
+ }
+
+ // If the old line has been allocated the deletion can be done in the
+ // existing line. Otherwise a new line has to be allocated.
+ bool was_alloced = ml_line_alloced(); // check if oldp was allocated
+ char_u *newp;
+ if (was_alloced) {
+ ml_add_deleted_len(curbuf->b_ml.ml_line_ptr, oldlen);
+ newp = oldp; // use same allocated memory
+ } else { // need to allocate a new line
+ newp = xmalloc((size_t)(oldlen + 1 - count));
+ memmove(newp, oldp, (size_t)col);
+ }
+ memmove(newp + col, oldp + col + count, (size_t)movelen);
+ if (!was_alloced) {
+ ml_replace(lnum, newp, false);
+ }
+
+ // mark the buffer as changed and prepare for displaying
+ changed_bytes(lnum, curwin->w_cursor.col);
+
+ return OK;
+}
+
+/// Copy the indent from ptr to the current line (and fill to size).
+/// Leaves the cursor on the first non-blank in the line.
+/// @return true if the line was changed.
+int copy_indent(int size, char_u *src)
+{
+ char_u *p = NULL;
+ char_u *line = NULL;
+ char_u *s;
+ int todo;
+ int ind_len;
+ int line_len = 0;
+ int tab_pad;
+ int ind_done;
+ int round;
+
+ // Round 1: compute the number of characters needed for the indent
+ // Round 2: copy the characters.
+ for (round = 1; round <= 2; round++) {
+ todo = size;
+ ind_len = 0;
+ ind_done = 0;
+ s = src;
+
+ // Count/copy the usable portion of the source line.
+ while (todo > 0 && ascii_iswhite(*s)) {
+ if (*s == TAB) {
+ tab_pad = (int)curbuf->b_p_ts
+ - (ind_done % (int)curbuf->b_p_ts);
+
+ // Stop if this tab will overshoot the target.
+ if (todo < tab_pad) {
+ break;
+ }
+ todo -= tab_pad;
+ ind_done += tab_pad;
+ } else {
+ todo--;
+ ind_done++;
+ }
+ ind_len++;
+
+ if (p != NULL) {
+ *p++ = *s;
+ }
+ s++;
+ }
+
+ // Fill to next tabstop with a tab, if possible.
+ tab_pad = (int)curbuf->b_p_ts - (ind_done % (int)curbuf->b_p_ts);
+
+ if ((todo >= tab_pad) && !curbuf->b_p_et) {
+ todo -= tab_pad;
+ ind_len++;
+
+ if (p != NULL) {
+ *p++ = TAB;
+ }
+ }
+
+ // Add tabs required for indent.
+ while (todo >= (int)curbuf->b_p_ts && !curbuf->b_p_et) {
+ todo -= (int)curbuf->b_p_ts;
+ ind_len++;
+
+ if (p != NULL) {
+ *p++ = TAB;
+ }
+ }
+
+ // Count/add spaces required for indent.
+ while (todo > 0) {
+ todo--;
+ ind_len++;
+
+ if (p != NULL) {
+ *p++ = ' ';
+ }
+ }
+
+ if (p == NULL) {
+ // Allocate memory for the result: the copied indent, new indent
+ // and the rest of the line.
+ line_len = (int)STRLEN(get_cursor_line_ptr()) + 1;
+ assert(ind_len + line_len >= 0);
+ size_t line_size;
+ STRICT_ADD(ind_len, line_len, &line_size, size_t);
+ line = xmalloc(line_size);
+ p = line;
+ }
+ }
+
+ // Append the original line
+ memmove(p, get_cursor_line_ptr(), (size_t)line_len);
+
+ // Replace the line
+ ml_replace(curwin->w_cursor.lnum, line, false);
+
+ // Put the cursor after the indent.
+ curwin->w_cursor.col = ind_len;
+ return true;
+}
+
+/// open_line: Add a new line below or above the current line.
+///
+/// For VREPLACE mode, we only add a new line when we get to the end of the
+/// file, otherwise we just start replacing the next line.
+///
+/// Caller must take care of undo. Since VREPLACE may affect any number of
+/// lines however, it may call u_save_cursor() again when starting to change a
+/// new line.
+/// "flags": OPENLINE_DELSPACES delete spaces after cursor
+/// OPENLINE_DO_COM format comments
+/// OPENLINE_KEEPTRAIL keep trailing spaces
+/// OPENLINE_MARKFIX adjust mark positions after the line break
+/// OPENLINE_COM_LIST format comments with list or 2nd line indent
+///
+/// "second_line_indent": indent for after ^^D in Insert mode or if flag
+/// OPENLINE_COM_LIST
+///
+/// @return true on success, false on failure
+int open_line(
+ int dir, // FORWARD or BACKWARD
+ int flags,
+ int second_line_indent
+)
+{
+ char_u *next_line = NULL; // copy of the next line
+ char_u *p_extra = NULL; // what goes to next line
+ colnr_T less_cols = 0; // less columns for mark in new line
+ colnr_T less_cols_off = 0; // columns to skip for mark adjust
+ pos_T old_cursor; // old cursor position
+ colnr_T newcol = 0; // new cursor column
+ int newindent = 0; // auto-indent of the new line
+ bool trunc_line = false; // truncate current line afterwards
+ bool retval = false; // return value
+ int extra_len = 0; // length of p_extra string
+ int lead_len; // length of comment leader
+ char_u *lead_flags; // position in 'comments' for comment leader
+ char_u *leader = NULL; // copy of comment leader
+ char_u *allocated = NULL; // allocated memory
+ char_u *p;
+ char_u saved_char = NUL; // init for GCC
+ pos_T *pos;
+ bool do_si = (!p_paste && curbuf->b_p_si && !curbuf->b_p_cin
+ && *curbuf->b_p_inde == NUL);
+ bool no_si = false; // reset did_si afterwards
+ int first_char = NUL; // init for GCC
+ int vreplace_mode;
+ bool did_append; // appended a new line
+ int saved_pi = curbuf->b_p_pi; // copy of preserveindent setting
+
+ // make a copy of the current line so we can mess with it
+ char_u *saved_line = vim_strsave(get_cursor_line_ptr());
+
+ if (State & VREPLACE_FLAG) {
+ // With VREPLACE we make a copy of the next line, which we will be
+ // starting to replace. First make the new line empty and let vim play
+ // with the indenting and comment leader to its heart's content. Then
+ // we grab what it ended up putting on the new line, put back the
+ // original line, and call ins_char() to put each new character onto
+ // the line, replacing what was there before and pushing the right
+ // stuff onto the replace stack. -- webb.
+ if (curwin->w_cursor.lnum < orig_line_count) {
+ next_line = vim_strsave(ml_get(curwin->w_cursor.lnum + 1));
+ } else {
+ next_line = vim_strsave((char_u *)"");
+ }
+
+ // In VREPLACE mode, a NL replaces the rest of the line, and starts
+ // replacing the next line, so push all of the characters left on the
+ // line onto the replace stack. We'll push any other characters that
+ // might be replaced at the start of the next line (due to autoindent
+ // etc) a bit later.
+ replace_push(NUL); // Call twice because BS over NL expects it
+ replace_push(NUL);
+ p = saved_line + curwin->w_cursor.col;
+ while (*p != NUL) {
+ p += replace_push_mb(p);
+ }
+ saved_line[curwin->w_cursor.col] = NUL;
+ }
+
+ if ((State & INSERT)
+ && !(State & VREPLACE_FLAG)
+ ) {
+ p_extra = saved_line + curwin->w_cursor.col;
+ if (do_si) { // need first char after new line break
+ p = skipwhite(p_extra);
+ first_char = *p;
+ }
+ extra_len = (int)STRLEN(p_extra);
+ saved_char = *p_extra;
+ *p_extra = NUL;
+ }
+
+ u_clearline(); // cannot do "U" command when adding lines
+ did_si = false;
+ ai_col = 0;
+
+ // If we just did an auto-indent, then we didn't type anything on
+ // the prior line, and it should be truncated. Do this even if 'ai' is not
+ // set because automatically inserting a comment leader also sets did_ai.
+ if (dir == FORWARD && did_ai) {
+ trunc_line = true;
+ }
+
+ // If 'autoindent' and/or 'smartindent' is set, try to figure out what
+ // indent to use for the new line.
+ if (curbuf->b_p_ai
+ || do_si
+ ) {
+ // count white space on current line
+ newindent = get_indent_str(saved_line, (int)curbuf->b_p_ts, false);
+ if (newindent == 0 && !(flags & OPENLINE_COM_LIST)) {
+ newindent = second_line_indent; // for ^^D command in insert mode
+ }
+
+ // Do smart indenting.
+ // In insert/replace mode (only when dir == FORWARD)
+ // we may move some text to the next line. If it starts with '{'
+ // don't add an indent. Fixes inserting a NL before '{' in line
+ // "if (condition) {"
+ if (!trunc_line && do_si && *saved_line != NUL
+ && (p_extra == NULL || first_char != '{')) {
+ char_u *ptr;
+ char_u last_char;
+
+ old_cursor = curwin->w_cursor;
+ ptr = saved_line;
+ if (flags & OPENLINE_DO_COM) {
+ lead_len = get_leader_len(ptr, NULL, false, true);
+ } else {
+ lead_len = 0;
+ }
+ if (dir == FORWARD) {
+ // Skip preprocessor directives, unless they are
+ // recognised as comments.
+ if (lead_len == 0 && ptr[0] == '#') {
+ while (ptr[0] == '#' && curwin->w_cursor.lnum > 1) {
+ ptr = ml_get(--curwin->w_cursor.lnum);
+ }
+ newindent = get_indent();
+ }
+ if (flags & OPENLINE_DO_COM) {
+ lead_len = get_leader_len(ptr, NULL, false, true);
+ } else {
+ lead_len = 0;
+ }
+ if (lead_len > 0) {
+ // This case gets the following right:
+ // \*
+ // * A comment (read '\' as '/').
+ // */
+ // #define IN_THE_WAY
+ // This should line up here;
+ p = skipwhite(ptr);
+ if (p[0] == '/' && p[1] == '*') {
+ p++;
+ }
+ if (p[0] == '*') {
+ for (p++; *p; p++) {
+ if (p[0] == '/' && p[-1] == '*') {
+ // End of C comment, indent should line up
+ // with the line containing the start of
+ // the comment
+ curwin->w_cursor.col = (colnr_T)(p - ptr);
+ if ((pos = findmatch(NULL, NUL)) != NULL) {
+ curwin->w_cursor.lnum = pos->lnum;
+ newindent = get_indent();
+ }
+ }
+ }
+ }
+ } else { // Not a comment line
+ // Find last non-blank in line
+ p = ptr + STRLEN(ptr) - 1;
+ while (p > ptr && ascii_iswhite(*p)) {
+ p--;
+ }
+ last_char = *p;
+
+ // find the character just before the '{' or ';'
+ if (last_char == '{' || last_char == ';') {
+ if (p > ptr) {
+ p--;
+ }
+ while (p > ptr && ascii_iswhite(*p)) {
+ p--;
+ }
+ }
+ // Try to catch lines that are split over multiple
+ // lines. eg:
+ // if (condition &&
+ // condition) {
+ // Should line up here!
+ // }
+ if (*p == ')') {
+ curwin->w_cursor.col = (colnr_T)(p - ptr);
+ if ((pos = findmatch(NULL, '(')) != NULL) {
+ curwin->w_cursor.lnum = pos->lnum;
+ newindent = get_indent();
+ ptr = get_cursor_line_ptr();
+ }
+ }
+ // If last character is '{' do indent, without
+ // checking for "if" and the like.
+ if (last_char == '{') {
+ did_si = true; // do indent
+ no_si = true; // don't delete it when '{' typed
+ // Look for "if" and the like, use 'cinwords'.
+ // Don't do this if the previous line ended in ';' or
+ // '}'.
+ } else if (last_char != ';' && last_char != '}'
+ && cin_is_cinword(ptr)) {
+ did_si = true;
+ }
+ }
+ } else { // dir == BACKWARD
+ // Skip preprocessor directives, unless they are
+ // recognised as comments.
+ if (lead_len == 0 && ptr[0] == '#') {
+ bool was_backslashed = false;
+
+ while ((ptr[0] == '#' || was_backslashed)
+ && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) {
+ if (*ptr && ptr[STRLEN(ptr) - 1] == '\\') {
+ was_backslashed = true;
+ } else {
+ was_backslashed = false;
+ }
+ ptr = ml_get(++curwin->w_cursor.lnum);
+ }
+ if (was_backslashed) {
+ newindent = 0; // Got to end of file
+ } else {
+ newindent = get_indent();
+ }
+ }
+ p = skipwhite(ptr);
+ if (*p == '}') { // if line starts with '}': do indent
+ did_si = true;
+ } else { // can delete indent when '{' typed
+ can_si_back = true;
+ }
+ }
+ curwin->w_cursor = old_cursor;
+ }
+ if (do_si) {
+ can_si = true;
+ }
+
+ did_ai = true;
+ }
+
+ // Find out if the current line starts with a comment leader.
+ // This may then be inserted in front of the new line.
+ end_comment_pending = NUL;
+ if (flags & OPENLINE_DO_COM) {
+ lead_len = get_leader_len(saved_line, &lead_flags, dir == BACKWARD, true);
+ } else {
+ lead_len = 0;
+ }
+ if (lead_len > 0) {
+ char_u *lead_repl = NULL; // replaces comment leader
+ int lead_repl_len = 0; // length of *lead_repl
+ char_u lead_middle[COM_MAX_LEN]; // middle-comment string
+ char_u lead_end[COM_MAX_LEN]; // end-comment string
+ char_u *comment_end = NULL; // where lead_end has been found
+ int extra_space = false; // append extra space
+ int current_flag;
+ int require_blank = false; // requires blank after middle
+ char_u *p2;
+
+ // If the comment leader has the start, middle or end flag, it may not
+ // be used or may be replaced with the middle leader.
+ for (p = lead_flags; *p && *p != ':'; p++) {
+ if (*p == COM_BLANK) {
+ require_blank = true;
+ continue;
+ }
+ if (*p == COM_START || *p == COM_MIDDLE) {
+ current_flag = *p;
+ if (*p == COM_START) {
+ // Doing "O" on a start of comment does not insert leader.
+ if (dir == BACKWARD) {
+ lead_len = 0;
+ break;
+ }
+
+ // find start of middle part
+ (void)copy_option_part(&p, lead_middle, COM_MAX_LEN, ",");
+ require_blank = false;
+ }
+
+ // Isolate the strings of the middle and end leader.
+ while (*p && p[-1] != ':') { // find end of middle flags
+ if (*p == COM_BLANK) {
+ require_blank = true;
+ }
+ p++;
+ }
+ (void)copy_option_part(&p, lead_middle, COM_MAX_LEN, ",");
+
+ while (*p && p[-1] != ':') { // find end of end flags
+ // Check whether we allow automatic ending of comments
+ if (*p == COM_AUTO_END) {
+ end_comment_pending = -1; // means we want to set it
+ }
+ p++;
+ }
+ size_t n = copy_option_part(&p, lead_end, COM_MAX_LEN, ",");
+
+ if (end_comment_pending == -1) { // we can set it now
+ end_comment_pending = lead_end[n - 1];
+ }
+
+ // If the end of the comment is in the same line, don't use
+ // the comment leader.
+ if (dir == FORWARD) {
+ for (p = saved_line + lead_len; *p; p++) {
+ if (STRNCMP(p, lead_end, n) == 0) {
+ comment_end = p;
+ lead_len = 0;
+ break;
+ }
+ }
+ }
+
+ // Doing "o" on a start of comment inserts the middle leader.
+ if (lead_len > 0) {
+ if (current_flag == COM_START) {
+ lead_repl = lead_middle;
+ lead_repl_len = (int)STRLEN(lead_middle);
+ }
+
+ // If we have hit RETURN immediately after the start
+ // comment leader, then put a space after the middle
+ // comment leader on the next line.
+ if (!ascii_iswhite(saved_line[lead_len - 1])
+ && ((p_extra != NULL
+ && (int)curwin->w_cursor.col == lead_len)
+ || (p_extra == NULL
+ && saved_line[lead_len] == NUL)
+ || require_blank)) {
+ extra_space = true;
+ }
+ }
+ break;
+ }
+ if (*p == COM_END) {
+ // Doing "o" on the end of a comment does not insert leader.
+ // Remember where the end is, might want to use it to find the
+ // start (for C-comments).
+ if (dir == FORWARD) {
+ comment_end = skipwhite(saved_line);
+ lead_len = 0;
+ break;
+ }
+
+ // Doing "O" on the end of a comment inserts the middle leader.
+ // Find the string for the middle leader, searching backwards.
+ while (p > curbuf->b_p_com && *p != ',') {
+ p--;
+ }
+ for (lead_repl = p; lead_repl > curbuf->b_p_com
+ && lead_repl[-1] != ':'; lead_repl--) {
+ }
+ lead_repl_len = (int)(p - lead_repl);
+
+ // We can probably always add an extra space when doing "O" on
+ // the comment-end
+ extra_space = true;
+
+ // Check whether we allow automatic ending of comments
+ for (p2 = p; *p2 && *p2 != ':'; p2++) {
+ if (*p2 == COM_AUTO_END) {
+ end_comment_pending = -1; // means we want to set it
+ }
+ }
+ if (end_comment_pending == -1) {
+ // Find last character in end-comment string
+ while (*p2 && *p2 != ',') {
+ p2++;
+ }
+ end_comment_pending = p2[-1];
+ }
+ break;
+ }
+ if (*p == COM_FIRST) {
+ // Comment leader for first line only: Don't repeat leader
+ // when using "O", blank out leader when using "o".
+ if (dir == BACKWARD) {
+ lead_len = 0;
+ } else {
+ lead_repl = (char_u *)"";
+ lead_repl_len = 0;
+ }
+ break;
+ }
+ }
+ if (lead_len > 0) {
+ // allocate buffer (may concatenate p_extra later)
+ int bytes = lead_len
+ + lead_repl_len
+ + extra_space
+ + extra_len
+ + (second_line_indent > 0 ? second_line_indent : 0)
+ + 1;
+ assert(bytes >= 0);
+ leader = xmalloc((size_t)bytes);
+ allocated = leader; // remember to free it later
+
+ STRLCPY(leader, saved_line, lead_len + 1);
+
+ // Replace leader with lead_repl, right or left adjusted
+ if (lead_repl != NULL) {
+ int c = 0;
+ int off = 0;
+
+ for (p = lead_flags; *p != NUL && *p != ':'; ) {
+ if (*p == COM_RIGHT || *p == COM_LEFT) {
+ c = *p++;
+ } else if (ascii_isdigit(*p) || *p == '-') {
+ off = getdigits_int(&p);
+ } else {
+ p++;
+ }
+ }
+ if (c == COM_RIGHT) { // right adjusted leader
+ // find last non-white in the leader to line up with
+ for (p = leader + lead_len - 1; p > leader
+ && ascii_iswhite(*p); p--) {
+ }
+ p++;
+
+ // Compute the length of the replaced characters in
+ // screen characters, not bytes.
+ {
+ int repl_size = vim_strnsize(lead_repl,
+ lead_repl_len);
+ int old_size = 0;
+ char_u *endp = p;
+ int l;
+
+ while (old_size < repl_size && p > leader) {
+ MB_PTR_BACK(leader, p);
+ old_size += ptr2cells(p);
+ }
+ l = lead_repl_len - (int)(endp - p);
+ if (l != 0) {
+ memmove(endp + l, endp,
+ (size_t)((leader + lead_len) - endp));
+ }
+ lead_len += l;
+ }
+ memmove(p, lead_repl, (size_t)lead_repl_len);
+ if (p + lead_repl_len > leader + lead_len) {
+ p[lead_repl_len] = NUL;
+ }
+
+ // blank-out any other chars from the old leader.
+ while (--p >= leader) {
+ int l = utf_head_off(leader, p);
+
+ if (l > 1) {
+ p -= l;
+ if (ptr2cells(p) > 1) {
+ p[1] = ' ';
+ l--;
+ }
+ memmove(p + 1, p + l + 1,
+ (size_t)((leader + lead_len) - (p + l + 1)));
+ lead_len -= l;
+ *p = ' ';
+ } else if (!ascii_iswhite(*p)) {
+ *p = ' ';
+ }
+ }
+ } else { // left adjusted leader
+ p = skipwhite(leader);
+ // Compute the length of the replaced characters in
+ // screen characters, not bytes. Move the part that is
+ // not to be overwritten.
+ {
+ int repl_size = vim_strnsize(lead_repl,
+ lead_repl_len);
+ int i;
+ int l;
+
+ for (i = 0; i < lead_len && p[i] != NUL; i += l) {
+ l = (*mb_ptr2len)(p + i);
+ if (vim_strnsize(p, i + l) > repl_size) {
+ break;
+ }
+ }
+ if (i != lead_repl_len) {
+ memmove(p + lead_repl_len, p + i,
+ (size_t)(lead_len - i - (p - leader)));
+ lead_len += lead_repl_len - i;
+ }
+ }
+ memmove(p, lead_repl, (size_t)lead_repl_len);
+
+ // Replace any remaining non-white chars in the old
+ // leader by spaces. Keep Tabs, the indent must
+ // remain the same.
+ for (p += lead_repl_len; p < leader + lead_len; p++) {
+ if (!ascii_iswhite(*p)) {
+ // Don't put a space before a TAB.
+ if (p + 1 < leader + lead_len && p[1] == TAB) {
+ lead_len--;
+ memmove(p, p + 1, (size_t)(leader + lead_len - p));
+ } else {
+ int l = (*mb_ptr2len)(p);
+
+ if (l > 1) {
+ if (ptr2cells(p) > 1) {
+ // Replace a double-wide char with
+ // two spaces
+ l--;
+ *p++ = ' ';
+ }
+ memmove(p + 1, p + l, (size_t)(leader + lead_len - p));
+ lead_len -= l - 1;
+ }
+ *p = ' ';
+ }
+ }
+ }
+ *p = NUL;
+ }
+
+ // Recompute the indent, it may have changed.
+ if (curbuf->b_p_ai
+ || do_si
+ ) {
+ newindent = get_indent_str(leader, (int)curbuf->b_p_ts, false);
+ }
+
+ // Add the indent offset
+ if (newindent + off < 0) {
+ off = -newindent;
+ newindent = 0;
+ } else {
+ newindent += off;
+ }
+
+ // Correct trailing spaces for the shift, so that
+ // alignment remains equal.
+ while (off > 0 && lead_len > 0
+ && leader[lead_len - 1] == ' ') {
+ // Don't do it when there is a tab before the space
+ if (vim_strchr(skipwhite(leader), '\t') != NULL) {
+ break;
+ }
+ lead_len--;
+ off--;
+ }
+
+ // If the leader ends in white space, don't add an
+ // extra space
+ if (lead_len > 0 && ascii_iswhite(leader[lead_len - 1])) {
+ extra_space = false;
+ }
+ leader[lead_len] = NUL;
+ }
+
+ if (extra_space) {
+ leader[lead_len++] = ' ';
+ leader[lead_len] = NUL;
+ }
+
+ newcol = lead_len;
+
+ // if a new indent will be set below, remove the indent that
+ // is in the comment leader
+ if (newindent
+ || did_si
+ ) {
+ while (lead_len && ascii_iswhite(*leader)) {
+ lead_len--;
+ newcol--;
+ leader++;
+ }
+ }
+
+ did_si = can_si = false;
+ } else if (comment_end != NULL) {
+ // We have finished a comment, so we don't use the leader.
+ // If this was a C-comment and 'ai' or 'si' is set do a normal
+ // indent to align with the line containing the start of the
+ // comment.
+ if (comment_end[0] == '*' && comment_end[1] == '/'
+ && (curbuf->b_p_ai || do_si)) {
+ old_cursor = curwin->w_cursor;
+ curwin->w_cursor.col = (colnr_T)(comment_end - saved_line);
+ if ((pos = findmatch(NULL, NUL)) != NULL) {
+ curwin->w_cursor.lnum = pos->lnum;
+ newindent = get_indent();
+ }
+ curwin->w_cursor = old_cursor;
+ }
+ }
+ }
+
+ // (State == INSERT || State == REPLACE), only when dir == FORWARD
+ if (p_extra != NULL) {
+ *p_extra = saved_char; // restore char that NUL replaced
+
+ // When 'ai' set or "flags" has OPENLINE_DELSPACES, skip to the first
+ // non-blank.
+ //
+ // When in REPLACE mode, put the deleted blanks on the replace stack,
+ // preceded by a NUL, so they can be put back when a BS is entered.
+ if (REPLACE_NORMAL(State)) {
+ replace_push(NUL); // end of extra blanks
+ }
+ if (curbuf->b_p_ai || (flags & OPENLINE_DELSPACES)) {
+ while ((*p_extra == ' ' || *p_extra == '\t')
+ && !utf_iscomposing(utf_ptr2char(p_extra + 1))) {
+ if (REPLACE_NORMAL(State)) {
+ replace_push(*p_extra);
+ }
+ p_extra++;
+ less_cols_off++;
+ }
+ }
+
+ // columns for marks adjusted for removed columns
+ less_cols = (int)(p_extra - saved_line);
+ }
+
+ if (p_extra == NULL) {
+ p_extra = (char_u *)""; // append empty line
+ }
+
+ // concatenate leader and p_extra, if there is a leader
+ if (lead_len > 0) {
+ if (flags & OPENLINE_COM_LIST && second_line_indent > 0) {
+ int i;
+ int padding = second_line_indent
+ - (newindent + (int)STRLEN(leader));
+
+ // Here whitespace is inserted after the comment char.
+ // Below, set_indent(newindent, SIN_INSERT) will insert the
+ // whitespace needed before the comment char.
+ for (i = 0; i < padding; i++) {
+ STRCAT(leader, " ");
+ less_cols--;
+ newcol++;
+ }
+ }
+ STRCAT(leader, p_extra);
+ p_extra = leader;
+ did_ai = true; // So truncating blanks works with comments
+ less_cols -= lead_len;
+ } else {
+ end_comment_pending = NUL; // turns out there was no leader
+ }
+
+ old_cursor = curwin->w_cursor;
+ if (dir == BACKWARD) {
+ curwin->w_cursor.lnum--;
+ }
+ if (!(State & VREPLACE_FLAG) || old_cursor.lnum >= orig_line_count) {
+ if (ml_append(curwin->w_cursor.lnum, p_extra, (colnr_T)0, false) == FAIL) {
+ goto theend;
+ }
+ // Postpone calling changed_lines(), because it would mess up folding
+ // with markers.
+ // Skip mark_adjust when adding a line after the last one, there can't
+ // be marks there. But still needed in diff mode.
+ if (curwin->w_cursor.lnum + 1 < curbuf->b_ml.ml_line_count
+ || curwin->w_p_diff) {
+ mark_adjust(curwin->w_cursor.lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, false);
+ }
+ did_append = true;
+ } else {
+ // In VREPLACE mode we are starting to replace the next line.
+ curwin->w_cursor.lnum++;
+ if (curwin->w_cursor.lnum >= Insstart.lnum + vr_lines_changed) {
+ // In case we NL to a new line, BS to the previous one, and NL
+ // again, we don't want to save the new line for undo twice.
+ (void)u_save_cursor(); // errors are ignored!
+ vr_lines_changed++;
+ }
+ ml_replace(curwin->w_cursor.lnum, p_extra, true);
+ changed_bytes(curwin->w_cursor.lnum, 0);
+ curwin->w_cursor.lnum--;
+ did_append = false;
+ }
+
+ inhibit_delete_count++;
+ if (newindent
+ || did_si
+ ) {
+ curwin->w_cursor.lnum++;
+ if (did_si) {
+ int sw = get_sw_value(curbuf);
+
+ if (p_sr) {
+ newindent -= newindent % sw;
+ }
+ newindent += sw;
+ }
+ // Copy the indent
+ if (curbuf->b_p_ci) {
+ (void)copy_indent(newindent, saved_line);
+
+ // Set the 'preserveindent' option so that any further screwing
+ // with the line doesn't entirely destroy our efforts to preserve
+ // it. It gets restored at the function end.
+ curbuf->b_p_pi = true;
+ } else {
+ (void)set_indent(newindent, SIN_INSERT);
+ }
+ less_cols -= curwin->w_cursor.col;
+
+ ai_col = curwin->w_cursor.col;
+
+ // In REPLACE mode, for each character in the new indent, there must
+ // be a NUL on the replace stack, for when it is deleted with BS
+ if (REPLACE_NORMAL(State)) {
+ for (colnr_T n = 0; n < curwin->w_cursor.col; n++) {
+ replace_push(NUL);
+ }
+ }
+ newcol += curwin->w_cursor.col;
+ if (no_si) {
+ did_si = false;
+ }
+ }
+ inhibit_delete_count--;
+
+ // In REPLACE mode, for each character in the extra leader, there must be
+ // a NUL on the replace stack, for when it is deleted with BS.
+ if (REPLACE_NORMAL(State)) {
+ while (lead_len-- > 0) {
+ replace_push(NUL);
+ }
+ }
+
+ curwin->w_cursor = old_cursor;
+
+ if (dir == FORWARD) {
+ if (trunc_line || (State & INSERT)) {
+ // truncate current line at cursor
+ saved_line[curwin->w_cursor.col] = NUL;
+ // Remove trailing white space, unless OPENLINE_KEEPTRAIL used.
+ if (trunc_line && !(flags & OPENLINE_KEEPTRAIL)) {
+ truncate_spaces(saved_line);
+ }
+ ml_replace(curwin->w_cursor.lnum, saved_line, false);
+ saved_line = NULL;
+ if (did_append) {
+ changed_lines(curwin->w_cursor.lnum, curwin->w_cursor.col,
+ curwin->w_cursor.lnum + 1, 1L, true);
+ did_append = false;
+
+ // Move marks after the line break to the new line.
+ if (flags & OPENLINE_MARKFIX) {
+ mark_col_adjust(curwin->w_cursor.lnum,
+ curwin->w_cursor.col + less_cols_off,
+ 1L, (long)-less_cols, 0);
+ }
+ } else {
+ changed_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col);
+ }
+ }
+
+ // Put the cursor on the new line. Careful: the scrollup() above may
+ // have moved w_cursor, we must use old_cursor.
+ curwin->w_cursor.lnum = old_cursor.lnum + 1;
+ }
+ if (did_append) {
+ changed_lines(curwin->w_cursor.lnum, 0, curwin->w_cursor.lnum, 1L, true);
+ }
+
+ curwin->w_cursor.col = newcol;
+ curwin->w_cursor.coladd = 0;
+
+ // In VREPLACE mode, we are handling the replace stack ourselves, so stop
+ // fixthisline() from doing it (via change_indent()) by telling it we're in
+ // normal INSERT mode.
+ if (State & VREPLACE_FLAG) {
+ vreplace_mode = State; // So we know to put things right later
+ State = INSERT;
+ } else {
+ vreplace_mode = 0;
+ }
+ // May do lisp indenting.
+ if (!p_paste
+ && leader == NULL
+ && curbuf->b_p_lisp
+ && curbuf->b_p_ai) {
+ fixthisline(get_lisp_indent);
+ ai_col = (colnr_T)getwhitecols_curline();
+ }
+ // May do indenting after opening a new line.
+ if (!p_paste
+ && (curbuf->b_p_cin
+ || *curbuf->b_p_inde != NUL
+ )
+ && in_cinkeys(dir == FORWARD
+ ? KEY_OPEN_FORW
+ : KEY_OPEN_BACK, ' ', linewhite(curwin->w_cursor.lnum))) {
+ do_c_expr_indent();
+ ai_col = (colnr_T)getwhitecols_curline();
+ }
+ if (vreplace_mode != 0) {
+ State = vreplace_mode;
+ }
+
+ // Finally, VREPLACE gets the stuff on the new line, then puts back the
+ // original line, and inserts the new stuff char by char, pushing old stuff
+ // onto the replace stack (via ins_char()).
+ if (State & VREPLACE_FLAG) {
+ // Put new line in p_extra
+ p_extra = vim_strsave(get_cursor_line_ptr());
+
+ // Put back original line
+ ml_replace(curwin->w_cursor.lnum, next_line, false);
+
+ // Insert new stuff into line again
+ curwin->w_cursor.col = 0;
+ curwin->w_cursor.coladd = 0;
+ ins_bytes(p_extra); // will call changed_bytes()
+ xfree(p_extra);
+ next_line = NULL;
+ }
+
+ retval = true; // success!
+theend:
+ curbuf->b_p_pi = saved_pi;
+ xfree(saved_line);
+ xfree(next_line);
+ xfree(allocated);
+ return retval;
+} // NOLINT(readability/fn_size)
+
+/// Delete from cursor to end of line.
+/// Caller must have prepared for undo.
+/// If "fixpos" is true fix the cursor position when done.
+void truncate_line(int fixpos)
+{
+ char_u *newp;
+ linenr_T lnum = curwin->w_cursor.lnum;
+ colnr_T col = curwin->w_cursor.col;
+
+ if (col == 0) {
+ newp = vim_strsave((char_u *)"");
+ } else {
+ newp = vim_strnsave(ml_get(lnum), (size_t)col);
+ }
+ ml_replace(lnum, newp, false);
+
+ // mark the buffer as changed and prepare for displaying
+ changed_bytes(lnum, curwin->w_cursor.col);
+
+ // If "fixpos" is true we don't want to end up positioned at the NUL.
+ if (fixpos && curwin->w_cursor.col > 0) {
+ curwin->w_cursor.col--;
+ }
+}
+
+/// Delete "nlines" lines at the cursor.
+/// Saves the lines for undo first if "undo" is true.
+void del_lines(long nlines, int undo)
+{
+ long n;
+ linenr_T first = curwin->w_cursor.lnum;
+
+ if (nlines <= 0) {
+ return;
+ }
+
+ // save the deleted lines for undo
+ if (undo && u_savedel(first, nlines) == FAIL) {
+ return;
+ }
+
+ for (n = 0; n < nlines; ) {
+ if (curbuf->b_ml.ml_flags & ML_EMPTY) { // nothing to delete
+ break;
+ }
+
+ ml_delete(first, true);
+ n++;
+
+ // If we delete the last line in the file, stop
+ if (first > curbuf->b_ml.ml_line_count) {
+ break;
+ }
+ }
+
+ // Correct the cursor position before calling deleted_lines_mark(), it may
+ // trigger a callback to display the cursor.
+ curwin->w_cursor.col = 0;
+ check_cursor_lnum();
+
+ // adjust marks, mark the buffer as changed and prepare for displaying
+ deleted_lines_mark(first, n);
+}
diff --git a/src/nvim/change.h b/src/nvim/change.h
new file mode 100644
index 0000000000..e1a1bfba17
--- /dev/null
+++ b/src/nvim/change.h
@@ -0,0 +1,11 @@
+#ifndef NVIM_CHANGE_H
+#define NVIM_CHANGE_H
+
+#include "nvim/buffer_defs.h" // for buf_T
+#include "nvim/pos.h" // for linenr_T
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "change.h.generated.h"
+#endif
+
+#endif // NVIM_CHANGE_H
diff --git a/src/nvim/cursor.c b/src/nvim/cursor.c
index bc14761877..f2b3cfe690 100644
--- a/src/nvim/cursor.c
+++ b/src/nvim/cursor.c
@@ -5,6 +5,7 @@
#include <inttypes.h>
#include "nvim/assert.h"
+#include "nvim/change.h"
#include "nvim/cursor.h"
#include "nvim/charset.h"
#include "nvim/fold.h"
diff --git a/src/nvim/diff.c b/src/nvim/diff.c
index 4176769f85..7328b88a40 100644
--- a/src/nvim/diff.c
+++ b/src/nvim/diff.c
@@ -18,6 +18,7 @@
#include "nvim/ascii.h"
#include "nvim/diff.h"
#include "nvim/buffer.h"
+#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/eval.h"
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index 5a9549cc89..2ac429cf9e 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -14,6 +14,7 @@
#include "nvim/ascii.h"
#include "nvim/edit.h"
#include "nvim/buffer.h"
+#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/digraph.h"
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 7ffa59f298..31a49faaf3 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -24,6 +24,7 @@
#endif
#include "nvim/eval.h"
#include "nvim/buffer.h"
+#include "nvim/change.h"
#include "nvim/channel.h"
#include "nvim/charset.h"
#include "nvim/context.h"
@@ -11259,7 +11260,7 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr)
"fork",
#endif
"gettext",
-#if defined(HAVE_ICONV_H) && defined(USE_ICONV)
+#if defined(HAVE_ICONV)
"iconv",
#endif
"insert_expand",
@@ -11372,10 +11373,6 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr)
n = stdout_isatty;
} else if (STRICMP(name, "multi_byte_encoding") == 0) {
n = has_mbyte != 0;
-#if defined(USE_ICONV) && defined(DYNAMIC_ICONV)
- } else if (STRICMP(name, "iconv") == 0) {
- n = iconv_enabled(false);
-#endif
} else if (STRICMP(name, "syntax_items") == 0) {
n = syntax_present(curwin);
#ifdef UNIX
@@ -12358,7 +12355,6 @@ static void f_jobstop(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return;
}
-
Channel *data = find_job(argvars[0].vval.v_number, true);
if (!data) {
return;
@@ -16763,6 +16759,8 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr)
break;
}
str += len;
+ capcol -= len;
+ len = 0;
}
}
}
diff --git a/src/nvim/event/libuv_process.c b/src/nvim/event/libuv_process.c
index ffe2db9b76..63efee59a8 100644
--- a/src/nvim/event/libuv_process.c
+++ b/src/nvim/event/libuv_process.c
@@ -101,6 +101,10 @@ static void close_cb(uv_handle_t *handle)
static void exit_cb(uv_process_t *handle, int64_t status, int term_signal)
{
Process *proc = handle->data;
- proc->status = (int)status;
+#if defined(WIN32)
+ // Use stored/expected signal.
+ term_signal = proc->exit_signal;
+#endif
+ proc->status = term_signal ? 128 + term_signal : (int)status;
proc->internal_exit_cb(proc);
}
diff --git a/src/nvim/event/process.c b/src/nvim/event/process.c
index 4410deadef..c31ecdaddf 100644
--- a/src/nvim/event/process.c
+++ b/src/nvim/event/process.c
@@ -159,7 +159,7 @@ void process_close_streams(Process *proc) FUNC_ATTR_NONNULL_ALL
/// 0 for no wait. -1 to wait until the process quits.
/// @return Exit code of the process. proc->status will have the same value.
/// -1 if the timeout expired while the process is still running.
-/// -2 if the user interruped the wait.
+/// -2 if the user interrupted the wait.
int process_wait(Process *proc, int ms, MultiQueue *events)
FUNC_ATTR_NONNULL_ARG(1)
{
@@ -220,6 +220,7 @@ void process_stop(Process *proc) FUNC_ATTR_NONNULL_ALL
return;
}
proc->stopped_time = os_hrtime();
+ proc->exit_signal = SIGTERM;
switch (proc->type) {
case kProcessTypeUv:
@@ -253,8 +254,10 @@ static void children_kill_cb(uv_timer_t *handle)
}
uint64_t term_sent = UINT64_MAX == proc->stopped_time;
if (kProcessTypePty != proc->type || term_sent) {
+ proc->exit_signal = SIGKILL;
os_proc_tree_kill(proc->pid, SIGKILL);
} else {
+ proc->exit_signal = SIGTERM;
os_proc_tree_kill(proc->pid, SIGTERM);
proc->stopped_time = UINT64_MAX; // Flag: SIGTERM was sent.
// Restart timer.
@@ -403,4 +406,3 @@ static void on_process_stream_close(Stream *stream, void *data)
Process *proc = data;
decref(proc);
}
-
diff --git a/src/nvim/event/process.h b/src/nvim/event/process.h
index 13dc3839ce..ef9d953ab7 100644
--- a/src/nvim/event/process.h
+++ b/src/nvim/event/process.h
@@ -19,6 +19,7 @@ struct process {
Loop *loop;
void *data;
int pid, status, refcount;
+ uint8_t exit_signal; // Signal used when killing (on Windows).
uint64_t stopped_time; // process_stop() timestamp
const char *cwd;
char **argv;
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 2ea6937126..e256351de2 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -20,6 +20,7 @@
#include "nvim/ascii.h"
#include "nvim/ex_cmds.h"
#include "nvim/buffer.h"
+#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/diff.h"
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index 73e6f85627..7c28461f4c 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -18,6 +18,7 @@
#endif
#include "nvim/ex_cmds2.h"
#include "nvim/buffer.h"
+#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/eval.h"
#include "nvim/ex_cmds.h"
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index b315639681..0f345df22b 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -16,6 +16,7 @@
#include "nvim/ascii.h"
#include "nvim/ex_docmd.h"
#include "nvim/buffer.h"
+#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/diff.h"
@@ -4060,10 +4061,9 @@ static char_u *replace_makeprg(exarg_T *eap, char_u *p, char_u **cmdlinep)
return p;
}
-/*
- * Expand file name in Ex command argument.
- * Return FAIL for failure, OK otherwise.
- */
+// Expand file name in Ex command argument.
+// When an error is detected, "errormsgp" is set to a non-NULL pointer.
+// Return FAIL for failure, OK otherwise.
int expand_filename(exarg_T *eap, char_u **cmdlinep, char_u **errormsgp)
{
int has_wildcards; /* need to expand wildcards */
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index a164cf47d5..d2620376c6 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -15,6 +15,7 @@
#include "nvim/ascii.h"
#include "nvim/fileio.h"
#include "nvim/buffer.h"
+#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/diff.h"
@@ -163,22 +164,22 @@ typedef struct AutoPatCmd {
* Structure to pass arguments from buf_write() to buf_write_bytes().
*/
struct bw_info {
- int bw_fd; /* file descriptor */
- char_u *bw_buf; /* buffer with data to be written */
- int bw_len; /* length of data */
+ int bw_fd; // file descriptor
+ char_u *bw_buf; // buffer with data to be written
+ int bw_len; // length of data
#ifdef HAS_BW_FLAGS
- int bw_flags; /* FIO_ flags */
+ int bw_flags; // FIO_ flags
#endif
- char_u bw_rest[CONV_RESTLEN]; /* not converted bytes */
- int bw_restlen; /* nr of bytes in bw_rest[] */
- int bw_first; /* first write call */
- char_u *bw_conv_buf; /* buffer for writing converted chars */
- int bw_conv_buflen; /* size of bw_conv_buf */
- int bw_conv_error; /* set for conversion error */
- linenr_T bw_conv_error_lnum; /* first line with error or zero */
- linenr_T bw_start_lnum; /* line number at start of buffer */
-# ifdef USE_ICONV
- iconv_t bw_iconv_fd; /* descriptor for iconv() or -1 */
+ char_u bw_rest[CONV_RESTLEN]; // not converted bytes
+ int bw_restlen; // nr of bytes in bw_rest[]
+ int bw_first; // first write call
+ char_u *bw_conv_buf; // buffer for writing converted chars
+ int bw_conv_buflen; // size of bw_conv_buf
+ int bw_conv_error; // set for conversion error
+ linenr_T bw_conv_error_lnum; // first line with error or zero
+ linenr_T bw_start_lnum; // line number at start of buffer
+# ifdef HAVE_ICONV
+ iconv_t bw_iconv_fd; // descriptor for iconv() or -1
# endif
};
@@ -327,10 +328,10 @@ readfile(
char_u *fenc_next = NULL; // next item in 'fencs' or NULL
bool advance_fenc = false;
long real_size = 0;
-# ifdef USE_ICONV
- iconv_t iconv_fd = (iconv_t)-1; /* descriptor for iconv() or -1 */
- int did_iconv = FALSE; /* TRUE when iconv() failed and trying
- 'charconvert' next */
+# ifdef HAVE_ICONV
+ iconv_t iconv_fd = (iconv_t)-1; // descriptor for iconv() or -1
+ int did_iconv = false; // TRUE when iconv() failed and trying
+ // 'charconvert' next
# endif
int converted = FALSE; /* TRUE if conversion done */
int notconverted = FALSE; /* TRUE if conversion wanted but it
@@ -841,7 +842,7 @@ retry:
fileformat = EOL_UNKNOWN; /* detect from file */
}
-# ifdef USE_ICONV
+# ifdef HAVE_ICONV
if (iconv_fd != (iconv_t)-1) {
/* aborted conversion with iconv(), close the descriptor */
iconv_close(iconv_fd);
@@ -908,15 +909,14 @@ retry:
-# ifdef USE_ICONV
- /*
- * Try using iconv() if we can't convert internally.
- */
+# ifdef HAVE_ICONV
+ // Try using iconv() if we can't convert internally.
if (fio_flags == 0
&& !did_iconv
- )
+ ) {
iconv_fd = (iconv_t)my_iconv_open(
enc_utf8 ? (char_u *)"utf-8" : p_enc, fenc);
+ }
# endif
/*
@@ -925,12 +925,12 @@ retry:
*/
if (fio_flags == 0 && !read_stdin && !read_buffer && *p_ccv != NUL
&& !read_fifo
-# ifdef USE_ICONV
+# ifdef HAVE_ICONV
&& iconv_fd == (iconv_t)-1
# endif
) {
-# ifdef USE_ICONV
- did_iconv = FALSE;
+# ifdef HAVE_ICONV
+ did_iconv = false;
# endif
/* Skip conversion when it's already done (retry for wrong
* "fileformat"). */
@@ -950,7 +950,7 @@ retry:
}
} else {
if (fio_flags == 0
-# ifdef USE_ICONV
+# ifdef HAVE_ICONV
&& iconv_fd == (iconv_t)-1
# endif
) {
@@ -1023,20 +1023,23 @@ retry:
* ucs-4 to utf-8: 4 bytes become up to 6 bytes, size must be
* multiple of 4 */
real_size = (int)size;
-# ifdef USE_ICONV
- if (iconv_fd != (iconv_t)-1)
+# ifdef HAVE_ICONV
+ if (iconv_fd != (iconv_t)-1) {
size = size / ICONV_MULT;
- else
+ } else {
# endif
- if (fio_flags & FIO_LATIN1)
+ if (fio_flags & FIO_LATIN1) {
size = size / 2;
- else if (fio_flags & (FIO_UCS2 | FIO_UTF16))
+ } else if (fio_flags & (FIO_UCS2 | FIO_UTF16)) {
size = (size * 2 / 3) & ~1;
- else if (fio_flags & FIO_UCS4)
+ } else if (fio_flags & FIO_UCS4) {
size = (size * 2 / 3) & ~3;
- else if (fio_flags == FIO_UCSBOM)
- size = size / ICONV_MULT; /* worst case */
-
+ } else if (fio_flags == FIO_UCSBOM) {
+ size = size / ICONV_MULT; // worst case
+ }
+# ifdef HAVE_ICONV
+ }
+# endif
if (conv_restlen > 0) {
// Insert unconverted bytes from previous line.
memmove(ptr, conv_rest, conv_restlen); // -V614
@@ -1112,7 +1115,7 @@ retry:
/* When we did a conversion report an error. */
if (fio_flags != 0
-# ifdef USE_ICONV
+# ifdef HAVE_ICONV
|| iconv_fd != (iconv_t)-1
# endif
) {
@@ -1135,7 +1138,7 @@ retry:
* leave the UTF8 checking code to do it, as it
* works slightly differently. */
if (bad_char_behavior != BAD_KEEP && (fio_flags != 0
-# ifdef USE_ICONV
+# ifdef HAVE_ICONV
|| iconv_fd != (iconv_t)-1
# endif
)) {
@@ -1144,8 +1147,8 @@ retry:
--conv_restlen;
}
}
- fio_flags = 0; /* don't convert this */
-# ifdef USE_ICONV
+ fio_flags = 0; // don't convert this
+# ifdef HAVE_ICONV
if (iconv_fd != (iconv_t)-1) {
iconv_close(iconv_fd);
iconv_fd = (iconv_t)-1;
@@ -1216,7 +1219,7 @@ retry:
if (size <= 0)
break;
-# ifdef USE_ICONV
+# ifdef HAVE_ICONV
if (iconv_fd != (iconv_t)-1) {
/*
* Attempt conversion of the read bytes to 'encoding' using
@@ -1474,10 +1477,11 @@ retry:
* file is more likely than a conversion error. */
if (can_retry && !incomplete_tail)
break;
-# ifdef USE_ICONV
- /* When we did a conversion report an error. */
- if (iconv_fd != (iconv_t)-1 && conv_error == 0)
+# ifdef HAVE_ICONV
+ // When we did a conversion report an error.
+ if (iconv_fd != (iconv_t)-1 && conv_error == 0) {
conv_error = readfile_linenr(linecnt, ptr, p);
+ }
# endif
/* Remember the first linenr with an illegal byte */
if (conv_error == 0 && illegal_byte == 0)
@@ -1497,15 +1501,18 @@ retry:
if (p < ptr + size && !incomplete_tail) {
/* Detected a UTF-8 error. */
rewind_retry:
- /* Retry reading with another conversion. */
-# ifdef USE_ICONV
- if (*p_ccv != NUL && iconv_fd != (iconv_t)-1)
- /* iconv() failed, try 'charconvert' */
- did_iconv = TRUE;
- else
+ // Retry reading with another conversion.
+# ifdef HAVE_ICONV
+ if (*p_ccv != NUL && iconv_fd != (iconv_t)-1) {
+ // iconv() failed, try 'charconvert'
+ did_iconv = true;
+ } else {
# endif
// use next item from 'fileencodings'
advance_fenc = true;
+# ifdef HAVE_ICONV
+ }
+# endif
file_rewind = true;
goto retry;
}
@@ -1710,7 +1717,7 @@ failed:
}
if (fenc_alloced)
xfree(fenc);
-# ifdef USE_ICONV
+# ifdef HAVE_ICONV
if (iconv_fd != (iconv_t)-1) {
iconv_close(iconv_fd);
# ifndef __clang_analyzer__
@@ -1755,6 +1762,9 @@ failed:
ml_delete(curbuf->b_ml.ml_line_count, false);
linecnt--;
}
+ curbuf->deleted_bytes = 0;
+ curbuf->deleted_codepoints = 0;
+ curbuf->deleted_codeunits = 0;
linecnt = curbuf->b_ml.ml_line_count - linecnt;
if (filesize == 0)
linecnt = 0;
@@ -2291,7 +2301,7 @@ buf_write(
write_info.bw_conv_error = FALSE;
write_info.bw_conv_error_lnum = 0;
write_info.bw_restlen = 0;
-# ifdef USE_ICONV
+# ifdef HAVE_ICONV
write_info.bw_iconv_fd = (iconv_t)-1;
# endif
@@ -3012,7 +3022,7 @@ nobackup:
if (converted && wb_flags == 0) {
-# ifdef USE_ICONV
+# ifdef HAVE_ICONV
// Use iconv() conversion when conversion is needed and it's not done
// internally.
write_info.bw_iconv_fd = (iconv_t)my_iconv_open(fenc,
@@ -3042,7 +3052,7 @@ nobackup:
}
}
if (converted && wb_flags == 0
-# ifdef USE_ICONV
+# ifdef HAVE_ICONV
&& write_info.bw_iconv_fd == (iconv_t)-1
# endif
&& wfname == fname
@@ -3570,7 +3580,7 @@ nofail:
xfree(buffer);
xfree(fenc_tofree);
xfree(write_info.bw_conv_buf);
-# ifdef USE_ICONV
+# ifdef HAVE_ICONV
if (write_info.bw_iconv_fd != (iconv_t)-1) {
iconv_close(write_info.bw_iconv_fd);
write_info.bw_iconv_fd = (iconv_t)-1;
@@ -3945,7 +3955,7 @@ static int buf_write_bytes(struct bw_info *ip)
}
}
-# ifdef USE_ICONV
+# ifdef HAVE_ICONV
if (ip->bw_iconv_fd != (iconv_t)-1) {
const char *from;
size_t fromlen;
diff --git a/src/nvim/fold.c b/src/nvim/fold.c
index 72d8c14468..ad0bfe29e2 100644
--- a/src/nvim/fold.c
+++ b/src/nvim/fold.c
@@ -13,6 +13,7 @@
#include "nvim/vim.h"
#include "nvim/ascii.h"
#include "nvim/fold.h"
+#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/diff.h"
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index f7614fd3e1..03f64c2019 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -1945,6 +1945,7 @@ static int vgetorpeek(int advance)
// No matching mapping found or found a non-matching mapping that
// matches at least what the matching mapping matched
keylen = 0;
+ (void)keylen; // suppress clang/dead assignment
// If there was no mapping, use the character from the typeahead
// buffer right here. Otherwise, use the mapping (loop around).
if (mp == NULL) {
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index de6f59b3f1..b095e759d9 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -627,6 +627,8 @@ EXTERN pos_T Insstart_orig;
EXTERN int orig_line_count INIT(= 0); /* Line count when "gR" started */
EXTERN int vr_lines_changed INIT(= 0); /* #Lines changed by "gR" so far */
+// increase around internal delete/replace
+EXTERN int inhibit_delete_count INIT(= 0);
/*
* These flags are set based upon 'fileencoding'.
@@ -653,16 +655,6 @@ EXTERN int vr_lines_changed INIT(= 0); /* #Lines changed by "gR" so far */
/// Encoding used when 'fencs' is set to "default"
EXTERN char_u *fenc_default INIT(= NULL);
-# if defined(USE_ICONV) && defined(DYNAMIC_ICONV)
-/* Pointers to functions and variables to be loaded at runtime */
-EXTERN size_t (*iconv)(iconv_t cd, const char **inbuf, size_t *inbytesleft,
- char **outbuf, size_t *outbytesleft);
-EXTERN iconv_t (*iconv_open)(const char *tocode, const char *fromcode);
-EXTERN int (*iconv_close)(iconv_t cd);
-EXTERN int (*iconvctl)(iconv_t cd, int request, void *argument);
-EXTERN int* (*iconv_errno)(void);
-# endif
-
/// "State" is the main state of Vim.
/// There are other variables that modify the state:
/// Visual_mode: When State is NORMAL or INSERT.
diff --git a/src/nvim/iconv.h b/src/nvim/iconv.h
index d7234090c4..a7c9ad4040 100644
--- a/src/nvim/iconv.h
+++ b/src/nvim/iconv.h
@@ -1,52 +1,20 @@
#ifndef NVIM_ICONV_H
#define NVIM_ICONV_H
-// iconv can be linked at compile-time as well as loaded at runtime. In the
-// latter case, some function pointers need to be initialized after loading
-// the library (see `iconv_enabled()` in mbyte.c). These function pointers
-// are stored in globals.h. Since globals.h includes iconv.h to get the
-// definition of USE_ICONV, we can't include it from iconv.h. One way to
-// solve this conundrum would be perhaps to let cmake decide the value of
-// USE_ICONV, or to put the USE_ICONV definition in config.h.in directly. As
-// it stands, globals.h needs to be included alongside iconv.h.
-
#include "auto/config.h"
-// Use iconv() when it's available, either by linking to the library at
-// compile time or by loading it at runtime.
-#if (defined(HAVE_ICONV_H) && defined(HAVE_ICONV)) || defined(DYNAMIC_ICONV)
-# define USE_ICONV
-#endif
-
-// If we don't have the actual iconv header files present but USE_ICONV was
-// defined, we provide a type shim (pull in errno.h and define iconv_t).
-// This enables us to still load and use iconv dynamically at runtime.
-#ifdef USE_ICONV
+#ifdef HAVE_ICONV
# include <errno.h>
-# ifdef HAVE_ICONV_H
-# include <iconv.h>
-# else
-typedef void *iconv_t;
-# endif
-#endif
+# include <iconv.h>
// define some missing constants if necessary
-# ifdef USE_ICONV
# ifndef EILSEQ
# define EILSEQ 123
# endif
-# ifdef DYNAMIC_ICONV
-// on win32 iconv.dll is dynamically loaded
-# define ICONV_ERRNO (*iconv_errno())
-# define ICONV_E2BIG 7
-# define ICONV_EINVAL 22
-# define ICONV_EILSEQ 42
-# else
-# define ICONV_ERRNO errno
-# define ICONV_E2BIG E2BIG
-# define ICONV_EINVAL EINVAL
-# define ICONV_EILSEQ EILSEQ
-# endif
-# endif
+# define ICONV_ERRNO errno
+# define ICONV_E2BIG E2BIG
+# define ICONV_EINVAL EINVAL
+# define ICONV_EILSEQ EILSEQ
+#endif
#endif // NVIM_ICONV_H
diff --git a/src/nvim/indent.c b/src/nvim/indent.c
index 8e20aa5be4..efbfea33aa 100644
--- a/src/nvim/indent.c
+++ b/src/nvim/indent.c
@@ -7,6 +7,7 @@
#include "nvim/ascii.h"
#include "nvim/assert.h"
+#include "nvim/change.h"
#include "nvim/indent.h"
#include "nvim/eval.h"
#include "nvim/charset.h"
@@ -316,109 +317,6 @@ int set_indent(int size, int flags)
}
-// Copy the indent from ptr to the current line (and fill to size).
-// Leaves the cursor on the first non-blank in the line.
-// @return true if the line was changed.
-int copy_indent(int size, char_u *src)
-{
- char_u *p = NULL;
- char_u *line = NULL;
- char_u *s;
- int todo;
- int ind_len;
- int line_len = 0;
- int tab_pad;
- int ind_done;
- int round;
-
- // Round 1: compute the number of characters needed for the indent
- // Round 2: copy the characters.
- for (round = 1; round <= 2; ++round) {
- todo = size;
- ind_len = 0;
- ind_done = 0;
- s = src;
-
- // Count/copy the usable portion of the source line.
- while (todo > 0 && ascii_iswhite(*s)) {
- if (*s == TAB) {
- tab_pad = (int)curbuf->b_p_ts
- - (ind_done % (int)curbuf->b_p_ts);
-
- // Stop if this tab will overshoot the target.
- if (todo < tab_pad) {
- break;
- }
- todo -= tab_pad;
- ind_done += tab_pad;
- } else {
- todo--;
- ind_done++;
- }
- ind_len++;
-
- if (p != NULL) {
- *p++ = *s;
- }
- s++;
- }
-
- // Fill to next tabstop with a tab, if possible.
- tab_pad = (int)curbuf->b_p_ts - (ind_done % (int)curbuf->b_p_ts);
-
- if ((todo >= tab_pad) && !curbuf->b_p_et) {
- todo -= tab_pad;
- ind_len++;
-
- if (p != NULL) {
- *p++ = TAB;
- }
- }
-
- // Add tabs required for indent.
- while (todo >= (int)curbuf->b_p_ts && !curbuf->b_p_et) {
- todo -= (int)curbuf->b_p_ts;
- ind_len++;
-
- if (p != NULL) {
- *p++ = TAB;
- }
- }
-
- // Count/add spaces required for indent.
- while (todo > 0) {
- todo--;
- ind_len++;
-
- if (p != NULL) {
- *p++ = ' ';
- }
- }
-
- if (p == NULL) {
- // Allocate memory for the result: the copied indent, new indent
- // and the rest of the line.
- line_len = (int)STRLEN(get_cursor_line_ptr()) + 1;
- assert(ind_len + line_len >= 0);
- size_t line_size;
- STRICT_ADD(ind_len, line_len, &line_size, size_t);
- line = xmalloc(line_size);
- p = line;
- }
- }
-
- // Append the original line
- memmove(p, get_cursor_line_ptr(), (size_t)line_len);
-
- // Replace the line
- ml_replace(curwin->w_cursor.lnum, line, false);
-
- // Put the cursor after the indent.
- curwin->w_cursor.col = ind_len;
- return true;
-}
-
-
// Return the indent of the current line after a number. Return -1 if no
// number was found. Used for 'n' in 'formatoptions': numbered list.
// Since a pattern is used it can actually handle more than numbers.
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index 4051354d65..3de1b531e6 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -23,6 +23,7 @@
#include "nvim/cursor.h"
#include "nvim/undo.h"
#include "nvim/ascii.h"
+#include "nvim/change.h"
#ifdef WIN32
#include "nvim/os/os.h"
@@ -112,6 +113,65 @@ static int nlua_stricmp(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
return 1;
}
+/// convert byte index to UTF-32 and UTF-16 indicies
+///
+/// Expects a string and an optional index. If no index is supplied, the length
+/// of the string is returned.
+///
+/// Returns two values: the UTF-32 and UTF-16 indicies.
+static int nlua_str_utfindex(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
+{
+ size_t s1_len;
+ const char *s1 = luaL_checklstring(lstate, 1, &s1_len);
+ intptr_t idx;
+ if (lua_gettop(lstate) >= 2) {
+ idx = luaL_checkinteger(lstate, 2);
+ if (idx < 0 || idx > (intptr_t)s1_len) {
+ return luaL_error(lstate, "index out of range");
+ }
+ } else {
+ idx = (intptr_t)s1_len;
+ }
+
+ size_t codepoints = 0, codeunits = 0;
+ mb_utflen((const char_u *)s1, (size_t)idx, &codepoints, &codeunits);
+
+ lua_pushinteger(lstate, (long)codepoints);
+ lua_pushinteger(lstate, (long)codeunits);
+
+ return 2;
+}
+
+/// convert UTF-32 or UTF-16 indicies to byte index.
+///
+/// Expects up to three args: string, index and use_utf16.
+/// If use_utf16 is not supplied it defaults to false (use UTF-32)
+///
+/// Returns the byte index.
+static int nlua_str_byteindex(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
+{
+ size_t s1_len;
+ const char *s1 = luaL_checklstring(lstate, 1, &s1_len);
+ intptr_t idx = luaL_checkinteger(lstate, 2);
+ if (idx < 0) {
+ return luaL_error(lstate, "index out of range");
+ }
+ bool use_utf16 = false;
+ if (lua_gettop(lstate) >= 3) {
+ use_utf16 = lua_toboolean(lstate, 3);
+ }
+
+ ssize_t byteidx = mb_utf_index_to_bytes((const char_u *)s1, s1_len,
+ (size_t)idx, use_utf16);
+ if (byteidx == -1) {
+ return luaL_error(lstate, "index out of range");
+ }
+
+ lua_pushinteger(lstate, (long)byteidx);
+
+ return 1;
+}
+
static void nlua_luv_error_event(void **argv)
{
char *error = (char *)argv[0];
@@ -220,6 +280,12 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
// stricmp
lua_pushcfunction(lstate, &nlua_stricmp);
lua_setfield(lstate, -2, "stricmp");
+ // str_utfindex
+ lua_pushcfunction(lstate, &nlua_str_utfindex);
+ lua_setfield(lstate, -2, "str_utfindex");
+ // str_byteindex
+ lua_pushcfunction(lstate, &nlua_str_byteindex);
+ lua_setfield(lstate, -2, "str_byteindex");
// schedule
lua_pushcfunction(lstate, &nlua_schedule);
lua_setfield(lstate, -2, "schedule");
diff --git a/src/nvim/main.c b/src/nvim/main.c
index 9c342e62c0..6bb3c37b92 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -664,9 +664,6 @@ void getout(int exitval)
ui_call_set_title(cstr_as_string((char *)p_titleold));
}
-#if defined(USE_ICONV) && defined(DYNAMIC_ICONV)
- iconv_end();
-#endif
cs_end();
if (garbage_collect_at_exit) {
garbage_collect(false);
diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c
index e7579399f3..fae7635d34 100644
--- a/src/nvim/mbyte.c
+++ b/src/nvim/mbyte.c
@@ -1438,6 +1438,64 @@ int utf16_to_utf8(const wchar_t *strw, char **str)
#endif
+/// Measure the length of a string in corresponding UTF-32 and UTF-16 units.
+///
+/// Invalid UTF-8 bytes, or embedded surrogates, count as one code point/unit
+/// each.
+///
+/// The out parameters are incremented. This is used to measure the size of
+/// a buffer region consisting of multiple line segments.
+///
+/// @param s the string
+/// @param len maximum length (an earlier NUL terminates)
+/// @param[out] codepoints incremented with UTF-32 code point size
+/// @param[out] codeunits incremented with UTF-16 code unit size
+void mb_utflen(const char_u *s, size_t len, size_t *codepoints,
+ size_t *codeunits)
+ FUNC_ATTR_NONNULL_ALL
+{
+ size_t count = 0, extra = 0;
+ size_t clen;
+ for (size_t i = 0; i < len && s[i] != NUL; i += clen) {
+ clen = utf_ptr2len_len(s+i, len-i);
+ // NB: gets the byte value of invalid sequence bytes.
+ // we only care whether the char fits in the BMP or not
+ int c = (clen > 1) ? utf_ptr2char(s+i) : s[i];
+ count++;
+ if (c > 0xFFFF) {
+ extra++;
+ }
+ }
+ *codepoints += count;
+ *codeunits += count + extra;
+}
+
+ssize_t mb_utf_index_to_bytes(const char_u *s, size_t len,
+ size_t index, bool use_utf16_units)
+ FUNC_ATTR_NONNULL_ALL
+{
+ size_t count = 0;
+ size_t clen, i;
+ if (index == 0) {
+ return 0;
+ }
+ for (i = 0; i < len && s[i] != NUL; i += clen) {
+ clen = utf_ptr2len_len(s+i, len-i);
+ // NB: gets the byte value of invalid sequence bytes.
+ // we only care whether the char fits in the BMP or not
+ int c = (clen > 1) ? utf_ptr2char(s+i) : s[i];
+ count++;
+ if (use_utf16_units && c > 0xFFFF) {
+ count++;
+ }
+ if (count >= index) {
+ return i+clen;
+ }
+ }
+ return -1;
+}
+
+
/*
* Version of strnicmp() that handles multi-byte characters.
* Needed for Big5, Shift-JIS and UTF-8 encoding. Other DBCS encodings can
@@ -2004,7 +2062,7 @@ enc_locale_copy_enc:
return enc_canonize((char_u *)buf);
}
-# if defined(USE_ICONV)
+# if defined(HAVE_ICONV)
/*
@@ -2025,13 +2083,6 @@ void * my_iconv_open(char_u *to, char_u *from)
if (iconv_working == kBroken)
return (void *)-1; /* detected a broken iconv() previously */
-#ifdef DYNAMIC_ICONV
- // Check if the iconv.dll can be found.
- if (!iconv_enabled(true)) {
- return (void *)-1;
- }
-#endif
-
fd = iconv_open((char *)enc_skip(to), (char *)enc_skip(from));
if (fd != (iconv_t)-1 && iconv_working == kUnknown) {
@@ -2138,152 +2189,7 @@ static char_u *iconv_string(const vimconv_T *const vcp, char_u *str,
return result;
}
-# if defined(DYNAMIC_ICONV)
-// Dynamically load the "iconv.dll" on Win32.
-
-#ifndef DYNAMIC_ICONV // just generating prototypes
-# define HINSTANCE int
-#endif
-static HINSTANCE hIconvDLL = 0;
-static HINSTANCE hMsvcrtDLL = 0;
-
-# ifndef DYNAMIC_ICONV_DLL
-# define DYNAMIC_ICONV_DLL "iconv.dll"
-# define DYNAMIC_ICONV_DLL_ALT "libiconv-2.dll"
-# endif
-# ifndef DYNAMIC_MSVCRT_DLL
-# define DYNAMIC_MSVCRT_DLL "msvcrt.dll"
-# endif
-
-/*
- * Get the address of 'funcname' which is imported by 'hInst' DLL.
- */
-static void * get_iconv_import_func(HINSTANCE hInst,
- const char *funcname)
-{
- PBYTE pImage = (PBYTE)hInst;
- PIMAGE_DOS_HEADER pDOS = (PIMAGE_DOS_HEADER)hInst;
- PIMAGE_NT_HEADERS pPE;
- PIMAGE_IMPORT_DESCRIPTOR pImpDesc;
- PIMAGE_THUNK_DATA pIAT; /* Import Address Table */
- PIMAGE_THUNK_DATA pINT; /* Import Name Table */
- PIMAGE_IMPORT_BY_NAME pImpName;
-
- if (pDOS->e_magic != IMAGE_DOS_SIGNATURE)
- return NULL;
- pPE = (PIMAGE_NT_HEADERS)(pImage + pDOS->e_lfanew);
- if (pPE->Signature != IMAGE_NT_SIGNATURE)
- return NULL;
- pImpDesc = (PIMAGE_IMPORT_DESCRIPTOR)(pImage
- + pPE->OptionalHeader.DataDirectory[
- IMAGE_DIRECTORY_ENTRY_IMPORT]
- .VirtualAddress);
- for (; pImpDesc->FirstThunk; ++pImpDesc) {
- if (!pImpDesc->OriginalFirstThunk)
- continue;
- pIAT = (PIMAGE_THUNK_DATA)(pImage + pImpDesc->FirstThunk);
- pINT = (PIMAGE_THUNK_DATA)(pImage + pImpDesc->OriginalFirstThunk);
- for (; pIAT->u1.Function; ++pIAT, ++pINT) {
- if (IMAGE_SNAP_BY_ORDINAL(pINT->u1.Ordinal))
- continue;
- pImpName = (PIMAGE_IMPORT_BY_NAME)(pImage
- + (UINT_PTR)(pINT->u1.AddressOfData));
- if (strcmp(pImpName->Name, funcname) == 0)
- return (void *)pIAT->u1.Function;
- }
- }
- return NULL;
-}
-
-// Load library "name".
-HINSTANCE vimLoadLib(char *name)
-{
- HINSTANCE dll = NULL;
-
- // NOTE: Do not use mch_dirname() and mch_chdir() here, they may call
- // vimLoadLib() recursively, which causes a stack overflow.
- wchar_t old_dirw[MAXPATHL];
-
- // Path to exe dir.
- char *buf = xstrdup((char *)get_vim_var_str(VV_PROGPATH));
- // ptrdiff_t len = ;
- // assert(len > 0);
- buf[path_tail_with_sep(buf) - buf] = '\0';
-
- if (GetCurrentDirectoryW(MAXPATHL, old_dirw) != 0) {
- // Change directory to where the executable is, both to make
- // sure we find a .dll there and to avoid looking for a .dll
- // in the current directory.
- SetCurrentDirectory((LPCSTR)buf);
- // TODO(justinmk): use uv_dlopen instead. see os_libcall
- dll = LoadLibrary(name);
- SetCurrentDirectoryW(old_dirw);
- }
-
- return dll;
-}
-
-
-/*
- * Try opening the iconv.dll and return TRUE if iconv() can be used.
- */
-bool iconv_enabled(bool verbose)
-{
- if (hIconvDLL != 0 && hMsvcrtDLL != 0)
- return true;
- hIconvDLL = vimLoadLib(DYNAMIC_ICONV_DLL);
- if (hIconvDLL == 0) /* sometimes it's called libiconv.dll */
- hIconvDLL = vimLoadLib(DYNAMIC_ICONV_DLL_ALT);
- if (hIconvDLL != 0)
- hMsvcrtDLL = vimLoadLib(DYNAMIC_MSVCRT_DLL);
- if (hIconvDLL == 0 || hMsvcrtDLL == 0) {
- /* Only give the message when 'verbose' is set, otherwise it might be
- * done whenever a conversion is attempted. */
- if (verbose && p_verbose > 0) {
- verbose_enter();
- EMSG2(_(e_loadlib),
- hIconvDLL == 0 ? DYNAMIC_ICONV_DLL : DYNAMIC_MSVCRT_DLL);
- verbose_leave();
- }
- iconv_end();
- return false;
- }
-
- iconv = (void *)GetProcAddress(hIconvDLL, "libiconv");
- iconv_open = (void *)GetProcAddress(hIconvDLL, "libiconv_open");
- iconv_close = (void *)GetProcAddress(hIconvDLL, "libiconv_close");
- iconvctl = (void *)GetProcAddress(hIconvDLL, "libiconvctl");
- iconv_errno = get_iconv_import_func(hIconvDLL, "_errno");
- if (iconv_errno == NULL)
- iconv_errno = (void *)GetProcAddress(hMsvcrtDLL, "_errno");
- if (iconv == NULL || iconv_open == NULL || iconv_close == NULL
- || iconvctl == NULL || iconv_errno == NULL) {
- iconv_end();
- if (verbose && p_verbose > 0) {
- verbose_enter();
- EMSG2(_(e_loadfunc), "for libiconv");
- verbose_leave();
- }
- return false;
- }
- return true;
-}
-
-void iconv_end(void)
-{
- if (hIconvDLL != 0) {
- // TODO(justinmk): use uv_dlclose instead.
- FreeLibrary(hIconvDLL);
- }
- if (hMsvcrtDLL != 0) {
- FreeLibrary(hMsvcrtDLL);
- }
- hIconvDLL = 0;
- hMsvcrtDLL = 0;
-}
-
-# endif /* DYNAMIC_ICONV */
-# endif /* USE_ICONV */
+# endif // HAVE_ICONV
@@ -2314,10 +2220,11 @@ int convert_setup_ext(vimconv_T *vcp, char_u *from, bool from_unicode_is_utf8,
int from_is_utf8;
int to_is_utf8;
- /* Reset to no conversion. */
-# ifdef USE_ICONV
- if (vcp->vc_type == CONV_ICONV && vcp->vc_fd != (iconv_t)-1)
+ // Reset to no conversion.
+# ifdef HAVE_ICONV
+ if (vcp->vc_type == CONV_ICONV && vcp->vc_fd != (iconv_t)-1) {
iconv_close(vcp->vc_fd);
+ }
# endif
*vcp = (vimconv_T)MBYTE_NONE_CONV;
@@ -2352,9 +2259,9 @@ int convert_setup_ext(vimconv_T *vcp, char_u *from, bool from_unicode_is_utf8,
/* Internal utf-8 -> latin9 conversion. */
vcp->vc_type = CONV_TO_LATIN9;
}
-# ifdef USE_ICONV
- else {
- /* Use iconv() for conversion. */
+# ifdef HAVE_ICONV
+ else { // NOLINT(readability/braces)
+ // Use iconv() for conversion.
vcp->vc_fd = (iconv_t)my_iconv_open(
to_is_utf8 ? (char_u *)"utf-8" : to,
from_is_utf8 ? (char_u *)"utf-8" : from);
@@ -2506,8 +2413,8 @@ char_u * string_convert_ext(const vimconv_T *const vcp, char_u *ptr,
*lenp = (size_t)(d - retval);
break;
-# ifdef USE_ICONV
- case CONV_ICONV: /* conversion with vcp->vc_fd */
+# ifdef HAVE_ICONV
+ case CONV_ICONV: // conversion with vcp->vc_fd
retval = iconv_string(vcp, ptr, len, unconvlenp, lenp);
break;
# endif
diff --git a/src/nvim/mbyte.h b/src/nvim/mbyte.h
index ed48705c6d..536d58be1f 100644
--- a/src/nvim/mbyte.h
+++ b/src/nvim/mbyte.h
@@ -63,7 +63,7 @@ typedef enum {
typedef struct {
int vc_type; ///< Zero or more ConvFlags.
int vc_factor; ///< Maximal expansion factor.
-# ifdef USE_ICONV
+# ifdef HAVE_ICONV
iconv_t vc_fd; ///< Value for CONV_ICONV.
# endif
bool vc_fail; ///< What to do with invalid characters: if true, fail,
diff --git a/src/nvim/memline.c b/src/nvim/memline.c
index b027459706..34774055c1 100644
--- a/src/nvim/memline.c
+++ b/src/nvim/memline.c
@@ -47,6 +47,7 @@
#include "nvim/vim.h"
#include "nvim/memline.h"
#include "nvim/buffer.h"
+#include "nvim/change.h"
#include "nvim/cursor.h"
#include "nvim/eval.h"
#include "nvim/getchar.h"
@@ -1178,7 +1179,7 @@ void ml_recover(void)
/* Recovering an empty file results in two lines and the first line is
* empty. Don't set the modified flag then. */
if (!(curbuf->b_ml.ml_line_count == 2 && *ml_get(1) == NUL)) {
- changed_int();
+ changed_internal();
buf_inc_changedtick(curbuf);
}
} else {
@@ -1188,7 +1189,7 @@ void ml_recover(void)
i = STRCMP(p, ml_get(idx + lnum));
xfree(p);
if (i != 0) {
- changed_int();
+ changed_internal();
buf_inc_changedtick(curbuf);
break;
}
@@ -2383,6 +2384,23 @@ static int ml_append_int(
return OK;
}
+void ml_add_deleted_len(char_u *ptr, ssize_t len)
+{
+ if (inhibit_delete_count) {
+ return;
+ }
+ if (len == -1) {
+ len = STRLEN(ptr);
+ }
+ curbuf->deleted_bytes += len+1;
+ if (curbuf->update_need_codepoints) {
+ mb_utflen(ptr, len, &curbuf->deleted_codepoints,
+ &curbuf->deleted_codeunits);
+ curbuf->deleted_codepoints++; // NL char
+ curbuf->deleted_codeunits++;
+ }
+}
+
/*
* Replace line lnum, with buffering, in current buffer.
*
@@ -2403,13 +2421,24 @@ int ml_replace(linenr_T lnum, char_u *line, bool copy)
if (curbuf->b_ml.ml_mfp == NULL && open_buffer(FALSE, NULL, 0) == FAIL)
return FAIL;
+ bool readlen = true;
+
if (copy) {
line = vim_strsave(line);
}
- if (curbuf->b_ml.ml_line_lnum != lnum) /* other line buffered */
- ml_flush_line(curbuf); /* flush it */
- else if (curbuf->b_ml.ml_flags & ML_LINE_DIRTY) /* same line allocated */
- xfree(curbuf->b_ml.ml_line_ptr); /* free it */
+ if (curbuf->b_ml.ml_line_lnum != lnum) { // other line buffered
+ ml_flush_line(curbuf); // flush it
+ } else if (curbuf->b_ml.ml_flags & ML_LINE_DIRTY) { // same line allocated
+ ml_add_deleted_len(curbuf->b_ml.ml_line_ptr, -1);
+ readlen = false; // already added the length
+
+ xfree(curbuf->b_ml.ml_line_ptr); // free it
+ }
+
+ if (readlen && kv_size(curbuf->update_callbacks)) {
+ ml_add_deleted_len(ml_get_buf(curbuf, lnum, false), -1);
+ }
+
curbuf->b_ml.ml_line_ptr = line;
curbuf->b_ml.ml_line_lnum = lnum;
curbuf->b_ml.ml_flags = (curbuf->b_ml.ml_flags | ML_LINE_DIRTY) & ~ML_EMPTY;
@@ -2491,6 +2520,10 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message)
else
line_size = ((dp->db_index[idx - 1]) & DB_INDEX_MASK) - line_start;
+ // Line should always have an NL char internally (represented as NUL),
+ // even if 'noeol' is set.
+ assert(line_size >= 1);
+ ml_add_deleted_len((char_u *)dp + line_start, line_size-1);
/*
* special case: If there is only one line in the data block it becomes empty.
@@ -2676,6 +2709,17 @@ void ml_clearmarked(void)
return;
}
+size_t ml_flush_deleted_bytes(buf_T *buf, size_t *codepoints, size_t *codeunits)
+{
+ size_t ret = buf->deleted_bytes;
+ *codepoints = buf->deleted_codepoints;
+ *codeunits = buf->deleted_codeunits;
+ buf->deleted_bytes = 0;
+ buf->deleted_codepoints = 0;
+ buf->deleted_codeunits = 0;
+ return ret;
+}
+
/*
* flush ml_line if necessary
*/
@@ -2704,6 +2748,8 @@ static void ml_flush_line(buf_T *buf)
return;
entered = TRUE;
+ buf->flush_count++;
+
lnum = buf->b_ml.ml_line_lnum;
new_line = buf->b_ml.ml_line_ptr;
diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c
index db0d56b5fd..44e2c7df5f 100644
--- a/src/nvim/misc1.c
+++ b/src/nvim/misc1.c
@@ -65,877 +65,6 @@
static garray_T ga_users = GA_EMPTY_INIT_VALUE;
/*
- * open_line: Add a new line below or above the current line.
- *
- * For VREPLACE mode, we only add a new line when we get to the end of the
- * file, otherwise we just start replacing the next line.
- *
- * Caller must take care of undo. Since VREPLACE may affect any number of
- * lines however, it may call u_save_cursor() again when starting to change a
- * new line.
- * "flags": OPENLINE_DELSPACES delete spaces after cursor
- * OPENLINE_DO_COM format comments
- * OPENLINE_KEEPTRAIL keep trailing spaces
- * OPENLINE_MARKFIX adjust mark positions after the line break
- * OPENLINE_COM_LIST format comments with list or 2nd line indent
- *
- * "second_line_indent": indent for after ^^D in Insert mode or if flag
- * OPENLINE_COM_LIST
- *
- * Return TRUE for success, FALSE for failure
- */
-int
-open_line (
- int dir, /* FORWARD or BACKWARD */
- int flags,
- int second_line_indent
-)
-{
- char_u *next_line = NULL; // copy of the next line
- char_u *p_extra = NULL; // what goes to next line
- colnr_T less_cols = 0; // less columns for mark in new line
- colnr_T less_cols_off = 0; // columns to skip for mark adjust
- pos_T old_cursor; // old cursor position
- colnr_T newcol = 0; // new cursor column
- int newindent = 0; // auto-indent of the new line
- bool trunc_line = false; // truncate current line afterwards
- bool retval = false; // return value
- int extra_len = 0; // length of p_extra string
- int lead_len; // length of comment leader
- char_u *lead_flags; // position in 'comments' for comment leader
- char_u *leader = NULL; // copy of comment leader
- char_u *allocated = NULL; // allocated memory
- char_u *p;
- char_u saved_char = NUL; // init for GCC
- pos_T *pos;
- bool do_si = (!p_paste && curbuf->b_p_si && !curbuf->b_p_cin
- && *curbuf->b_p_inde == NUL);
- bool no_si = false; // reset did_si afterwards
- int first_char = NUL; // init for GCC
- int vreplace_mode;
- bool did_append; // appended a new line
- int saved_pi = curbuf->b_p_pi; // copy of preserveindent setting
-
- // make a copy of the current line so we can mess with it
- char_u *saved_line = vim_strsave(get_cursor_line_ptr());
-
- if (State & VREPLACE_FLAG) {
- /*
- * With VREPLACE we make a copy of the next line, which we will be
- * starting to replace. First make the new line empty and let vim play
- * with the indenting and comment leader to its heart's content. Then
- * we grab what it ended up putting on the new line, put back the
- * original line, and call ins_char() to put each new character onto
- * the line, replacing what was there before and pushing the right
- * stuff onto the replace stack. -- webb.
- */
- if (curwin->w_cursor.lnum < orig_line_count)
- next_line = vim_strsave(ml_get(curwin->w_cursor.lnum + 1));
- else
- next_line = vim_strsave((char_u *)"");
-
- /*
- * In VREPLACE mode, a NL replaces the rest of the line, and starts
- * replacing the next line, so push all of the characters left on the
- * line onto the replace stack. We'll push any other characters that
- * might be replaced at the start of the next line (due to autoindent
- * etc) a bit later.
- */
- replace_push(NUL); /* Call twice because BS over NL expects it */
- replace_push(NUL);
- p = saved_line + curwin->w_cursor.col;
- while (*p != NUL) {
- if (has_mbyte)
- p += replace_push_mb(p);
- else
- replace_push(*p++);
- }
- saved_line[curwin->w_cursor.col] = NUL;
- }
-
- if ((State & INSERT)
- && !(State & VREPLACE_FLAG)
- ) {
- p_extra = saved_line + curwin->w_cursor.col;
- if (do_si) { /* need first char after new line break */
- p = skipwhite(p_extra);
- first_char = *p;
- }
- extra_len = (int)STRLEN(p_extra);
- saved_char = *p_extra;
- *p_extra = NUL;
- }
-
- u_clearline(); // cannot do "U" command when adding lines
- did_si = false;
- ai_col = 0;
-
- /*
- * If we just did an auto-indent, then we didn't type anything on
- * the prior line, and it should be truncated. Do this even if 'ai' is not
- * set because automatically inserting a comment leader also sets did_ai.
- */
- if (dir == FORWARD && did_ai)
- trunc_line = TRUE;
-
- /*
- * If 'autoindent' and/or 'smartindent' is set, try to figure out what
- * indent to use for the new line.
- */
- if (curbuf->b_p_ai
- || do_si
- ) {
- // count white space on current line
- newindent = get_indent_str(saved_line, (int)curbuf->b_p_ts, false);
- if (newindent == 0 && !(flags & OPENLINE_COM_LIST)) {
- newindent = second_line_indent; // for ^^D command in insert mode
- }
-
- /*
- * Do smart indenting.
- * In insert/replace mode (only when dir == FORWARD)
- * we may move some text to the next line. If it starts with '{'
- * don't add an indent. Fixes inserting a NL before '{' in line
- * "if (condition) {"
- */
- if (!trunc_line && do_si && *saved_line != NUL
- && (p_extra == NULL || first_char != '{')) {
- char_u *ptr;
- char_u last_char;
-
- old_cursor = curwin->w_cursor;
- ptr = saved_line;
- if (flags & OPENLINE_DO_COM)
- lead_len = get_leader_len(ptr, NULL, FALSE, TRUE);
- else
- lead_len = 0;
- if (dir == FORWARD) {
- // Skip preprocessor directives, unless they are
- // recognised as comments.
- if (lead_len == 0 && ptr[0] == '#') {
- while (ptr[0] == '#' && curwin->w_cursor.lnum > 1) {
- ptr = ml_get(--curwin->w_cursor.lnum);
- }
- newindent = get_indent();
- }
- if (flags & OPENLINE_DO_COM)
- lead_len = get_leader_len(ptr, NULL, FALSE, TRUE);
- else
- lead_len = 0;
- if (lead_len > 0) {
- /*
- * This case gets the following right:
- * \*
- * * A comment (read '\' as '/').
- * *\
- * #define IN_THE_WAY
- * This should line up here;
- */
- p = skipwhite(ptr);
- if (p[0] == '/' && p[1] == '*')
- p++;
- if (p[0] == '*') {
- for (p++; *p; p++) {
- if (p[0] == '/' && p[-1] == '*') {
- /*
- * End of C comment, indent should line up
- * with the line containing the start of
- * the comment
- */
- curwin->w_cursor.col = (colnr_T)(p - ptr);
- if ((pos = findmatch(NULL, NUL)) != NULL) {
- curwin->w_cursor.lnum = pos->lnum;
- newindent = get_indent();
- }
- }
- }
- }
- } else { /* Not a comment line */
- /* Find last non-blank in line */
- p = ptr + STRLEN(ptr) - 1;
- while (p > ptr && ascii_iswhite(*p))
- --p;
- last_char = *p;
-
- /*
- * find the character just before the '{' or ';'
- */
- if (last_char == '{' || last_char == ';') {
- if (p > ptr)
- --p;
- while (p > ptr && ascii_iswhite(*p))
- --p;
- }
- /*
- * Try to catch lines that are split over multiple
- * lines. eg:
- * if (condition &&
- * condition) {
- * Should line up here!
- * }
- */
- if (*p == ')') {
- curwin->w_cursor.col = (colnr_T)(p - ptr);
- if ((pos = findmatch(NULL, '(')) != NULL) {
- curwin->w_cursor.lnum = pos->lnum;
- newindent = get_indent();
- ptr = get_cursor_line_ptr();
- }
- }
- /*
- * If last character is '{' do indent, without
- * checking for "if" and the like.
- */
- if (last_char == '{') {
- did_si = true; // do indent
- no_si = true; // don't delete it when '{' typed
- }
- /*
- * Look for "if" and the like, use 'cinwords'.
- * Don't do this if the previous line ended in ';' or
- * '}'.
- */
- else if (last_char != ';' && last_char != '}'
- && cin_is_cinword(ptr))
- did_si = true;
- }
- } else { // dir == BACKWARD
- // Skip preprocessor directives, unless they are
- // recognised as comments.
- if (lead_len == 0 && ptr[0] == '#') {
- bool was_backslashed = false;
-
- while ((ptr[0] == '#' || was_backslashed)
- && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) {
- if (*ptr && ptr[STRLEN(ptr) - 1] == '\\') {
- was_backslashed = true;
- } else {
- was_backslashed = false;
- }
- ptr = ml_get(++curwin->w_cursor.lnum);
- }
- if (was_backslashed) {
- newindent = 0; // Got to end of file
- } else {
- newindent = get_indent();
- }
- }
- p = skipwhite(ptr);
- if (*p == '}') { // if line starts with '}': do indent
- did_si = true;
- } else { // can delete indent when '{' typed
- can_si_back = true;
- }
- }
- curwin->w_cursor = old_cursor;
- }
- if (do_si) {
- can_si = true;
- }
-
- did_ai = true;
- }
-
- /*
- * Find out if the current line starts with a comment leader.
- * This may then be inserted in front of the new line.
- */
- end_comment_pending = NUL;
- if (flags & OPENLINE_DO_COM)
- lead_len = get_leader_len(saved_line, &lead_flags, dir == BACKWARD, TRUE);
- else
- lead_len = 0;
- if (lead_len > 0) {
- char_u *lead_repl = NULL; /* replaces comment leader */
- int lead_repl_len = 0; /* length of *lead_repl */
- char_u lead_middle[COM_MAX_LEN]; /* middle-comment string */
- char_u lead_end[COM_MAX_LEN]; /* end-comment string */
- char_u *comment_end = NULL; /* where lead_end has been found */
- int extra_space = FALSE; /* append extra space */
- int current_flag;
- int require_blank = FALSE; /* requires blank after middle */
- char_u *p2;
-
- /*
- * If the comment leader has the start, middle or end flag, it may not
- * be used or may be replaced with the middle leader.
- */
- for (p = lead_flags; *p && *p != ':'; ++p) {
- if (*p == COM_BLANK) {
- require_blank = TRUE;
- continue;
- }
- if (*p == COM_START || *p == COM_MIDDLE) {
- current_flag = *p;
- if (*p == COM_START) {
- /*
- * Doing "O" on a start of comment does not insert leader.
- */
- if (dir == BACKWARD) {
- lead_len = 0;
- break;
- }
-
- /* find start of middle part */
- (void)copy_option_part(&p, lead_middle, COM_MAX_LEN, ",");
- require_blank = FALSE;
- }
-
- /*
- * Isolate the strings of the middle and end leader.
- */
- while (*p && p[-1] != ':') { /* find end of middle flags */
- if (*p == COM_BLANK)
- require_blank = TRUE;
- ++p;
- }
- (void)copy_option_part(&p, lead_middle, COM_MAX_LEN, ",");
-
- while (*p && p[-1] != ':') { /* find end of end flags */
- /* Check whether we allow automatic ending of comments */
- if (*p == COM_AUTO_END)
- end_comment_pending = -1; /* means we want to set it */
- ++p;
- }
- size_t n = copy_option_part(&p, lead_end, COM_MAX_LEN, ",");
-
- if (end_comment_pending == -1) /* we can set it now */
- end_comment_pending = lead_end[n - 1];
-
- /*
- * If the end of the comment is in the same line, don't use
- * the comment leader.
- */
- if (dir == FORWARD) {
- for (p = saved_line + lead_len; *p; ++p)
- if (STRNCMP(p, lead_end, n) == 0) {
- comment_end = p;
- lead_len = 0;
- break;
- }
- }
-
- /*
- * Doing "o" on a start of comment inserts the middle leader.
- */
- if (lead_len > 0) {
- if (current_flag == COM_START) {
- lead_repl = lead_middle;
- lead_repl_len = (int)STRLEN(lead_middle);
- }
-
- /*
- * If we have hit RETURN immediately after the start
- * comment leader, then put a space after the middle
- * comment leader on the next line.
- */
- if (!ascii_iswhite(saved_line[lead_len - 1])
- && ((p_extra != NULL
- && (int)curwin->w_cursor.col == lead_len)
- || (p_extra == NULL
- && saved_line[lead_len] == NUL)
- || require_blank))
- extra_space = TRUE;
- }
- break;
- }
- if (*p == COM_END) {
- /*
- * Doing "o" on the end of a comment does not insert leader.
- * Remember where the end is, might want to use it to find the
- * start (for C-comments).
- */
- if (dir == FORWARD) {
- comment_end = skipwhite(saved_line);
- lead_len = 0;
- break;
- }
-
- /*
- * Doing "O" on the end of a comment inserts the middle leader.
- * Find the string for the middle leader, searching backwards.
- */
- while (p > curbuf->b_p_com && *p != ',')
- --p;
- for (lead_repl = p; lead_repl > curbuf->b_p_com
- && lead_repl[-1] != ':'; --lead_repl)
- ;
- lead_repl_len = (int)(p - lead_repl);
-
- /* We can probably always add an extra space when doing "O" on
- * the comment-end */
- extra_space = TRUE;
-
- /* Check whether we allow automatic ending of comments */
- for (p2 = p; *p2 && *p2 != ':'; p2++) {
- if (*p2 == COM_AUTO_END)
- end_comment_pending = -1; /* means we want to set it */
- }
- if (end_comment_pending == -1) {
- /* Find last character in end-comment string */
- while (*p2 && *p2 != ',')
- p2++;
- end_comment_pending = p2[-1];
- }
- break;
- }
- if (*p == COM_FIRST) {
- /*
- * Comment leader for first line only: Don't repeat leader
- * when using "O", blank out leader when using "o".
- */
- if (dir == BACKWARD)
- lead_len = 0;
- else {
- lead_repl = (char_u *)"";
- lead_repl_len = 0;
- }
- break;
- }
- }
- if (lead_len > 0) {
- // allocate buffer (may concatenate p_extra later)
- int bytes = lead_len
- + lead_repl_len
- + extra_space
- + extra_len
- + (second_line_indent > 0 ? second_line_indent : 0)
- + 1;
- assert(bytes >= 0);
- leader = xmalloc((size_t)bytes);
- allocated = leader; // remember to free it later
-
- STRLCPY(leader, saved_line, lead_len + 1);
-
- /*
- * Replace leader with lead_repl, right or left adjusted
- */
- if (lead_repl != NULL) {
- int c = 0;
- int off = 0;
-
- for (p = lead_flags; *p != NUL && *p != ':'; ) {
- if (*p == COM_RIGHT || *p == COM_LEFT)
- c = *p++;
- else if (ascii_isdigit(*p) || *p == '-')
- off = getdigits_int(&p);
- else
- ++p;
- }
- if (c == COM_RIGHT) { /* right adjusted leader */
- /* find last non-white in the leader to line up with */
- for (p = leader + lead_len - 1; p > leader
- && ascii_iswhite(*p); --p)
- ;
- ++p;
-
- /* Compute the length of the replaced characters in
- * screen characters, not bytes. */
- {
- int repl_size = vim_strnsize(lead_repl,
- lead_repl_len);
- int old_size = 0;
- char_u *endp = p;
- int l;
-
- while (old_size < repl_size && p > leader) {
- MB_PTR_BACK(leader, p);
- old_size += ptr2cells(p);
- }
- l = lead_repl_len - (int)(endp - p);
- if (l != 0)
- memmove(endp + l, endp,
- (size_t)((leader + lead_len) - endp));
- lead_len += l;
- }
- memmove(p, lead_repl, (size_t)lead_repl_len);
- if (p + lead_repl_len > leader + lead_len)
- p[lead_repl_len] = NUL;
-
- /* blank-out any other chars from the old leader. */
- while (--p >= leader) {
- int l = utf_head_off(leader, p);
-
- if (l > 1) {
- p -= l;
- if (ptr2cells(p) > 1) {
- p[1] = ' ';
- --l;
- }
- memmove(p + 1, p + l + 1,
- (size_t)((leader + lead_len) - (p + l + 1)));
- lead_len -= l;
- *p = ' ';
- } else if (!ascii_iswhite(*p))
- *p = ' ';
- }
- } else { /* left adjusted leader */
- p = skipwhite(leader);
- /* Compute the length of the replaced characters in
- * screen characters, not bytes. Move the part that is
- * not to be overwritten. */
- {
- int repl_size = vim_strnsize(lead_repl,
- lead_repl_len);
- int i;
- int l;
-
- for (i = 0; i < lead_len && p[i] != NUL; i += l) {
- l = (*mb_ptr2len)(p + i);
- if (vim_strnsize(p, i + l) > repl_size)
- break;
- }
- if (i != lead_repl_len) {
- memmove(p + lead_repl_len, p + i,
- (size_t)(lead_len - i - (p - leader)));
- lead_len += lead_repl_len - i;
- }
- }
- memmove(p, lead_repl, (size_t)lead_repl_len);
-
- /* Replace any remaining non-white chars in the old
- * leader by spaces. Keep Tabs, the indent must
- * remain the same. */
- for (p += lead_repl_len; p < leader + lead_len; ++p)
- if (!ascii_iswhite(*p)) {
- /* Don't put a space before a TAB. */
- if (p + 1 < leader + lead_len && p[1] == TAB) {
- lead_len--;
- memmove(p, p + 1, (size_t)(leader + lead_len - p));
- } else {
- int l = (*mb_ptr2len)(p);
-
- if (l > 1) {
- if (ptr2cells(p) > 1) {
- /* Replace a double-wide char with
- * two spaces */
- --l;
- *p++ = ' ';
- }
- memmove(p + 1, p + l, (size_t)(leader + lead_len - p));
- lead_len -= l - 1;
- }
- *p = ' ';
- }
- }
- *p = NUL;
- }
-
- /* Recompute the indent, it may have changed. */
- if (curbuf->b_p_ai
- || do_si
- )
- newindent = get_indent_str(leader, (int)curbuf->b_p_ts, false);
-
- /* Add the indent offset */
- if (newindent + off < 0) {
- off = -newindent;
- newindent = 0;
- } else
- newindent += off;
-
- /* Correct trailing spaces for the shift, so that
- * alignment remains equal. */
- while (off > 0 && lead_len > 0
- && leader[lead_len - 1] == ' ') {
- /* Don't do it when there is a tab before the space */
- if (vim_strchr(skipwhite(leader), '\t') != NULL)
- break;
- --lead_len;
- --off;
- }
-
- /* If the leader ends in white space, don't add an
- * extra space */
- if (lead_len > 0 && ascii_iswhite(leader[lead_len - 1]))
- extra_space = FALSE;
- leader[lead_len] = NUL;
- }
-
- if (extra_space) {
- leader[lead_len++] = ' ';
- leader[lead_len] = NUL;
- }
-
- newcol = lead_len;
-
- /*
- * if a new indent will be set below, remove the indent that
- * is in the comment leader
- */
- if (newindent
- || did_si
- ) {
- while (lead_len && ascii_iswhite(*leader)) {
- --lead_len;
- --newcol;
- ++leader;
- }
- }
-
- did_si = can_si = false;
- } else if (comment_end != NULL) {
- // We have finished a comment, so we don't use the leader.
- // If this was a C-comment and 'ai' or 'si' is set do a normal
- // indent to align with the line containing the start of the
- // comment.
- if (comment_end[0] == '*' && comment_end[1] == '/'
- && (curbuf->b_p_ai || do_si)) {
- old_cursor = curwin->w_cursor;
- curwin->w_cursor.col = (colnr_T)(comment_end - saved_line);
- if ((pos = findmatch(NULL, NUL)) != NULL) {
- curwin->w_cursor.lnum = pos->lnum;
- newindent = get_indent();
- }
- curwin->w_cursor = old_cursor;
- }
- }
- }
-
- /* (State == INSERT || State == REPLACE), only when dir == FORWARD */
- if (p_extra != NULL) {
- *p_extra = saved_char; /* restore char that NUL replaced */
-
- /*
- * When 'ai' set or "flags" has OPENLINE_DELSPACES, skip to the first
- * non-blank.
- *
- * When in REPLACE mode, put the deleted blanks on the replace stack,
- * preceded by a NUL, so they can be put back when a BS is entered.
- */
- if (REPLACE_NORMAL(State))
- replace_push(NUL); /* end of extra blanks */
- if (curbuf->b_p_ai || (flags & OPENLINE_DELSPACES)) {
- while ((*p_extra == ' ' || *p_extra == '\t')
- && !utf_iscomposing(utf_ptr2char(p_extra + 1))) {
- if (REPLACE_NORMAL(State)) {
- replace_push(*p_extra);
- }
- p_extra++;
- less_cols_off++;
- }
- }
-
- /* columns for marks adjusted for removed columns */
- less_cols = (int)(p_extra - saved_line);
- }
-
- if (p_extra == NULL)
- p_extra = (char_u *)""; /* append empty line */
-
- /* concatenate leader and p_extra, if there is a leader */
- if (lead_len > 0) {
- if (flags & OPENLINE_COM_LIST && second_line_indent > 0) {
- int i;
- int padding = second_line_indent
- - (newindent + (int)STRLEN(leader));
-
- /* Here whitespace is inserted after the comment char.
- * Below, set_indent(newindent, SIN_INSERT) will insert the
- * whitespace needed before the comment char. */
- for (i = 0; i < padding; i++) {
- STRCAT(leader, " ");
- less_cols--;
- newcol++;
- }
- }
- STRCAT(leader, p_extra);
- p_extra = leader;
- did_ai = true; // So truncating blanks works with comments
- less_cols -= lead_len;
- } else
- end_comment_pending = NUL; /* turns out there was no leader */
-
- old_cursor = curwin->w_cursor;
- if (dir == BACKWARD)
- --curwin->w_cursor.lnum;
- if (!(State & VREPLACE_FLAG) || old_cursor.lnum >= orig_line_count) {
- if (ml_append(curwin->w_cursor.lnum, p_extra, (colnr_T)0, false) == FAIL) {
- goto theend;
- }
- // Postpone calling changed_lines(), because it would mess up folding
- // with markers.
- // Skip mark_adjust when adding a line after the last one, there can't
- // be marks there. But still needed in diff mode.
- if (curwin->w_cursor.lnum + 1 < curbuf->b_ml.ml_line_count
- || curwin->w_p_diff) {
- mark_adjust(curwin->w_cursor.lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, false);
- }
- did_append = true;
- } else {
- /*
- * In VREPLACE mode we are starting to replace the next line.
- */
- curwin->w_cursor.lnum++;
- if (curwin->w_cursor.lnum >= Insstart.lnum + vr_lines_changed) {
- /* In case we NL to a new line, BS to the previous one, and NL
- * again, we don't want to save the new line for undo twice.
- */
- (void)u_save_cursor(); /* errors are ignored! */
- vr_lines_changed++;
- }
- ml_replace(curwin->w_cursor.lnum, p_extra, true);
- changed_bytes(curwin->w_cursor.lnum, 0);
- curwin->w_cursor.lnum--;
- did_append = FALSE;
- }
-
- if (newindent
- || did_si
- ) {
- ++curwin->w_cursor.lnum;
- if (did_si) {
- int sw = get_sw_value(curbuf);
-
- if (p_sr)
- newindent -= newindent % sw;
- newindent += sw;
- }
- /* Copy the indent */
- if (curbuf->b_p_ci) {
- (void)copy_indent(newindent, saved_line);
-
- /*
- * Set the 'preserveindent' option so that any further screwing
- * with the line doesn't entirely destroy our efforts to preserve
- * it. It gets restored at the function end.
- */
- curbuf->b_p_pi = TRUE;
- } else
- (void)set_indent(newindent, SIN_INSERT);
- less_cols -= curwin->w_cursor.col;
-
- ai_col = curwin->w_cursor.col;
-
- /*
- * In REPLACE mode, for each character in the new indent, there must
- * be a NUL on the replace stack, for when it is deleted with BS
- */
- if (REPLACE_NORMAL(State)) {
- for (colnr_T n = 0; n < curwin->w_cursor.col; n++) {
- replace_push(NUL);
- }
- }
- newcol += curwin->w_cursor.col;
- if (no_si) {
- did_si = false;
- }
- }
-
- /*
- * In REPLACE mode, for each character in the extra leader, there must be
- * a NUL on the replace stack, for when it is deleted with BS.
- */
- if (REPLACE_NORMAL(State))
- while (lead_len-- > 0)
- replace_push(NUL);
-
- curwin->w_cursor = old_cursor;
-
- if (dir == FORWARD) {
- if (trunc_line || (State & INSERT)) {
- // truncate current line at cursor
- saved_line[curwin->w_cursor.col] = NUL;
- // Remove trailing white space, unless OPENLINE_KEEPTRAIL used.
- if (trunc_line && !(flags & OPENLINE_KEEPTRAIL)) {
- truncate_spaces(saved_line);
- }
- ml_replace(curwin->w_cursor.lnum, saved_line, false);
- saved_line = NULL;
- if (did_append) {
- changed_lines(curwin->w_cursor.lnum, curwin->w_cursor.col,
- curwin->w_cursor.lnum + 1, 1L, true);
- did_append = false;
-
- /* Move marks after the line break to the new line. */
- if (flags & OPENLINE_MARKFIX)
- mark_col_adjust(curwin->w_cursor.lnum,
- curwin->w_cursor.col + less_cols_off,
- 1L, (long)-less_cols, 0);
- } else {
- changed_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col);
- }
- }
-
- /*
- * Put the cursor on the new line. Careful: the scrollup() above may
- * have moved w_cursor, we must use old_cursor.
- */
- curwin->w_cursor.lnum = old_cursor.lnum + 1;
- }
- if (did_append) {
- changed_lines(curwin->w_cursor.lnum, 0, curwin->w_cursor.lnum, 1L, true);
- }
-
- curwin->w_cursor.col = newcol;
- curwin->w_cursor.coladd = 0;
-
- /*
- * In VREPLACE mode, we are handling the replace stack ourselves, so stop
- * fixthisline() from doing it (via change_indent()) by telling it we're in
- * normal INSERT mode.
- */
- if (State & VREPLACE_FLAG) {
- vreplace_mode = State; /* So we know to put things right later */
- State = INSERT;
- } else
- vreplace_mode = 0;
- /*
- * May do lisp indenting.
- */
- if (!p_paste
- && leader == NULL
- && curbuf->b_p_lisp
- && curbuf->b_p_ai) {
- fixthisline(get_lisp_indent);
- ai_col = (colnr_T)getwhitecols_curline();
- }
- /*
- * May do indenting after opening a new line.
- */
- if (!p_paste
- && (curbuf->b_p_cin
- || *curbuf->b_p_inde != NUL
- )
- && in_cinkeys(dir == FORWARD
- ? KEY_OPEN_FORW
- : KEY_OPEN_BACK, ' ', linewhite(curwin->w_cursor.lnum))) {
- do_c_expr_indent();
- ai_col = (colnr_T)getwhitecols_curline();
- }
- if (vreplace_mode != 0)
- State = vreplace_mode;
-
- /*
- * Finally, VREPLACE gets the stuff on the new line, then puts back the
- * original line, and inserts the new stuff char by char, pushing old stuff
- * onto the replace stack (via ins_char()).
- */
- if (State & VREPLACE_FLAG) {
- /* Put new line in p_extra */
- p_extra = vim_strsave(get_cursor_line_ptr());
-
- // Put back original line
- ml_replace(curwin->w_cursor.lnum, next_line, false);
-
- /* Insert new stuff into line again */
- curwin->w_cursor.col = 0;
- curwin->w_cursor.coladd = 0;
- ins_bytes(p_extra); /* will call changed_bytes() */
- xfree(p_extra);
- next_line = NULL;
- }
-
- retval = true; // success!
-theend:
- curbuf->b_p_pi = saved_pi;
- xfree(saved_line);
- xfree(next_line);
- xfree(allocated);
- return retval;
-}
-
-/*
* get_leader_len() returns the length in bytes of the prefix of the given
* string which introduces a comment. If this string is not a comment then
* 0 is returned.
@@ -1378,400 +507,6 @@ int plines_m_win(win_T *wp, linenr_T first, linenr_T last)
return count;
}
-/*
- * Insert string "p" at the cursor position. Stops at a NUL byte.
- * Handles Replace mode and multi-byte characters.
- */
-void ins_bytes(char_u *p)
-{
- ins_bytes_len(p, STRLEN(p));
-}
-
-/// Insert string "p" with length "len" at the cursor position.
-/// Handles Replace mode and multi-byte characters.
-void ins_bytes_len(char_u *p, size_t len)
-{
- if (has_mbyte) {
- size_t n;
- for (size_t i = 0; i < len; i += n) {
- if (enc_utf8) {
- // avoid reading past p[len]
- n = (size_t)utfc_ptr2len_len(p + i, (int)(len - i));
- } else {
- n = (size_t)(*mb_ptr2len)(p + i);
- }
- ins_char_bytes(p + i, n);
- }
- } else {
- for (size_t i = 0; i < len; i++) {
- ins_char(p[i]);
- }
- }
-}
-
-/// Insert or replace a single character at the cursor position.
-/// When in REPLACE or VREPLACE mode, replace any existing character.
-/// Caller must have prepared for undo.
-/// For multi-byte characters we get the whole character, the caller must
-/// convert bytes to a character.
-void ins_char(int c)
-{
- char_u buf[MB_MAXBYTES + 1];
- size_t n = (size_t)utf_char2bytes(c, buf);
-
- // When "c" is 0x100, 0x200, etc. we don't want to insert a NUL byte.
- // Happens for CTRL-Vu9900.
- if (buf[0] == 0) {
- buf[0] = '\n';
- }
- ins_char_bytes(buf, n);
-}
-
-void ins_char_bytes(char_u *buf, size_t charlen)
-{
- // Break tabs if needed.
- if (virtual_active() && curwin->w_cursor.coladd > 0) {
- coladvance_force(getviscol());
- }
-
- size_t col = (size_t)curwin->w_cursor.col;
- linenr_T lnum = curwin->w_cursor.lnum;
- char_u *oldp = ml_get(lnum);
- size_t linelen = STRLEN(oldp) + 1; // length of old line including NUL
-
- // The lengths default to the values for when not replacing.
- size_t oldlen = 0; // nr of bytes inserted
- size_t newlen = charlen; // nr of bytes deleted (0 when not replacing)
-
- if (State & REPLACE_FLAG) {
- if (State & VREPLACE_FLAG) {
- // Disable 'list' temporarily, unless 'cpo' contains the 'L' flag.
- // Returns the old value of list, so when finished,
- // curwin->w_p_list should be set back to this.
- int old_list = curwin->w_p_list;
- if (old_list && vim_strchr(p_cpo, CPO_LISTWM) == NULL) {
- curwin->w_p_list = false;
- }
- // In virtual replace mode each character may replace one or more
- // characters (zero if it's a TAB). Count the number of bytes to
- // be deleted to make room for the new character, counting screen
- // cells. May result in adding spaces to fill a gap.
- colnr_T vcol;
- getvcol(curwin, &curwin->w_cursor, NULL, &vcol, NULL);
- colnr_T new_vcol = vcol + chartabsize(buf, vcol);
- while (oldp[col + oldlen] != NUL && vcol < new_vcol) {
- vcol += chartabsize(oldp + col + oldlen, vcol);
- // Don't need to remove a TAB that takes us to the right
- // position.
- if (vcol > new_vcol && oldp[col + oldlen] == TAB) {
- break;
- }
- oldlen += (size_t)(*mb_ptr2len)(oldp + col + oldlen);
- // Deleted a bit too much, insert spaces.
- if (vcol > new_vcol) {
- newlen += (size_t)(vcol - new_vcol);
- }
- }
- curwin->w_p_list = old_list;
- } else if (oldp[col] != NUL) {
- // normal replace
- oldlen = (size_t)(*mb_ptr2len)(oldp + col);
- }
-
-
- /* Push the replaced bytes onto the replace stack, so that they can be
- * put back when BS is used. The bytes of a multi-byte character are
- * done the other way around, so that the first byte is popped off
- * first (it tells the byte length of the character). */
- replace_push(NUL);
- for (size_t i = 0; i < oldlen; i++) {
- if (has_mbyte) {
- i += (size_t)replace_push_mb(oldp + col + i) - 1;
- } else {
- replace_push(oldp[col + i]);
- }
- }
- }
-
- char_u *newp = xmalloc((size_t)(linelen + newlen - oldlen));
-
- // Copy bytes before the cursor.
- if (col > 0) {
- memmove(newp, oldp, (size_t)col);
- }
-
- // Copy bytes after the changed character(s).
- char_u *p = newp + col;
- if (linelen > col + oldlen) {
- memmove(p + newlen, oldp + col + oldlen,
- (size_t)(linelen - col - oldlen));
- }
-
- // Insert or overwrite the new character.
- memmove(p, buf, charlen);
-
- // Fill with spaces when necessary.
- for (size_t i = charlen; i < newlen; i++) {
- p[i] = ' ';
- }
-
- // Replace the line in the buffer.
- ml_replace(lnum, newp, false);
-
- // mark the buffer as changed and prepare for displaying
- changed_bytes(lnum, (colnr_T)col);
-
- /*
- * If we're in Insert or Replace mode and 'showmatch' is set, then briefly
- * show the match for right parens and braces.
- */
- if (p_sm && (State & INSERT)
- && msg_silent == 0
- && !ins_compl_active()
- ) {
- showmatch(utf_ptr2char(buf));
- }
-
- if (!p_ri || (State & REPLACE_FLAG)) {
- // Normal insert: move cursor right
- curwin->w_cursor.col += (int)charlen;
- }
- /*
- * TODO: should try to update w_row here, to avoid recomputing it later.
- */
-}
-
-/*
- * Insert a string at the cursor position.
- * Note: Does NOT handle Replace mode.
- * Caller must have prepared for undo.
- */
-void ins_str(char_u *s)
-{
- char_u *oldp, *newp;
- int newlen = (int)STRLEN(s);
- int oldlen;
- colnr_T col;
- linenr_T lnum = curwin->w_cursor.lnum;
-
- if (virtual_active() && curwin->w_cursor.coladd > 0)
- coladvance_force(getviscol());
-
- col = curwin->w_cursor.col;
- oldp = ml_get(lnum);
- oldlen = (int)STRLEN(oldp);
-
- newp = (char_u *)xmalloc((size_t)oldlen + (size_t)newlen + 1);
- if (col > 0) {
- memmove(newp, oldp, (size_t)col);
- }
- memmove(newp + col, s, (size_t)newlen);
- int bytes = oldlen - col + 1;
- assert(bytes >= 0);
- memmove(newp + col + newlen, oldp + col, (size_t)bytes);
- ml_replace(lnum, newp, false);
- changed_bytes(lnum, col);
- curwin->w_cursor.col += newlen;
-}
-
-// Delete one character under the cursor.
-// If "fixpos" is true, don't leave the cursor on the NUL after the line.
-// Caller must have prepared for undo.
-//
-// return FAIL for failure, OK otherwise
-int del_char(bool fixpos)
-{
- if (has_mbyte) {
- /* Make sure the cursor is at the start of a character. */
- mb_adjust_cursor();
- if (*get_cursor_pos_ptr() == NUL)
- return FAIL;
- return del_chars(1L, fixpos);
- }
- return del_bytes(1, fixpos, true);
-}
-
-/*
- * Like del_bytes(), but delete characters instead of bytes.
- */
-int del_chars(long count, int fixpos)
-{
- int bytes = 0;
- long i;
- char_u *p;
- int l;
-
- p = get_cursor_pos_ptr();
- for (i = 0; i < count && *p != NUL; ++i) {
- l = (*mb_ptr2len)(p);
- bytes += l;
- p += l;
- }
- return del_bytes(bytes, fixpos, TRUE);
-}
-
-/// Delete "count" bytes under the cursor.
-/// If "fixpos" is true, don't leave the cursor on the NUL after the line.
-/// Caller must have prepared for undo.
-///
-/// @param count number of bytes to be deleted
-/// @param fixpos_arg leave the cursor on the NUL after the line
-/// @param use_delcombine 'delcombine' option applies
-///
-/// @return FAIL for failure, OK otherwise
-int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine)
-{
- linenr_T lnum = curwin->w_cursor.lnum;
- colnr_T col = curwin->w_cursor.col;
- bool fixpos = fixpos_arg;
- char_u *oldp = ml_get(lnum);
- colnr_T oldlen = (colnr_T)STRLEN(oldp);
-
- // Can't do anything when the cursor is on the NUL after the line.
- if (col >= oldlen) {
- return FAIL;
- }
- // If "count" is zero there is nothing to do.
- if (count == 0) {
- return OK;
- }
- // If "count" is negative the caller must be doing something wrong.
- if (count < 1) {
- IEMSGN("E950: Invalid count for del_bytes(): %ld", count);
- return FAIL;
- }
-
- /* If 'delcombine' is set and deleting (less than) one character, only
- * delete the last combining character. */
- if (p_deco && use_delcombine && enc_utf8
- && utfc_ptr2len(oldp + col) >= count) {
- int cc[MAX_MCO];
- int n;
-
- (void)utfc_ptr2char(oldp + col, cc);
- if (cc[0] != NUL) {
- /* Find the last composing char, there can be several. */
- n = col;
- do {
- col = n;
- count = utf_ptr2len(oldp + n);
- n += count;
- } while (UTF_COMPOSINGLIKE(oldp + col, oldp + n));
- fixpos = false;
- }
- }
-
- // When count is too big, reduce it.
- int movelen = oldlen - col - count + 1; // includes trailing NUL
- if (movelen <= 1) {
- /*
- * If we just took off the last character of a non-blank line, and
- * fixpos is TRUE, we don't want to end up positioned at the NUL,
- * unless "restart_edit" is set or 'virtualedit' contains "onemore".
- */
- if (col > 0 && fixpos && restart_edit == 0
- && (ve_flags & VE_ONEMORE) == 0
- ) {
- --curwin->w_cursor.col;
- curwin->w_cursor.coladd = 0;
- curwin->w_cursor.col -= utf_head_off(oldp, oldp + curwin->w_cursor.col);
- }
- count = oldlen - col;
- movelen = 1;
- }
-
- // If the old line has been allocated the deletion can be done in the
- // existing line. Otherwise a new line has to be allocated.
- bool was_alloced = ml_line_alloced(); // check if oldp was allocated
- char_u *newp;
- if (was_alloced) {
- newp = oldp; // use same allocated memory
- } else { // need to allocate a new line
- newp = xmalloc((size_t)(oldlen + 1 - count));
- memmove(newp, oldp, (size_t)col);
- }
- memmove(newp + col, oldp + col + count, (size_t)movelen);
- if (!was_alloced) {
- ml_replace(lnum, newp, false);
- }
-
- /* mark the buffer as changed and prepare for displaying */
- changed_bytes(lnum, curwin->w_cursor.col);
-
- return OK;
-}
-
-/*
- * Delete from cursor to end of line.
- * Caller must have prepared for undo.
- */
-void
-truncate_line (
- int fixpos /* if TRUE fix the cursor position when done */
-)
-{
- char_u *newp;
- linenr_T lnum = curwin->w_cursor.lnum;
- colnr_T col = curwin->w_cursor.col;
-
- if (col == 0) {
- newp = vim_strsave((char_u *)"");
- } else {
- newp = vim_strnsave(ml_get(lnum), (size_t)col);
- }
- ml_replace(lnum, newp, false);
-
- /* mark the buffer as changed and prepare for displaying */
- changed_bytes(lnum, curwin->w_cursor.col);
-
- /*
- * If "fixpos" is TRUE we don't want to end up positioned at the NUL.
- */
- if (fixpos && curwin->w_cursor.col > 0)
- --curwin->w_cursor.col;
-}
-
-/*
- * Delete "nlines" lines at the cursor.
- * Saves the lines for undo first if "undo" is TRUE.
- */
-void
-del_lines (
- long nlines, /* number of lines to delete */
- int undo /* if TRUE, prepare for undo */
-)
-{
- long n;
- linenr_T first = curwin->w_cursor.lnum;
-
- if (nlines <= 0)
- return;
-
- /* save the deleted lines for undo */
- if (undo && u_savedel(first, nlines) == FAIL)
- return;
-
- for (n = 0; n < nlines; ) {
- if (curbuf->b_ml.ml_flags & ML_EMPTY) /* nothing to delete */
- break;
-
- ml_delete(first, true);
- n++;
-
- /* If we delete the last line in the file, stop */
- if (first > curbuf->b_ml.ml_line_count)
- break;
- }
-
- /* Correct the cursor position before calling deleted_lines_mark(), it may
- * trigger a callback to display the cursor. */
- curwin->w_cursor.col = 0;
- check_cursor_lnum();
-
- /* adjust marks, mark the buffer as changed and prepare for displaying */
- deleted_lines_mark(first, n);
-}
-
int gchar_pos(pos_T *pos)
FUNC_ATTR_NONNULL_ARG(1)
{
@@ -1783,430 +518,6 @@ int gchar_pos(pos_T *pos)
}
/*
- * Call this function when something in the current buffer is changed.
- *
- * Most often called through changed_bytes() and changed_lines(), which also
- * mark the area of the display to be redrawn.
- *
- * Careful: may trigger autocommands that reload the buffer.
- */
-void changed(void)
-{
-
- if (!curbuf->b_changed) {
- int save_msg_scroll = msg_scroll;
-
- /* Give a warning about changing a read-only file. This may also
- * check-out the file, thus change "curbuf"! */
- change_warning(0);
-
- /* Create a swap file if that is wanted.
- * Don't do this for "nofile" and "nowrite" buffer types. */
- if (curbuf->b_may_swap
- && !bt_dontwrite(curbuf)
- ) {
- int save_need_wait_return = need_wait_return;
-
- need_wait_return = false;
- ml_open_file(curbuf);
-
- /* The ml_open_file() can cause an ATTENTION message.
- * Wait two seconds, to make sure the user reads this unexpected
- * message. Since we could be anywhere, call wait_return() now,
- * and don't let the emsg() set msg_scroll. */
- if (need_wait_return && emsg_silent == 0) {
- ui_flush();
- os_delay(2000L, true);
- wait_return(TRUE);
- msg_scroll = save_msg_scroll;
- } else {
- need_wait_return = save_need_wait_return;
- }
- }
- changed_int();
- }
- buf_inc_changedtick(curbuf);
-
- // If a pattern is highlighted, the position may now be invalid.
- highlight_match = false;
-}
-
-/*
- * Internal part of changed(), no user interaction.
- */
-void changed_int(void)
-{
- curbuf->b_changed = true;
- ml_setflags(curbuf);
- check_status(curbuf);
- redraw_tabline = TRUE;
- need_maketitle = TRUE; /* set window title later */
-}
-
-
-/*
- * Changed bytes within a single line for the current buffer.
- * - marks the windows on this buffer to be redisplayed
- * - marks the buffer changed by calling changed()
- * - invalidates cached values
- * Careful: may trigger autocommands that reload the buffer.
- */
-void changed_bytes(linenr_T lnum, colnr_T col)
-{
- changedOneline(curbuf, lnum);
- changed_common(lnum, col, lnum + 1, 0L);
- // notify any channels that are watching
- buf_updates_send_changes(curbuf, lnum, 1, 1, true);
-
- /* Diff highlighting in other diff windows may need to be updated too. */
- if (curwin->w_p_diff) {
- linenr_T wlnum;
-
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (wp->w_p_diff && wp != curwin) {
- redraw_win_later(wp, VALID);
- wlnum = diff_lnum_win(lnum, wp);
- if (wlnum > 0)
- changedOneline(wp->w_buffer, wlnum);
- }
- }
- }
-}
-
-static void changedOneline(buf_T *buf, linenr_T lnum)
-{
- if (buf->b_mod_set) {
- /* find the maximum area that must be redisplayed */
- if (lnum < buf->b_mod_top)
- buf->b_mod_top = lnum;
- else if (lnum >= buf->b_mod_bot)
- buf->b_mod_bot = lnum + 1;
- } else {
- /* set the area that must be redisplayed to one line */
- buf->b_mod_set = true;
- buf->b_mod_top = lnum;
- buf->b_mod_bot = lnum + 1;
- buf->b_mod_xlines = 0;
- }
-}
-
-/*
- * Appended "count" lines below line "lnum" in the current buffer.
- * Must be called AFTER the change and after mark_adjust().
- * Takes care of marking the buffer to be redrawn and sets the changed flag.
- */
-void appended_lines(linenr_T lnum, long count)
-{
- changed_lines(lnum + 1, 0, lnum + 1, count, true);
-}
-
-/*
- * Like appended_lines(), but adjust marks first.
- */
-void appended_lines_mark(linenr_T lnum, long count)
-{
- // Skip mark_adjust when adding a line after the last one, there can't
- // be marks there. But it's still needed in diff mode.
- if (lnum + count < curbuf->b_ml.ml_line_count || curwin->w_p_diff) {
- mark_adjust(lnum + 1, (linenr_T)MAXLNUM, count, 0L, false);
- }
- changed_lines(lnum + 1, 0, lnum + 1, count, true);
-}
-
-/*
- * Deleted "count" lines at line "lnum" in the current buffer.
- * Must be called AFTER the change and after mark_adjust().
- * Takes care of marking the buffer to be redrawn and sets the changed flag.
- */
-void deleted_lines(linenr_T lnum, long count)
-{
- changed_lines(lnum, 0, lnum + count, -count, true);
-}
-
-/*
- * Like deleted_lines(), but adjust marks first.
- * Make sure the cursor is on a valid line before calling, a GUI callback may
- * be triggered to display the cursor.
- */
-void deleted_lines_mark(linenr_T lnum, long count)
-{
- mark_adjust(lnum, (linenr_T)(lnum + count - 1), (long)MAXLNUM, -count, false);
- changed_lines(lnum, 0, lnum + count, -count, true);
-}
-
-/*
- * Changed lines for the current buffer.
- * Must be called AFTER the change and after mark_adjust().
- * - mark the buffer changed by calling changed()
- * - mark the windows on this buffer to be redisplayed
- * - invalidate cached values
- * "lnum" is the first line that needs displaying, "lnume" the first line
- * below the changed lines (BEFORE the change).
- * When only inserting lines, "lnum" and "lnume" are equal.
- * Takes care of calling changed() and updating b_mod_*.
- * Careful: may trigger autocommands that reload the buffer.
- */
-void
-changed_lines(
- linenr_T lnum, // first line with change
- colnr_T col, // column in first line with change
- linenr_T lnume, // line below last changed line
- long xtra, // number of extra lines (negative when deleting)
- bool do_buf_event // some callers like undo/redo call changed_lines()
- // and then increment changedtick *again*. This flag
- // allows these callers to send the nvim_buf_lines_event
- // events after they're done modifying changedtick.
-)
-{
- changed_lines_buf(curbuf, lnum, lnume, xtra);
-
- if (xtra == 0 && curwin->w_p_diff && !diff_internal()) {
- // When the number of lines doesn't change then mark_adjust() isn't
- // called and other diff buffers still need to be marked for
- // displaying.
- linenr_T wlnum;
-
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (wp->w_p_diff && wp != curwin) {
- redraw_win_later(wp, VALID);
- wlnum = diff_lnum_win(lnum, wp);
- if (wlnum > 0) {
- changed_lines_buf(wp->w_buffer, wlnum,
- lnume - lnum + wlnum, 0L);
- }
- }
- }
- }
-
- changed_common(lnum, col, lnume, xtra);
-
- if (do_buf_event) {
- int64_t num_added = (int64_t)(lnume + xtra - lnum);
- int64_t num_removed = lnume - lnum;
- buf_updates_send_changes(curbuf, lnum, num_added, num_removed, true);
- }
-}
-
-/// Mark line range in buffer as changed.
-///
-/// @param buf the buffer where lines were changed
-/// @param lnum first line with change
-/// @param lnume line below last changed line
-/// @param xtra number of extra lines (negative when deleting)
-void changed_lines_buf(buf_T *buf, linenr_T lnum, linenr_T lnume, long xtra)
-{
- if (buf->b_mod_set) {
- /* find the maximum area that must be redisplayed */
- if (lnum < buf->b_mod_top)
- buf->b_mod_top = lnum;
- if (lnum < buf->b_mod_bot) {
- /* adjust old bot position for xtra lines */
- buf->b_mod_bot += xtra;
- if (buf->b_mod_bot < lnum)
- buf->b_mod_bot = lnum;
- }
- if (lnume + xtra > buf->b_mod_bot)
- buf->b_mod_bot = lnume + xtra;
- buf->b_mod_xlines += xtra;
- } else {
- /* set the area that must be redisplayed */
- buf->b_mod_set = true;
- buf->b_mod_top = lnum;
- buf->b_mod_bot = lnume + xtra;
- buf->b_mod_xlines = xtra;
- }
-}
-
-/*
- * Common code for when a change is was made.
- * See changed_lines() for the arguments.
- * Careful: may trigger autocommands that reload the buffer.
- */
-static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, long xtra)
-{
- int i;
- int cols;
- pos_T *p;
- int add;
-
- /* mark the buffer as modified */
- changed();
-
- if (curwin->w_p_diff && diff_internal()) {
- curtab->tp_diff_update = true;
- }
-
- /* set the '. mark */
- if (!cmdmod.keepjumps) {
- RESET_FMARK(&curbuf->b_last_change, ((pos_T) {lnum, col, 0}), 0);
-
- /* Create a new entry if a new undo-able change was started or we
- * don't have an entry yet. */
- if (curbuf->b_new_change || curbuf->b_changelistlen == 0) {
- if (curbuf->b_changelistlen == 0)
- add = TRUE;
- else {
- /* Don't create a new entry when the line number is the same
- * as the last one and the column is not too far away. Avoids
- * creating many entries for typing "xxxxx". */
- p = &curbuf->b_changelist[curbuf->b_changelistlen - 1].mark;
- if (p->lnum != lnum)
- add = TRUE;
- else {
- cols = comp_textwidth(FALSE);
- if (cols == 0)
- cols = 79;
- add = (p->col + cols < col || col + cols < p->col);
- }
- }
- if (add) {
- /* This is the first of a new sequence of undo-able changes
- * and it's at some distance of the last change. Use a new
- * position in the changelist. */
- curbuf->b_new_change = false;
-
- if (curbuf->b_changelistlen == JUMPLISTSIZE) {
- /* changelist is full: remove oldest entry */
- curbuf->b_changelistlen = JUMPLISTSIZE - 1;
- memmove(curbuf->b_changelist, curbuf->b_changelist + 1,
- sizeof(curbuf->b_changelist[0]) * (JUMPLISTSIZE - 1));
- FOR_ALL_TAB_WINDOWS(tp, wp) {
- /* Correct position in changelist for other windows on
- * this buffer. */
- if (wp->w_buffer == curbuf && wp->w_changelistidx > 0) {
- --wp->w_changelistidx;
- }
- }
- }
- FOR_ALL_TAB_WINDOWS(tp, wp) {
- /* For other windows, if the position in the changelist is
- * at the end it stays at the end. */
- if (wp->w_buffer == curbuf
- && wp->w_changelistidx == curbuf->b_changelistlen) {
- ++wp->w_changelistidx;
- }
- }
- ++curbuf->b_changelistlen;
- }
- }
- curbuf->b_changelist[curbuf->b_changelistlen - 1] =
- curbuf->b_last_change;
- /* The current window is always after the last change, so that "g,"
- * takes you back to it. */
- curwin->w_changelistidx = curbuf->b_changelistlen;
- }
-
- FOR_ALL_TAB_WINDOWS(tp, wp) {
- if (wp->w_buffer == curbuf) {
- /* Mark this window to be redrawn later. */
- if (wp->w_redr_type < VALID)
- wp->w_redr_type = VALID;
-
- /* Check if a change in the buffer has invalidated the cached
- * values for the cursor. */
- /*
- * Update the folds for this window. Can't postpone this, because
- * a following operator might work on the whole fold: ">>dd".
- */
- foldUpdate(wp, lnum, lnume + xtra - 1);
-
- /* The change may cause lines above or below the change to become
- * included in a fold. Set lnum/lnume to the first/last line that
- * might be displayed differently.
- * Set w_cline_folded here as an efficient way to update it when
- * inserting lines just above a closed fold. */
- bool folded = hasFoldingWin(wp, lnum, &lnum, NULL, false, NULL);
- if (wp->w_cursor.lnum == lnum)
- wp->w_cline_folded = folded;
- folded = hasFoldingWin(wp, lnume, NULL, &lnume, false, NULL);
- if (wp->w_cursor.lnum == lnume)
- wp->w_cline_folded = folded;
-
- /* If the changed line is in a range of previously folded lines,
- * compare with the first line in that range. */
- if (wp->w_cursor.lnum <= lnum) {
- i = find_wl_entry(wp, lnum);
- if (i >= 0 && wp->w_cursor.lnum > wp->w_lines[i].wl_lnum)
- changed_line_abv_curs_win(wp);
- }
-
- if (wp->w_cursor.lnum > lnum)
- changed_line_abv_curs_win(wp);
- else if (wp->w_cursor.lnum == lnum && wp->w_cursor.col >= col)
- changed_cline_bef_curs_win(wp);
- if (wp->w_botline >= lnum) {
- /* Assume that botline doesn't change (inserted lines make
- * other lines scroll down below botline). */
- approximate_botline_win(wp);
- }
-
- /* Check if any w_lines[] entries have become invalid.
- * For entries below the change: Correct the lnums for
- * inserted/deleted lines. Makes it possible to stop displaying
- * after the change. */
- for (i = 0; i < wp->w_lines_valid; ++i)
- if (wp->w_lines[i].wl_valid) {
- if (wp->w_lines[i].wl_lnum >= lnum) {
- if (wp->w_lines[i].wl_lnum < lnume) {
- /* line included in change */
- wp->w_lines[i].wl_valid = FALSE;
- } else if (xtra != 0) {
- /* line below change */
- wp->w_lines[i].wl_lnum += xtra;
- wp->w_lines[i].wl_lastlnum += xtra;
- }
- } else if (wp->w_lines[i].wl_lastlnum >= lnum) {
- /* change somewhere inside this range of folded lines,
- * may need to be redrawn */
- wp->w_lines[i].wl_valid = FALSE;
- }
- }
-
- /* Take care of side effects for setting w_topline when folds have
- * changed. Esp. when the buffer was changed in another window. */
- if (hasAnyFolding(wp))
- set_topline(wp, wp->w_topline);
-
- // relative numbering may require updating more
- if (wp->w_p_rnu) {
- redraw_win_later(wp, SOME_VALID);
- }
- }
- }
-
- /* Call update_screen() later, which checks out what needs to be redrawn,
- * since it notices b_mod_set and then uses b_mod_*. */
- if (must_redraw < VALID)
- must_redraw = VALID;
-
- /* when the cursor line is changed always trigger CursorMoved */
- if (lnum <= curwin->w_cursor.lnum
- && lnume + (xtra < 0 ? -xtra : xtra) > curwin->w_cursor.lnum)
- curwin->w_last_cursormoved.lnum = 0;
-}
-
-/*
- * unchanged() is called when the changed flag must be reset for buffer 'buf'
- */
-void
-unchanged (
- buf_T *buf,
- int ff /* also reset 'fileformat' */
-)
-{
- if (buf->b_changed || (ff && file_ff_differs(buf, false))) {
- buf->b_changed = false;
- ml_setflags(buf);
- if (ff)
- save_file_ff(buf);
- check_status(buf);
- redraw_tabline = TRUE;
- need_maketitle = TRUE; /* set window title later */
- }
- buf_inc_changedtick(buf);
-}
-
-/*
* check_status: called when the status bars for the buffer 'buf'
* need to be updated
*/
@@ -2222,55 +533,6 @@ void check_status(buf_T *buf)
}
}
-/*
- * If the file is readonly, give a warning message with the first change.
- * Don't do this for autocommands.
- * Don't use emsg(), because it flushes the macro buffer.
- * If we have undone all changes b_changed will be false, but "b_did_warn"
- * will be true.
- * Careful: may trigger autocommands that reload the buffer.
- */
-void
-change_warning (
- int col /* column for message; non-zero when in insert
- mode and 'showmode' is on */
-)
-{
- static char *w_readonly = N_("W10: Warning: Changing a readonly file");
-
- if (curbuf->b_did_warn == false
- && curbufIsChanged() == 0
- && !autocmd_busy
- && curbuf->b_p_ro) {
- ++curbuf_lock;
- apply_autocmds(EVENT_FILECHANGEDRO, NULL, NULL, FALSE, curbuf);
- --curbuf_lock;
- if (!curbuf->b_p_ro)
- return;
- /*
- * Do what msg() does, but with a column offset if the warning should
- * be after the mode message.
- */
- msg_start();
- if (msg_row == Rows - 1)
- msg_col = col;
- msg_source(HL_ATTR(HLF_W));
- msg_ext_set_kind("wmsg");
- MSG_PUTS_ATTR(_(w_readonly), HL_ATTR(HLF_W) | MSG_HIST);
- set_vim_var_string(VV_WARNINGMSG, _(w_readonly), -1);
- msg_clr_eos();
- (void)msg_end();
- if (msg_silent == 0 && !silent_mode && ui_active()) {
- ui_flush();
- os_delay(1000L, true); /* give the user time to think about it */
- }
- curbuf->b_did_warn = true;
- redraw_cmdline = FALSE; /* don't redraw and erase the message */
- if (msg_row < Rows - 1)
- showmode();
- }
-}
-
/// Ask for a reply from the user, 'y' or 'n'
///
/// No other characters are accepted, the message is repeated until a valid
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index eeb41a5d13..73841cf449 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -18,6 +18,7 @@
#include "nvim/ascii.h"
#include "nvim/normal.h"
#include "nvim/buffer.h"
+#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/diff.h"
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index 35ab9c4d84..4f1709bb1f 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -15,6 +15,7 @@
#include "nvim/ascii.h"
#include "nvim/ops.h"
#include "nvim/buffer.h"
+#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/assert.h"
diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c
index 5fdf0e6181..f0bc13783c 100644
--- a/src/nvim/os/pty_process_unix.c
+++ b/src/nvim/os/pty_process_unix.c
@@ -288,7 +288,7 @@ static void chld_handler(uv_signal_t *handle, int signum)
if (WIFEXITED(stat)) {
proc->status = WEXITSTATUS(stat);
} else if (WIFSIGNALED(stat)) {
- proc->status = WTERMSIG(stat);
+ proc->status = 128 + WTERMSIG(stat);
}
proc->internal_exit_cb(proc);
}
diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c
index c5f8efadff..290668bca3 100644
--- a/src/nvim/os/pty_process_win.c
+++ b/src/nvim/os/pty_process_win.c
@@ -252,7 +252,7 @@ static void pty_process_finish2(PtyProcess *ptyproc)
DWORD exit_code = 0;
GetExitCodeProcess(ptyproc->process_handle, &exit_code);
- proc->status = (int)exit_code;
+ proc->status = proc->exit_signal ? 128 + proc->exit_signal : (int)exit_code;
CloseHandle(ptyproc->process_handle);
ptyproc->process_handle = NULL;
diff --git a/src/nvim/search.c b/src/nvim/search.c
index 7d46b3b4d5..fe4fdf57ba 100644
--- a/src/nvim/search.c
+++ b/src/nvim/search.c
@@ -4242,6 +4242,7 @@ static void search_stat(int dirc, pos_T *pos,
// STRNICMP ignores case, but we should not ignore case.
// Unfortunately, there is no STRNICMP function.
if (!(chgtick == buf_get_changedtick(curbuf)
+ && lastpat != NULL // supress clang/NULL passed as nonnull parameter
&& STRNICMP(lastpat, spats[last_idx].pat, STRLEN(lastpat)) == 0
&& STRLEN(lastpat) == STRLEN(spats[last_idx].pat)
&& equalpos(lastpos, curwin->w_cursor)
diff --git a/src/nvim/spell.c b/src/nvim/spell.c
index cc214616f4..8d800843f8 100644
--- a/src/nvim/spell.c
+++ b/src/nvim/spell.c
@@ -84,6 +84,7 @@
#include "nvim/ascii.h"
#include "nvim/spell.h"
#include "nvim/buffer.h"
+#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/edit.h"
@@ -1807,9 +1808,11 @@ void count_common_word(slang_T *lp, char_u *word, int len, int count)
char_u buf[MAXWLEN];
char_u *p;
- if (len == -1)
+ if (len == -1) {
p = word;
- else {
+ } else if (len >= MAXWLEN) {
+ return;
+ } else {
STRLCPY(buf, word, len + 1);
p = buf;
}
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index d8d529d0f6..5aeea3223b 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -53,6 +53,7 @@
#include "nvim/macros.h"
#include "nvim/mbyte.h"
#include "nvim/buffer.h"
+#include "nvim/change.h"
#include "nvim/ascii.h"
#include "nvim/getchar.h"
#include "nvim/ui.h"
@@ -1306,7 +1307,7 @@ static void refresh_screen(Terminal *term, buf_T *buf)
static void adjust_topline(Terminal *term, buf_T *buf, long added)
{
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
if (wp->w_buffer == buf) {
linenr_T ml_end = buf->b_ml.ml_line_count;
bool following = ml_end == wp->w_cursor.lnum + added; // cursor at end?
diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim
index b3438cc649..230cb72335 100644
--- a/src/nvim/testdir/test_spell.vim
+++ b/src/nvim/testdir/test_spell.vim
@@ -1,4 +1,5 @@
" Test spell checking
+" Note: this file uses latin1 encoding, but is used with utf-8 encoding.
if !has('spell')
finish
@@ -68,6 +69,47 @@ func Test_z_equal_on_invalid_utf8_word()
bwipe!
endfunc
+" Test spellbadword() with argument
+func Test_spellbadword()
+ set spell
+
+ call assert_equal(['bycycle', 'bad'], spellbadword('My bycycle.'))
+ call assert_equal(['another', 'caps'], spellbadword('A sentence. another sentence'))
+
+ set spelllang=en
+ call assert_equal(['', ''], spellbadword('centre'))
+ call assert_equal(['', ''], spellbadword('center'))
+ set spelllang=en_us
+ call assert_equal(['centre', 'local'], spellbadword('centre'))
+ call assert_equal(['', ''], spellbadword('center'))
+ set spelllang=en_gb
+ call assert_equal(['', ''], spellbadword('centre'))
+ call assert_equal(['center', 'local'], spellbadword('center'))
+
+ " Create a small word list to test that spellbadword('...')
+ " can return ['...', 'rare'].
+ e Xwords
+ insert
+foo
+foobar/?
+.
+ w!
+ mkspell! Xwords.spl Xwords
+ set spelllang=Xwords.spl
+ call assert_equal(['foobar', 'rare'], spellbadword('foo foobar'))
+
+ " Typo should not be detected without the 'spell' option.
+ set spelllang=en_gb nospell
+ call assert_equal(['', ''], spellbadword('centre'))
+ call assert_equal(['', ''], spellbadword('My bycycle.'))
+ call assert_equal(['', ''], spellbadword('A sentence. another sentence'))
+
+ call delete('Xwords.spl')
+ call delete('Xwords')
+ set spelllang&
+ set spell&
+endfunc
+
func Test_spellreall()
new
set spell
@@ -351,6 +393,18 @@ func Test_zeq_crash()
bwipe!
endfunc
+" Check handling a word longer than MAXWLEN.
+func Test_spell_long_word()
+ set enc=utf-8
+ new
+ call setline(1, "d\xCC\xB4\xCC\xBD\xCD\x88\xCD\x94a\xCC\xB5\xCD\x84\xCD\x84\xCC\xA8\xCD\x9Cr\xCC\xB5\xCC\x8E\xCD\x85\xCD\x85k\xCC\xB6\xCC\x89\xCC\x9D \xCC\xB6\xCC\x83\xCC\x8F\xCC\xA4\xCD\x8Ef\xCC\xB7\xCC\x81\xCC\x80\xCC\xA9\xCC\xB0\xCC\xAC\xCC\xA2\xCD\x95\xCD\x87\xCD\x8D\xCC\x9E\xCD\x99\xCC\xAD\xCC\xAB\xCC\x97\xCC\xBBo\xCC\xB6\xCC\x84\xCC\x95\xCC\x8C\xCC\x8B\xCD\x9B\xCD\x9C\xCC\xAFr\xCC\xB7\xCC\x94\xCD\x83\xCD\x97\xCC\x8C\xCC\x82\xCD\x82\xCD\x80\xCD\x91\xCC\x80\xCC\xBE\xCC\x82\xCC\x8F\xCC\xA3\xCD\x85\xCC\xAE\xCD\x8D\xCD\x99\xCC\xBC\xCC\xAB\xCC\xA7\xCD\x88c\xCC\xB7\xCD\x83\xCC\x84\xCD\x92\xCC\x86\xCC\x83\xCC\x88\xCC\x92\xCC\x94\xCC\xBE\xCC\x9D\xCC\xAF\xCC\x98\xCC\x9D\xCC\xBB\xCD\x8E\xCC\xBB\xCC\xB3\xCC\xA3\xCD\x8E\xCD\x99\xCC\xA5\xCC\xAD\xCC\x99\xCC\xB9\xCC\xAE\xCC\xA5\xCC\x9E\xCD\x88\xCC\xAE\xCC\x9E\xCC\xA9\xCC\x97\xCC\xBC\xCC\x99\xCC\xA5\xCD\x87\xCC\x97\xCD\x8E\xCD\x94\xCC\x99\xCC\x9D\xCC\x96\xCD\x94\xCC\xAB\xCC\xA7\xCC\xA5\xCC\x98\xCC\xBB\xCC\xAF\xCC\xABe\xCC\xB7\xCC\x8E\xCC\x82\xCD\x86\xCD\x9B\xCC\x94\xCD\x83\xCC\x85\xCD\x8A\xCD\x8C\xCC\x8B\xCD\x92\xCD\x91\xCC\x8F\xCC\x81\xCD\x95\xCC\xA2\xCC\xB9\xCC\xB2\xCD\x9C\xCC\xB1\xCC\xA6\xCC\xB3\xCC\xAF\xCC\xAE\xCC\x9C\xCD\x99s\xCC\xB8\xCC\x8C\xCC\x8E\xCC\x87\xCD\x81\xCD\x82\xCC\x86\xCD\x8C\xCD\x8C\xCC\x8B\xCC\x84\xCC\x8C\xCD\x84\xCD\x9B\xCD\x86\xCC\x93\xCD\x90\xCC\x85\xCC\x94\xCD\x98\xCD\x84\xCD\x92\xCD\x8B\xCC\x90\xCC\x83\xCC\x8F\xCD\x84\xCD\x81\xCD\x9B\xCC\x90\xCD\x81\xCC\x8F\xCC\xBD\xCC\x88\xCC\xBF\xCC\x88\xCC\x84\xCC\x8E\xCD\x99\xCD\x94\xCC\x99\xCD\x99\xCC\xB0\xCC\xA8\xCC\xA3\xCC\xA8\xCC\x96\xCC\x99\xCC\xAE\xCC\xBC\xCC\x99\xCD\x9A\xCC\xB2\xCC\xB1\xCC\x9F\xCC\xBB\xCC\xA6\xCD\x85\xCC\xAA\xCD\x89\xCC\x9D\xCC\x99\xCD\x96\xCC\xB1\xCC\xB1\xCC\x99\xCC\xA6\xCC\xA5\xCD\x95\xCC\xB2\xCC\xA0\xCD\x99 within")
+ set spell spelllang=en
+ redraw
+ redraw!
+ bwipe!
+ set nospell
+endfunc
+
func LoadAffAndDic(aff_contents, dic_contents)
throw 'skipped: Nvim does not support enc=latin1'
set enc=latin1
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
index 1305e013ad..1a31f6a6c7 100644
--- a/src/nvim/undo.c
+++ b/src/nvim/undo.c
@@ -85,6 +85,7 @@
#include "nvim/buffer.h"
#include "nvim/ascii.h"
+#include "nvim/change.h"
#include "nvim/undo.h"
#include "nvim/cursor.h"
#include "nvim/edit.h"
diff --git a/src/nvim/version.c b/src/nvim/version.c
index 33b2a05cbb..74a4852def 100644
--- a/src/nvim/version.c
+++ b/src/nvim/version.c
@@ -52,12 +52,8 @@ static char *features[] = {
"-acl",
#endif
-#if (defined(HAVE_ICONV_H) && defined(USE_ICONV)) || defined(DYNAMIC_ICONV)
-# ifdef DYNAMIC_ICONV
-"+iconv/dyn",
-# else
+#if defined(HAVE_ICONV)
"+iconv",
-# endif
#else
"-iconv",
#endif
@@ -2034,14 +2030,19 @@ static void version_msg(char *s)
version_msg_wrap((char_u *)s, false);
}
-/// List all features aligned in columns, dictionary style.
+/// List all features.
+/// This does not use list_in_columns (as in Vim), because there are only a
+/// few, and we do not start at a new line.
static void list_features(void)
{
- list_in_columns((char_u **)features, -1, -1);
- if (msg_col > 0) {
- msg_putchar('\n');
+ version_msg(_("\n\nFeatures: "));
+ for (int i = 0; features[i] != NULL; i++) {
+ version_msg(features[i]);
+ if (features[i+1] != NULL) {
+ version_msg(" ");
+ }
}
- MSG_PUTS("See \":help feature-compile\"\n\n");
+ version_msg("\nSee \":help feature-compile\"\n\n");
}
/// List string items nicely aligned in columns.
@@ -2150,8 +2151,6 @@ void list_version(void)
}
#endif // ifdef HAVE_PATHDEF
- version_msg(_("\n\nFeatures: "));
-
list_features();
#ifdef SYS_VIMRC_FILE
diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua
index 212b76b5d9..180ed9aa02 100644
--- a/test/functional/core/job_spec.lua
+++ b/test/functional/core/job_spec.lua
@@ -183,7 +183,7 @@ describe('jobs', function()
)
nvim('command', "call jobstop(j)")
eq({'notification', 'stdout', {0, {''}}}, next_msg())
- eq({'notification', 'exit', {0, iswin() and 15 or 0}}, next_msg())
+ eq({'notification', 'exit', {0, 143}}, next_msg())
end)
it('preserves NULs', function()
@@ -217,7 +217,7 @@ describe('jobs', function()
eq({'notification', 'stdout', {0, {'abc', 'xyz'}}}, next_msg())
nvim('command', "call jobstop(j)")
eq({'notification', 'stdout', {0, {''}}}, next_msg())
- eq({'notification', 'exit', {0, iswin() and 15 or 0}}, next_msg())
+ eq({'notification', 'exit', {0, 143}}, next_msg())
end)
it('preserves newlines', function()
@@ -234,7 +234,7 @@ describe('jobs', function()
next_msg())
nvim('command', "call jobstop(j)")
eq({'notification', 'stdout', {0, {''}}}, next_msg())
- eq({'notification', 'exit', {0, iswin() and 15 or 0}}, next_msg())
+ eq({'notification', 'exit', {0, 143}}, next_msg())
end)
it('avoids sending final newline', function()
@@ -244,7 +244,7 @@ describe('jobs', function()
next_msg())
nvim('command', "call jobstop(j)")
eq({'notification', 'stdout', {0, {''}}}, next_msg())
- eq({'notification', 'exit', {0, iswin() and 15 or 0}}, next_msg())
+ eq({'notification', 'exit', {0, 143}}, next_msg())
end)
it('closes the job streams with jobclose', function()
@@ -284,18 +284,7 @@ describe('jobs', function()
neq(NIL, meths.get_proc(pid))
nvim('command', 'call jobstop(j)')
eq({'notification', 'stdout', {0, {''}}}, next_msg())
- if iswin() then
- expect_msg_seq(
- -- win64
- { {'notification', 'exit', {0, 1}}
- },
- -- win32
- { {'notification', 'exit', {0, 15}}
- }
- )
- else
- eq({'notification', 'exit', {0, 0}}, next_msg())
- end
+ eq({'notification', 'exit', {0, 143}}, next_msg())
eq(NIL, meths.get_proc(pid))
end)
diff --git a/test/functional/fixtures/shell-test.c b/test/functional/fixtures/shell-test.c
index f1357c5dbb..550e5dd997 100644
--- a/test/functional/fixtures/shell-test.c
+++ b/test/functional/fixtures/shell-test.c
@@ -40,6 +40,7 @@ static void help(void)
puts(" 0: foo bar");
puts(" ...");
puts(" 96: foo bar");
+ puts(" shell-test REP_NODELAY N {text}");
puts(" shell-test INTERACT");
puts(" Prints \"interact $ \" to stderr, and waits for \"exit\" input.");
}
@@ -66,7 +67,8 @@ int main(int argc, char **argv)
if (argc >= 3) {
fprintf(stderr, "%s\n", argv[2]);
}
- } else if (strcmp(argv[1], "REP") == 0) {
+ } else if (strcmp(argv[1], "REP") == 0 ||
+ strcmp(argv[1], "REP_NODELAY") == 0) {
if (argc != 4) {
fprintf(stderr, "REP expects exactly 3 arguments\n");
return 4;
@@ -76,10 +78,17 @@ int main(int argc, char **argv)
fprintf(stderr, "Invalid count: %s\n", argv[2]);
return 4;
}
- for (int i = 0; i < count; i++) {
- printf("%d: %s\n", i, argv[3]);
- fflush(stdout);
- usleep(1000); // Wait 1 ms (simulate typical output).
+ if (strcmp(argv[1], "REP_NODELAY") == 0) {
+ for (int i = 0; i < count; i++) {
+ printf("%d: %s\n", i, argv[3]);
+ fflush(stdout);
+ }
+ } else {
+ for (int i = 0; i < count; i++) {
+ printf("%d: %s\n", i, argv[3]);
+ fflush(stdout);
+ usleep(1000); // Wait 1 ms (simulate typical output).
+ }
}
} else if (strcmp(argv[1], "UTF-8") == 0) {
// test split-up UTF-8 sequence
diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua
index 8223290760..d4ace3030c 100644
--- a/test/functional/helpers.lua
+++ b/test/functional/helpers.lua
@@ -1,6 +1,7 @@
require('coxpcall')
local luv = require('luv')
local lfs = require('lfs')
+local mpack = require('mpack')
local global_helpers = require('test.helpers')
-- nvim client: Found in .deps/usr/share/lua/<version>/nvim/ if "bundled".
@@ -20,26 +21,32 @@ local sleep = global_helpers.sleep
local tbl_contains = global_helpers.tbl_contains
local write_file = global_helpers.write_file
+local module = {
+ NIL = mpack.NIL,
+ mkdir = lfs.mkdir,
+}
+
local start_dir = lfs.currentdir()
-- XXX: NVIM_PROG takes precedence, QuickBuild sets it.
-local nvim_prog = (
+module.nvim_prog = (
os.getenv('NVIM_PROG')
or os.getenv('NVIM_PRG')
or global_helpers.test_build_dir .. '/bin/nvim'
)
-- Default settings for the test session.
-local nvim_set = 'set shortmess+=IS background=light noswapfile noautoindent'
- ..' laststatus=1 undodir=. directory=. viewdir=. backupdir=.'
- ..' belloff= wildoptions-=pum noshowcmd noruler nomore'
-local nvim_argv = {nvim_prog, '-u', 'NONE', '-i', 'NONE',
- '--cmd', nvim_set, '--embed'}
+module.nvim_set = (
+ 'set shortmess+=IS background=light noswapfile noautoindent'
+ ..' laststatus=1 undodir=. directory=. viewdir=. backupdir=.'
+ ..' belloff= wildoptions-=pum noshowcmd noruler nomore')
+module.nvim_argv = {
+ module.nvim_prog, '-u', 'NONE', '-i', 'NONE',
+ '--cmd', module.nvim_set, '--embed'}
-- Directory containing nvim.
-local nvim_dir = nvim_prog:gsub("[/\\][^/\\]+$", "")
-if nvim_dir == nvim_prog then
- nvim_dir = "."
+module.nvim_dir = module.nvim_prog:gsub("[/\\][^/\\]+$", "")
+if module.nvim_dir == module.nvim_prog then
+ module.nvim_dir = "."
end
-local mpack = require('mpack')
local tmpname = global_helpers.tmpname
local uname = global_helpers.uname
local prepend_argv
@@ -69,26 +76,27 @@ if prepend_argv then
for i = 1, len do
new_nvim_argv[i] = prepend_argv[i]
end
- for i = 1, #nvim_argv do
- new_nvim_argv[i + len] = nvim_argv[i]
+ for i = 1, #module.nvim_argv do
+ new_nvim_argv[i + len] = module.nvim_argv[i]
end
- nvim_argv = new_nvim_argv
+ module.nvim_argv = new_nvim_argv
+ module.prepend_argv = prepend_argv
end
local session, loop_running, last_error, method_error
-local function get_session()
+function module.get_session()
return session
end
-local function set_session(s, keep)
+function module.set_session(s, keep)
if session and not keep then
session:close()
end
session = s
end
-local function request(method, ...)
+function module.request(method, ...)
local status, rv = session:request(method, ...)
if not status then
if loop_running then
@@ -101,14 +109,14 @@ local function request(method, ...)
return rv
end
-local function next_msg(timeout)
+function module.next_msg(timeout)
return session:next_message(timeout and timeout or 10000)
end
-local function expect_twostreams(msgs1, msgs2)
+function module.expect_twostreams(msgs1, msgs2)
local pos1, pos2 = 1, 1
while pos1 <= #msgs1 or pos2 <= #msgs2 do
- local msg = next_msg()
+ local msg = module.next_msg()
if pos1 <= #msgs1 and pcall(eq, msgs1[pos1], msg) then
pos1 = pos1 + 1
elseif pos2 <= #msgs2 then
@@ -131,7 +139,7 @@ end
--
-- ignore: List of ignored event names.
-- seqs: List of one or more potential event sequences.
-local function expect_msg_seq(...)
+function module.expect_msg_seq(...)
if select('#', ...) < 1 then
error('need at least 1 argument')
end
@@ -157,11 +165,12 @@ local function expect_msg_seq(...)
end
return string.format('%s\n%s\n%s', err1, string.rep('=', 78), err2)
end
+ local msg_timeout = module.load_adjust(10000) -- Big timeout for ASAN/valgrind.
for anum = 1, #seqs do
local expected_seq = seqs[anum]
-- Collect enough messages to compare the next expected sequence.
while #actual_seq < #expected_seq do
- local msg = next_msg(10000) -- Big timeout for ASAN/valgrind.
+ local msg = module.next_msg(msg_timeout)
local msg_type = msg and msg[2] or nil
if msg == nil then
error(cat_err(final_error,
@@ -192,11 +201,11 @@ local function call_and_stop_on_error(lsession, ...)
return result
end
-local function set_method_error(err)
+function module.set_method_error(err)
method_error = err
end
-local function run_session(lsession, request_cb, notification_cb, setup_cb, timeout)
+function module.run_session(lsession, request_cb, notification_cb, setup_cb, timeout)
local on_request, on_notification, on_setup
if request_cb then
@@ -232,43 +241,43 @@ local function run_session(lsession, request_cb, notification_cb, setup_cb, time
end
end
-local function run(request_cb, notification_cb, setup_cb, timeout)
- run_session(session, request_cb, notification_cb, setup_cb, timeout)
+function module.run(request_cb, notification_cb, setup_cb, timeout)
+ module.run_session(session, request_cb, notification_cb, setup_cb, timeout)
end
-local function stop()
+function module.stop()
session:stop()
end
-local function nvim_prog_abs()
+function module.nvim_prog_abs()
-- system(['build/bin/nvim']) does not work for whatever reason. It must
-- be executable searched in $PATH or something starting with / or ./.
- if nvim_prog:match('[/\\]') then
- return request('nvim_call_function', 'fnamemodify', {nvim_prog, ':p'})
+ if module.nvim_prog:match('[/\\]') then
+ return module.request('nvim_call_function', 'fnamemodify', {module.nvim_prog, ':p'})
else
- return nvim_prog
+ return module.nvim_prog
end
end
-- Executes an ex-command. VimL errors manifest as client (lua) errors, but
-- v:errmsg will not be updated.
-local function nvim_command(cmd)
- request('nvim_command', cmd)
+function module.command(cmd)
+ module.request('nvim_command', cmd)
end
-- Evaluates a VimL expression.
-- Fails on VimL error, but does not update v:errmsg.
-local function nvim_eval(expr)
- return request('nvim_eval', expr)
+function module.eval(expr)
+ return module.request('nvim_eval', expr)
end
-local os_name = (function()
+module.os_name = (function()
local name = nil
return (function()
if not name then
- if nvim_eval('has("win32")') == 1 then
+ if module.eval('has("win32")') == 1 then
name = 'windows'
- elseif nvim_eval('has("macunix")') == 1 then
+ elseif module.eval('has("macunix")') == 1 then
name = 'osx'
else
name = 'unix'
@@ -278,38 +287,38 @@ local os_name = (function()
end)
end)()
-local function iswin()
+function module.iswin()
return package.config:sub(1,1) == '\\'
end
-- Executes a VimL function.
-- Fails on VimL error, but does not update v:errmsg.
-local function nvim_call(name, ...)
- return request('nvim_call_function', name, {...})
+function module.call(name, ...)
+ return module.request('nvim_call_function', name, {...})
end
-- Sends user input to Nvim.
-- Does not fail on VimL error, but v:errmsg will be updated.
local function nvim_feed(input)
while #input > 0 do
- local written = request('nvim_input', input)
+ local written = module.request('nvim_input', input)
input = input:sub(written + 1)
end
end
-local function feed(...)
+function module.feed(...)
for _, v in ipairs({...}) do
nvim_feed(dedent(v))
end
end
-local function rawfeed(...)
+function module.rawfeed(...)
for _, v in ipairs({...}) do
nvim_feed(dedent(v))
end
end
-local function merge_args(...)
+function module.merge_args(...)
local i = 1
local argv = {}
for anum = 1,select('#', ...) do
@@ -361,15 +370,15 @@ local function remove_args(args, args_rm)
return new_args
end
-local function spawn(argv, merge, env)
+function module.spawn(argv, merge, env)
local child_stream = ChildProcessStream.spawn(
- merge and merge_args(prepend_argv, argv) or argv,
+ merge and module.merge_args(prepend_argv, argv) or argv,
env)
return Session.new(child_stream)
end
-- Creates a new Session connected by domain socket (named pipe) or TCP.
-local function connect(file_or_address)
+function module.connect(file_or_address)
local addr, port = string.match(file_or_address, "(.*):(%d+)")
local stream = (addr and port) and TcpStream.open(addr, port) or
SocketStream.open(file_or_address)
@@ -378,7 +387,7 @@ end
-- Calls fn() until it succeeds, up to `max` times or until `max_ms`
-- milliseconds have passed.
-local function retry(max, max_ms, fn)
+function module.retry(max, max_ms, fn)
assert(max == nil or max > 0)
assert(max_ms == nil or max_ms > 0)
local tries = 1
@@ -410,8 +419,16 @@ end
-- Example:
-- clear('-e')
-- clear{args={'-e'}, args_rm={'-i'}, env={TERM=term}}
-local function clear(...)
- local args = {unpack(nvim_argv)}
+function module.clear(...)
+ local argv, env = module.new_argv(...)
+ module.set_session(module.spawn(argv, nil, env))
+end
+
+-- Builds an argument list for use in clear().
+--
+--@see clear() for parameters.
+function module.new_argv(...)
+ local args = {unpack(module.nvim_argv)}
table.insert(args, '--headless')
local new_args
local env = nil
@@ -450,21 +467,21 @@ local function clear(...)
for _, arg in ipairs(new_args) do
table.insert(args, arg)
end
- set_session(spawn(args, nil, env))
+ return args, env
end
-local function insert(...)
+function module.insert(...)
nvim_feed('i')
for _, v in ipairs({...}) do
local escaped = v:gsub('<', '<lt>')
- rawfeed(escaped)
+ module.rawfeed(escaped)
end
nvim_feed('<ESC>')
end
-- Executes an ex-command by user input. Because nvim_input() is used, VimL
-- errors will not manifest as client (lua) errors. Use command() for that.
-local function feed_command(...)
+function module.feed_command(...)
for _, v in ipairs({...}) do
if v:sub(1, 1) ~= '/' then
-- not a search command, prefix with colon
@@ -476,10 +493,10 @@ local function feed_command(...)
end
local sourced_fnames = {}
-local function source(code)
+function module.source(code)
local fname = tmpname()
write_file(fname, code)
- nvim_command('source '..fname)
+ module.command('source '..fname)
-- DO NOT REMOVE FILE HERE.
-- do_source() has a habit of checking whether files are “same” by using inode
-- and device IDs. If you run two source() calls in quick succession there is
@@ -495,76 +512,76 @@ local function source(code)
return fname
end
-local function set_shell_powershell()
- source([[
+function module.set_shell_powershell()
+ module.source([[
set shell=powershell shellquote=( shellpipe=\| shellredir=> shellxquote=
let &shellcmdflag = '-NoLogo -NoProfile -ExecutionPolicy RemoteSigned -Command Remove-Item -Force alias:sleep; Remove-Item -Force alias:cat;'
]])
end
-local function nvim(method, ...)
- return request('nvim_'..method, ...)
+function module.nvim(method, ...)
+ return module.request('nvim_'..method, ...)
end
local function ui(method, ...)
- return request('nvim_ui_'..method, ...)
+ return module.request('nvim_ui_'..method, ...)
end
-local function nvim_async(method, ...)
+function module.nvim_async(method, ...)
session:notify('nvim_'..method, ...)
end
-local function buffer(method, ...)
- return request('nvim_buf_'..method, ...)
+function module.buffer(method, ...)
+ return module.request('nvim_buf_'..method, ...)
end
-local function window(method, ...)
- return request('nvim_win_'..method, ...)
+function module.window(method, ...)
+ return module.request('nvim_win_'..method, ...)
end
-local function tabpage(method, ...)
- return request('nvim_tabpage_'..method, ...)
+function module.tabpage(method, ...)
+ return module.request('nvim_tabpage_'..method, ...)
end
-local function curbuf(method, ...)
+function module.curbuf(method, ...)
if not method then
- return nvim('get_current_buf')
+ return module.nvim('get_current_buf')
end
- return buffer(method, 0, ...)
+ return module.buffer(method, 0, ...)
end
-local function wait()
+function module.wait()
-- Execute 'nvim_eval' (a deferred function) to block
-- until all pending input is processed.
session:request('nvim_eval', '1')
end
-local function curbuf_contents()
- wait() -- Before inspecting the buffer, process all input.
- return table.concat(curbuf('get_lines', 0, -1, true), '\n')
+function module.curbuf_contents()
+ module.wait() -- Before inspecting the buffer, process all input.
+ return table.concat(module.curbuf('get_lines', 0, -1, true), '\n')
end
-local function curwin(method, ...)
+function module.curwin(method, ...)
if not method then
- return nvim('get_current_win')
+ return module.nvim('get_current_win')
end
- return window(method, 0, ...)
+ return module.window(method, 0, ...)
end
-local function curtab(method, ...)
+function module.curtab(method, ...)
if not method then
- return nvim('get_current_tabpage')
+ return module.nvim('get_current_tabpage')
end
- return tabpage(method, 0, ...)
+ return module.tabpage(method, 0, ...)
end
-local function expect(contents)
- return eq(dedent(contents), curbuf_contents())
+function module.expect(contents)
+ return eq(dedent(contents), module.curbuf_contents())
end
-local function expect_any(contents)
+function module.expect_any(contents)
contents = dedent(contents)
- return ok(nil ~= string.find(curbuf_contents(), contents, 1, true))
+ return ok(nil ~= string.find(module.curbuf_contents(), contents, 1, true))
end
local function do_rmdir(path)
@@ -584,8 +601,8 @@ local function do_rmdir(path)
else
-- Try Nvim delete(): it handles `readonly` attribute on Windows,
-- and avoids Lua cross-version/platform incompatibilities.
- if -1 == nvim_call('delete', abspath) then
- local hint = (os_name() == 'windows'
+ if -1 == module.call('delete', abspath) then
+ local hint = (module.os_name() == 'windows'
and ' (hint: try :%bwipeout! before rmdir())' or '')
error('delete() failed'..hint..': '..abspath)
end
@@ -600,12 +617,12 @@ local function do_rmdir(path)
end
end
-local function rmdir(path)
+function module.rmdir(path)
local ret, _ = pcall(do_rmdir, path)
- if not ret and os_name() == "windows" then
+ if not ret and module.os_name() == "windows" then
-- Maybe "Permission denied"; try again after changing the nvim
-- process to the top-level directory.
- nvim_command([[exe 'cd '.fnameescape(']]..start_dir.."')")
+ module.command([[exe 'cd '.fnameescape(']]..start_dir.."')")
ret, _ = pcall(do_rmdir, path)
end
-- During teardown, the nvim process may not exit quickly enough, then rmdir()
@@ -616,20 +633,20 @@ local function rmdir(path)
end
end
-local exc_exec = function(cmd)
- nvim_command(([[
+function module.exc_exec(cmd)
+ module.command(([[
try
execute "%s"
catch
let g:__exception = v:exception
endtry
]]):format(cmd:gsub('\n', '\\n'):gsub('[\\"]', '\\%0')))
- local ret = nvim_eval('get(g:, "__exception", 0)')
- nvim_command('unlet! g:__exception')
+ local ret = module.eval('get(g:, "__exception", 0)')
+ module.command('unlet! g:__exception')
return ret
end
-local function create_callindex(func)
+function module.create_callindex(func)
local table = {}
setmetatable(table, {
__index = function(tbl, arg1)
@@ -643,7 +660,7 @@ end
-- Helper to skip tests. Returns true in Windows systems.
-- pending_fn is pending() from busted
-local function pending_win32(pending_fn)
+function module.pending_win32(pending_fn)
if uname() == 'Windows' then
if pending_fn ~= nil then
pending_fn('FIXME: Windows', function() end)
@@ -656,7 +673,7 @@ end
-- Calls pending() and returns `true` if the system is too slow to
-- run fragile or expensive tests. Else returns `false`.
-local function skip_fragile(pending_fn, cond)
+function module.skip_fragile(pending_fn, cond)
if pending_fn == nil or type(pending_fn) ~= type(function()end) then
error("invalid pending_fn")
end
@@ -670,7 +687,7 @@ local function skip_fragile(pending_fn, cond)
return false
end
-local function meth_pcall(...)
+function module.meth_pcall(...)
local ret = {pcall(...)}
if type(ret[2]) == 'string' then
ret[2] = ret[2]:gsub('^[^:]+:%d+: ', '')
@@ -678,66 +695,66 @@ local function meth_pcall(...)
return ret
end
-local funcs = create_callindex(nvim_call)
-local meths = create_callindex(nvim)
-local uimeths = create_callindex(ui)
-local bufmeths = create_callindex(buffer)
-local winmeths = create_callindex(window)
-local tabmeths = create_callindex(tabpage)
-local curbufmeths = create_callindex(curbuf)
-local curwinmeths = create_callindex(curwin)
-local curtabmeths = create_callindex(curtab)
+module.funcs = module.create_callindex(module.call)
+module.meths = module.create_callindex(module.nvim)
+module.uimeths = module.create_callindex(ui)
+module.bufmeths = module.create_callindex(module.buffer)
+module.winmeths = module.create_callindex(module.window)
+module.tabmeths = module.create_callindex(module.tabpage)
+module.curbufmeths = module.create_callindex(module.curbuf)
+module.curwinmeths = module.create_callindex(module.curwin)
+module.curtabmeths = module.create_callindex(module.curtab)
-local function exec_lua(code, ...)
- return meths.execute_lua(code, {...})
+function module.exec_lua(code, ...)
+ return module.meths.execute_lua(code, {...})
end
-local function redir_exec(cmd)
- meths.set_var('__redir_exec_cmd', cmd)
- nvim_command([[
+function module.redir_exec(cmd)
+ module.meths.set_var('__redir_exec_cmd', cmd)
+ module.command([[
redir => g:__redir_exec_output
silent! execute g:__redir_exec_cmd
redir END
]])
- local ret = meths.get_var('__redir_exec_output')
- meths.del_var('__redir_exec_output')
- meths.del_var('__redir_exec_cmd')
+ local ret = module.meths.get_var('__redir_exec_output')
+ module.meths.del_var('__redir_exec_output')
+ module.meths.del_var('__redir_exec_cmd')
return ret
end
-local function get_pathsep()
- return iswin() and '\\' or '/'
+function module.get_pathsep()
+ return module.iswin() and '\\' or '/'
end
-local function pathroot()
+function module.pathroot()
local pathsep = package.config:sub(1,1)
- return iswin() and (nvim_dir:sub(1,2)..pathsep) or '/'
+ return module.iswin() and (module.nvim_dir:sub(1,2)..pathsep) or '/'
end
-- Returns a valid, platform-independent $NVIM_LISTEN_ADDRESS.
-- Useful for communicating with child instances.
-local function new_pipename()
+function module.new_pipename()
-- HACK: Start a server temporarily, get the name, then stop it.
- local pipename = nvim_eval('serverstart()')
- funcs.serverstop(pipename)
+ local pipename = module.eval('serverstart()')
+ module.funcs.serverstop(pipename)
return pipename
end
-local function missing_provider(provider)
+function module.missing_provider(provider)
if provider == 'ruby' or provider == 'node' then
- local prog = funcs['provider#' .. provider .. '#Detect']()
+ local prog = module.funcs['provider#' .. provider .. '#Detect']()
return prog == '' and (provider .. ' not detected') or false
elseif provider == 'python' or provider == 'python3' then
local py_major_version = (provider == 'python3' and 3 or 2)
- local errors = funcs['provider#pythonx#Detect'](py_major_version)[2]
+ local errors = module.funcs['provider#pythonx#Detect'](py_major_version)[2]
return errors ~= '' and errors or false
else
assert(false, 'Unknown provider: ' .. provider)
end
end
-local function alter_slashes(obj)
- if not iswin() then
+function module.alter_slashes(obj)
+ if not module.iswin() then
return obj
end
if type(obj) == 'string' then
@@ -746,7 +763,7 @@ local function alter_slashes(obj)
elseif type(obj) == 'table' then
local ret = {}
for k, v in pairs(obj) do
- ret[k] = alter_slashes(v)
+ ret[k] = module.alter_slashes(v)
end
return ret
else
@@ -754,23 +771,23 @@ local function alter_slashes(obj)
end
end
-
-local load_factor = nil
-local function load_adjust(num)
- if load_factor == nil then -- Compute load factor only once.
- clear()
- request('nvim_command', 'source src/nvim/testdir/load.vim')
- load_factor = request('nvim_eval', 'g:test_load_factor')
- end
+local load_factor = 1
+if global_helpers.isCI() then
+ -- Compute load factor only once (but outside of any tests).
+ module.clear()
+ module.request('nvim_command', 'source src/nvim/testdir/load.vim')
+ load_factor = module.request('nvim_eval', 'g:test_load_factor')
+end
+function module.load_adjust(num)
return math.ceil(num * load_factor)
end
-local function parse_context(ctx)
+function module.parse_context(ctx)
local parsed = {}
for _, item in ipairs({'regs', 'jumps', 'buflist', 'gvars'}) do
parsed[item] = filter(function(v)
return type(v) == 'table'
- end, nvim_call('msgpackparse', ctx[item]))
+ end, module.call('msgpackparse', ctx[item]))
end
parsed['buflist'] = parsed['buflist'][1]
return map(function(v)
@@ -781,78 +798,6 @@ local function parse_context(ctx)
end, parsed)
end
-local module = {
- NIL = mpack.NIL,
- alter_slashes = alter_slashes,
- buffer = buffer,
- bufmeths = bufmeths,
- call = nvim_call,
- create_callindex = create_callindex,
- clear = clear,
- command = nvim_command,
- connect = connect,
- curbuf = curbuf,
- curbuf_contents = curbuf_contents,
- curbufmeths = curbufmeths,
- curtab = curtab,
- curtabmeths = curtabmeths,
- curwin = curwin,
- curwinmeths = curwinmeths,
- eval = nvim_eval,
- exc_exec = exc_exec,
- exec_lua = exec_lua,
- expect = expect,
- expect_any = expect_any,
- expect_msg_seq = expect_msg_seq,
- expect_twostreams = expect_twostreams,
- feed = feed,
- feed_command = feed_command,
- funcs = funcs,
- get_pathsep = get_pathsep,
- get_session = get_session,
- insert = insert,
- iswin = iswin,
- merge_args = merge_args,
- meth_pcall = meth_pcall,
- meths = meths,
- missing_provider = missing_provider,
- mkdir = lfs.mkdir,
- load_adjust = load_adjust,
- new_pipename = new_pipename,
- next_msg = next_msg,
- nvim = nvim,
- nvim_argv = nvim_argv,
- nvim_async = nvim_async,
- nvim_dir = nvim_dir,
- nvim_prog = nvim_prog,
- nvim_prog_abs = nvim_prog_abs,
- nvim_set = nvim_set,
- os_name = os_name,
- parse_context = parse_context,
- pathroot = pathroot,
- pending_win32 = pending_win32,
- prepend_argv = prepend_argv,
- rawfeed = rawfeed,
- redir_exec = redir_exec,
- request = request,
- retry = retry,
- rmdir = rmdir,
- run = run,
- run_session = run_session,
- set_session = set_session,
- set_method_error = set_method_error,
- set_shell_powershell = set_shell_powershell,
- skip_fragile = skip_fragile,
- source = source,
- spawn = spawn,
- stop = stop,
- tabmeths = tabmeths,
- tabpage = tabpage,
- uimeths = uimeths,
- wait = wait,
- window = window,
- winmeths = winmeths,
-}
module = global_helpers.tbl_extend('error', module, global_helpers)
return function(after_each)
diff --git a/test/functional/lua/buffer_updates_spec.lua b/test/functional/lua/buffer_updates_spec.lua
index c419d89be3..990cb97fec 100644
--- a/test/functional/lua/buffer_updates_spec.lua
+++ b/test/functional/lua/buffer_updates_spec.lua
@@ -5,28 +5,31 @@ local command = helpers.command
local meths = helpers.meths
local clear = helpers.clear
local eq = helpers.eq
+local exec_lua = helpers.exec_lua
+local feed = helpers.feed
local origlines = {"original line 1",
"original line 2",
"original line 3",
"original line 4",
"original line 5",
- "original line 6"}
+ "original line 6",
+ " indented line"}
describe('lua: buffer event callbacks', function()
before_each(function()
clear()
- meths.execute_lua([[
+ exec_lua([[
local events = {}
- function test_register(bufnr, id, changedtick)
+ function test_register(bufnr, id, changedtick, utf_sizes)
local function callback(...)
table.insert(events, {id, ...})
if test_unreg == id then
return true
end
end
- local opts = {on_lines=callback, on_detach=callback}
+ local opts = {on_lines=callback, on_detach=callback, utf_sizes=utf_sizes}
if changedtick then
opts.on_changedtick = callback
end
@@ -38,55 +41,166 @@ describe('lua: buffer event callbacks', function()
events = {}
return ret_events
end
- ]], {})
+ ]])
end)
- it('works', function()
+
+ -- verifying the sizes with nvim_buf_get_offset is nice (checks we cannot
+ -- assert the wrong thing), but masks errors with unflushed lines (as
+ -- nvim_buf_get_offset forces a flush of the memline). To be safe run the
+ -- test both ways.
+ local function check(verify,utf_sizes)
+ local lastsize
meths.buf_set_lines(0, 0, -1, true, origlines)
- meths.execute_lua("return test_register(...)", {0, "test1"})
+ if verify then
+ lastsize = meths.buf_get_offset(0, meths.buf_line_count(0))
+ end
+ exec_lua("return test_register(...)", 0, "test1",false,utf_sizes)
local tick = meths.buf_get_changedtick(0)
+ local verify_name = "test1"
+ local function check_events(expected)
+ local events = exec_lua("return get_events(...)" )
+ if utf_sizes then
+ -- this test case uses ASCII only, so sizes sshould be the same.
+ -- Unicode is tested below.
+ for _, event in ipairs(expected) do
+ event[9] = event[8]
+ event[10] = event[8]
+ end
+ end
+ eq(expected, events)
+ if verify then
+ for _, event in ipairs(events) do
+ if event[1] == verify_name and event[2] == "lines" then
+ local startline, endline = event[5], event[7]
+ local newrange = meths.buf_get_offset(0, endline) - meths.buf_get_offset(0, startline)
+ local newsize = meths.buf_get_offset(0, meths.buf_line_count(0))
+ local oldrange = newrange + lastsize - newsize
+ eq(oldrange, event[8])
+ lastsize = newsize
+ end
+ end
+ end
+ end
+
+ command('set autoindent')
command('normal! GyyggP')
tick = tick + 1
- eq({{ "test1", "lines", 1, tick, 0, 0, 1 }},
- meths.execute_lua("return get_events(...)", {}))
+ check_events({{ "test1", "lines", 1, tick, 0, 0, 1, 0}})
meths.buf_set_lines(0, 3, 5, true, {"changed line"})
tick = tick + 1
- eq({{ "test1", "lines", 1, tick, 3, 5, 4 }},
- meths.execute_lua("return get_events(...)", {}))
+ check_events({{ "test1", "lines", 1, tick, 3, 5, 4, 32 }})
- meths.execute_lua("return test_register(...)", {0, "test2", true})
+ exec_lua("return test_register(...)", 0, "test2", true, utf_sizes)
tick = tick + 1
command('undo')
-- plugins can opt in to receive changedtick events, or choose
-- to only recieve actual changes.
- eq({{ "test1", "lines", 1, tick, 3, 4, 5 },
- { "test2", "lines", 1, tick, 3, 4, 5 },
- { "test2", "changedtick", 1, tick+1 } },
- meths.execute_lua("return get_events(...)", {}))
+ check_events({{ "test1", "lines", 1, tick, 3, 4, 5, 13 },
+ { "test2", "lines", 1, tick, 3, 4, 5, 13 },
+ { "test2", "changedtick", 1, tick+1 } })
tick = tick + 1
-- simulate next callback returning true
- meths.execute_lua("test_unreg = 'test1'", {})
+ exec_lua("test_unreg = 'test1'")
meths.buf_set_lines(0, 6, 7, true, {"x1","x2","x3"})
tick = tick + 1
-- plugins can opt in to receive changedtick events, or choose
-- to only recieve actual changes.
- eq({{ "test1", "lines", 1, tick, 6, 7, 9 },
- { "test2", "lines", 1, tick, 6, 7, 9 }},
- meths.execute_lua("return get_events(...)", {}))
+ check_events({{ "test1", "lines", 1, tick, 6, 7, 9, 16 },
+ { "test2", "lines", 1, tick, 6, 7, 9, 16 }})
+
+ verify_name = "test2"
meths.buf_set_lines(0, 1, 1, true, {"added"})
tick = tick + 1
- eq({{ "test2", "lines", 1, tick, 1, 1, 2 }},
- meths.execute_lua("return get_events(...)", {}))
+ check_events({{ "test2", "lines", 1, tick, 1, 1, 2, 0 }})
+
+ feed('wix')
+ tick = tick + 1
+ check_events({{ "test2", "lines", 1, tick, 4, 5, 5, 16 }})
+
+ -- check hot path for multiple insert
+ feed('yz')
+ tick = tick + 1
+ check_events({{ "test2", "lines", 1, tick, 4, 5, 5, 17 }})
+
+ feed('<bs>')
+ tick = tick + 1
+ check_events({{ "test2", "lines", 1, tick, 4, 5, 5, 19 }})
+
+ feed('<esc>Go')
+ tick = tick + 1
+ check_events({{ "test2", "lines", 1, tick, 11, 11, 12, 0 }})
+
+ feed('x')
+ tick = tick + 1
+ check_events({{ "test2", "lines", 1, tick, 11, 12, 12, 5 }})
command('bwipe!')
- eq({{ "test2", "detach", 1 }},
- meths.execute_lua("return get_events(...)", {}))
+ check_events({{ "test2", "detach", 1 }})
+ end
+
+ it('works', function()
+ check(false)
end)
+
+ it('works with verify', function()
+ check(true)
+ end)
+
+ it('works with utf_sizes and ASCII text', function()
+ check(false,true)
+ end)
+
+ it('works with utf_sizes and unicode text', function()
+ local unicode_text = {"ascii text",
+ "latin text åäö",
+ "BMP text ɧ αλφά",
+ "BMP text 汉语 ↥↧",
+ "SMP 🤦 🦄🦃",
+ "combining å بِيَّة"}
+ meths.buf_set_lines(0, 0, -1, true, unicode_text)
+ feed('gg')
+ exec_lua("return test_register(...)", 0, "test1", false, true)
+ local tick = meths.buf_get_changedtick(0)
+
+ feed('dd')
+ tick = tick + 1
+ eq({{ "test1", "lines", 1, tick, 0, 1, 0, 11, 11, 11 }}, exec_lua("return get_events(...)" ))
+
+ feed('A<bs>')
+ tick = tick + 1
+ eq({{ "test1", "lines", 1, tick, 0, 1, 1, 18, 15, 15 }}, exec_lua("return get_events(...)" ))
+
+ feed('<esc>jylp')
+ tick = tick + 1
+ eq({{ "test1", "lines", 1, tick, 1, 2, 2, 21, 16, 16 }}, exec_lua("return get_events(...)" ))
+
+ feed('+eea<cr>')
+ tick = tick + 1
+ eq({{ "test1", "lines", 1, tick, 2, 3, 4, 23, 15, 15 }}, exec_lua("return get_events(...)" ))
+
+ feed('<esc>jdw')
+ tick = tick + 1
+ -- non-BMP chars count as 2 UTF-2 codeunits
+ eq({{ "test1", "lines", 1, tick, 4, 5, 5, 18, 9, 12 }}, exec_lua("return get_events(...)" ))
+
+ feed('+rx')
+ tick = tick + 1
+ -- count the individual codepoints of a composed character.
+ eq({{ "test1", "lines", 1, tick, 5, 6, 6, 27, 20, 20 }}, exec_lua("return get_events(...)" ))
+
+ feed('kJ')
+ tick = tick + 1
+ -- NB: this is inefficient (but not really wrong).
+ eq({{ "test1", "lines", 1, tick, 4, 5, 5, 14, 5, 8 },
+ { "test1", "lines", 1, tick+1, 5, 6, 5, 27, 20, 20 }}, exec_lua("return get_events(...)" ))
+ end)
+
end)
diff --git a/test/functional/lua/utility_functions_spec.lua b/test/functional/lua/utility_functions_spec.lua
index 780d3a1565..0d93914119 100644
--- a/test/functional/lua/utility_functions_spec.lua
+++ b/test/functional/lua/utility_functions_spec.lua
@@ -2,12 +2,12 @@
local helpers = require('test.functional.helpers')(after_each)
local funcs = helpers.funcs
-local meths = helpers.meths
local clear = helpers.clear
local eq = helpers.eq
local eval = helpers.eval
local feed = helpers.feed
local meth_pcall = helpers.meth_pcall
+local exec_lua = helpers.exec_lua
before_each(clear)
@@ -110,28 +110,53 @@ describe('lua function', function()
eq(1, funcs.luaeval('vim.stricmp("\\0C\\0", "\\0B\\0")'))
end)
+ it("vim.str_utfindex/str_byteindex", function()
+ exec_lua([[_G.test_text = "xy åäö ɧ 汉语 ↥ 🤦x🦄 å بِيَّ"]])
+ local indicies32 = {[0]=0,1,2,3,5,7,9,10,12,13,16,19,20,23,24,28,29,33,34,35,37,38,40,42,44,46,48}
+ local indicies16 = {[0]=0,1,2,3,5,7,9,10,12,13,16,19,20,23,24,28,28,29,33,33,34,35,37,38,40,42,44,46,48}
+ for i,k in pairs(indicies32) do
+ eq(k, exec_lua("return vim.str_byteindex(_G.test_text, ...)", i), i)
+ end
+ for i,k in pairs(indicies16) do
+ eq(k, exec_lua("return vim.str_byteindex(_G.test_text, ..., true)", i), i)
+ end
+ local i32, i16 = 0, 0
+ for k = 0,48 do
+ if indicies32[i32] < k then
+ i32 = i32 + 1
+ end
+ if indicies16[i16] < k then
+ i16 = i16 + 1
+ if indicies16[i16+1] == indicies16[i16] then
+ i16 = i16 + 1
+ end
+ end
+ eq({i32, i16}, exec_lua("return {vim.str_utfindex(_G.test_text, ...)}", k), k)
+ end
+ end)
+
it("vim.schedule", function()
- meths.execute_lua([[
+ exec_lua([[
test_table = {}
vim.schedule(function()
table.insert(test_table, "xx")
end)
table.insert(test_table, "yy")
- ]], {})
- eq({"yy","xx"}, meths.execute_lua("return test_table", {}))
+ ]])
+ eq({"yy","xx"}, exec_lua("return test_table"))
-- type checked args
eq({false, 'Error executing lua: vim.schedule: expected function'},
- meth_pcall(meths.execute_lua, "vim.schedule('stringly')", {}))
+ meth_pcall(exec_lua, "vim.schedule('stringly')"))
eq({false, 'Error executing lua: vim.schedule: expected function'},
- meth_pcall(meths.execute_lua, "vim.schedule()", {}))
+ meth_pcall(exec_lua, "vim.schedule()"))
- meths.execute_lua([[
+ exec_lua([[
vim.schedule(function()
error("big failure\nvery async")
end)
- ]], {})
+ ]])
feed("<cr>")
eq('Error executing vim.schedule lua callback: [string "<nvim>"]:2: big failure\nvery async', eval("v:errmsg"))
@@ -139,7 +164,7 @@ describe('lua function', function()
it("vim.split", function()
local split = function(str, sep)
- return meths.execute_lua('return vim.split(...)', {str, sep})
+ return exec_lua('return vim.split(...)', str, sep)
end
local tests = {
@@ -172,7 +197,7 @@ describe('lua function', function()
it('vim.trim', function()
local trim = function(s)
- return meths.execute_lua('return vim.trim(...)', { s })
+ return exec_lua('return vim.trim(...)', s)
end
local trims = {
@@ -194,7 +219,7 @@ describe('lua function', function()
it('vim.inspect', function()
-- just make sure it basically works, it has its own test suite
local inspect = function(t, opts)
- return meths.execute_lua('return vim.inspect(...)', { t, opts })
+ return exec_lua('return vim.inspect(...)', t, opts)
end
eq('2', inspect(2))
@@ -202,18 +227,18 @@ describe('lua function', function()
inspect({ a = { b = 1 } }, { newline = '+', indent = '' }))
-- special value vim.inspect.KEY works
- eq('{ KEY_a = "x", KEY_b = "y"}', meths.execute_lua([[
+ eq('{ KEY_a = "x", KEY_b = "y"}', exec_lua([[
return vim.inspect({a="x", b="y"}, {newline = '', process = function(item, path)
if path[#path] == vim.inspect.KEY then
return 'KEY_'..item
end
return item
end})
- ]], {}))
+ ]]))
end)
it("vim.deepcopy", function()
- local is_dc = meths.execute_lua([[
+ local is_dc = exec_lua([[
local a = { x = { 1, 2 }, y = 5}
local b = vim.deepcopy(a)
@@ -222,7 +247,7 @@ describe('lua function', function()
return b.x[1] == 1 and b.x[2] == 2 and b.y == 5 and count == 2
and tostring(a) ~= tostring(b)
- ]], {})
+ ]])
assert(is_dc)
end)
diff --git a/test/functional/plugin/shada_spec.lua b/test/functional/plugin/shada_spec.lua
index 778dc4e219..c0104a58f7 100644
--- a/test/functional/plugin/shada_spec.lua
+++ b/test/functional/plugin/shada_spec.lua
@@ -1,4 +1,3 @@
-local paths = require('test.config.paths')
local helpers = require('test.functional.helpers')(after_each)
local clear = helpers.clear
local eq, nvim_eval, nvim_command, nvim, exc_exec, funcs, nvim_feed, curbuf =
@@ -13,14 +12,8 @@ local shada_helpers = require('test.functional.shada.helpers')
local get_shada_rw = shada_helpers.get_shada_rw
local function reset(shada_file)
- -- TODO(justinmk): why is this needed?
- local rtp_value = ('\'%s/runtime\''):format(
- paths.test_source_path:gsub('\'', '\'\''))
- clear{args_rm={'-u', '-i'},
- args={'-u', 'NORC',
+ clear{ args={'-u', 'NORC',
'-i', shada_file or 'NONE',
- '--cmd', 'set laststatus&',
- '--cmd', 'let &runtimepath='..rtp_value,
}}
end
@@ -2554,6 +2547,7 @@ describe('syntax/shada.vim', function()
it('works', function()
nvim_command('syntax on')
nvim_command('setlocal syntax=shada')
+ nvim_command('set laststatus&')
curbuf('set_lines', 0, 1, true, {
'Header with timestamp ' .. epoch .. ':',
' % Key Value',
@@ -2890,4 +2884,3 @@ describe('syntax/shada.vim', function()
eq(exp, act)
end)
end)
-
diff --git a/test/functional/shada/buffers_spec.lua b/test/functional/shada/buffers_spec.lua
index a4746c2205..04c9c01d7c 100644
--- a/test/functional/shada/buffers_spec.lua
+++ b/test/functional/shada/buffers_spec.lua
@@ -1,26 +1,22 @@
--- ShaDa buffer list saving/reading support
+-- shada buffer list saving/reading support
local helpers = require('test.functional.helpers')(after_each)
local nvim_command, funcs, eq, curbufmeths =
helpers.command, helpers.funcs, helpers.eq, helpers.curbufmeths
local shada_helpers = require('test.functional.shada.helpers')
-local reset, set_additional_cmd, clear =
- shada_helpers.reset, shada_helpers.set_additional_cmd,
- shada_helpers.clear
+local reset, clear = shada_helpers.reset, shada_helpers.clear
-describe('ShaDa support code', function()
+describe('shada support code', function()
local testfilename = 'Xtestfile-functional-shada-buffers'
local testfilename_2 = 'Xtestfile-functional-shada-buffers-2'
- before_each(reset)
after_each(clear)
it('is able to dump and restore buffer list', function()
- set_additional_cmd('set shada+=%')
- reset()
+ reset('set shada+=%')
nvim_command('edit ' .. testfilename)
nvim_command('edit ' .. testfilename_2)
nvim_command('qall')
- reset()
+ reset('set shada+=%')
eq(3, funcs.bufnr('$'))
eq('', funcs.bufname(1))
eq(testfilename, funcs.bufname(2))
@@ -28,11 +24,9 @@ describe('ShaDa support code', function()
end)
it('does not restore buffer list without % in &shada', function()
- set_additional_cmd('set shada+=%')
- reset()
+ reset('set shada+=%')
nvim_command('edit ' .. testfilename)
nvim_command('edit ' .. testfilename_2)
- set_additional_cmd('')
nvim_command('qall')
reset()
eq(1, funcs.bufnr('$'))
@@ -40,61 +34,57 @@ describe('ShaDa support code', function()
end)
it('does not dump buffer list without % in &shada', function()
+ reset()
nvim_command('edit ' .. testfilename)
nvim_command('edit ' .. testfilename_2)
- set_additional_cmd('set shada+=%')
nvim_command('qall')
- reset()
+ reset('set shada+=%')
eq(1, funcs.bufnr('$'))
eq('', funcs.bufname(1))
end)
it('does not dump unlisted buffer', function()
- set_additional_cmd('set shada+=%')
- reset()
+ reset('set shada+=%')
nvim_command('edit ' .. testfilename)
nvim_command('edit ' .. testfilename_2)
curbufmeths.set_option('buflisted', false)
nvim_command('qall')
- reset()
+ reset('set shada+=%')
eq(2, funcs.bufnr('$'))
eq('', funcs.bufname(1))
eq(testfilename, funcs.bufname(2))
end)
it('does not dump quickfix buffer', function()
- set_additional_cmd('set shada+=%')
- reset()
+ reset('set shada+=%')
nvim_command('edit ' .. testfilename)
nvim_command('edit ' .. testfilename_2)
curbufmeths.set_option('buftype', 'quickfix')
nvim_command('qall')
- reset()
+ reset('set shada+=%')
eq(2, funcs.bufnr('$'))
eq('', funcs.bufname(1))
eq(testfilename, funcs.bufname(2))
end)
it('does not dump unnamed buffers', function()
- set_additional_cmd('set shada+=% hidden')
- reset()
+ reset('set shada+=% hidden')
curbufmeths.set_lines(0, 1, true, {'foo'})
nvim_command('enew')
curbufmeths.set_lines(0, 1, true, {'bar'})
eq(2, funcs.bufnr('$'))
nvim_command('qall!')
- reset()
+ reset('set shada+=% hidden')
eq(1, funcs.bufnr('$'))
eq('', funcs.bufname(1))
end)
it('restores 1 buffer with %1 in &shada, #5759', function()
- set_additional_cmd('set shada+=%1')
- reset()
+ reset('set shada+=%1')
nvim_command('edit ' .. testfilename)
nvim_command('edit ' .. testfilename_2)
nvim_command('qall')
- reset()
+ reset('set shada+=%1')
eq(2, funcs.bufnr('$'))
eq('', funcs.bufname(1))
eq(testfilename, funcs.bufname(2))
diff --git a/test/functional/shada/helpers.lua b/test/functional/shada/helpers.lua
index d5e061bb50..fb3ec4a87c 100644
--- a/test/functional/shada/helpers.lua
+++ b/test/functional/shada/helpers.lua
@@ -1,47 +1,39 @@
local helpers = require('test.functional.helpers')(nil)
-local spawn, set_session, meths, nvim_prog =
- helpers.spawn, helpers.set_session, helpers.meths, helpers.nvim_prog
-local write_file, merge_args = helpers.write_file, helpers.merge_args
+local meths = helpers.meths
+local write_file = helpers.write_file
+local concat_tables = helpers.concat_tables
local mpack = require('mpack')
local tmpname = helpers.tmpname()
-local append_argv = nil
-local function nvim_argv(shada_file, embed)
- if embed == nil then
- embed = true
+-- o={
+-- args=…,
+-- args_rm=…,
+-- shadafile=…,
+-- }
+local function reset(o)
+ assert(o == nil or type(o) == 'table' or type(o) == 'string')
+ o = o and o or {}
+ local args_rm = o.args_rm or {}
+ table.insert(args_rm, '-i')
+ local args={
+ '-i', o.shadafile or tmpname,
+ }
+ if type(o) == 'string' then
+ args = concat_tables(args, {'--cmd', o})
+ elseif o.args then
+ args = concat_tables(args, o.args)
end
- local argv = {nvim_prog, '-u', 'NONE', '-i', shada_file or tmpname, '-N',
- '--cmd', 'set shortmess+=I background=light noswapfile',
- '--headless', embed and '--embed' or nil}
- if helpers.prepend_argv or append_argv then
- return merge_args(helpers.prepend_argv, argv, append_argv)
- else
- return argv
- end
-end
-
-local reset = function(shada_file)
- set_session(spawn(nvim_argv(shada_file)))
+ helpers.clear{
+ args_rm=args_rm,
+ args=args,
+ }
meths.set_var('tmpname', tmpname)
end
-local set_additional_cmd = function(s)
- append_argv = {'--cmd', s}
-end
-
-local function add_argv(...)
- if select('#', ...) == 0 then
- append_argv = nil
- else
- append_argv = {...}
- end
-end
-
local clear = function()
os.remove(tmpname)
- append_argv = nil
end
local get_shada_rw = function(fname)
@@ -89,10 +81,7 @@ end
return {
reset=reset,
- set_additional_cmd=set_additional_cmd,
- add_argv=add_argv,
clear=clear,
get_shada_rw=get_shada_rw,
read_shada_file=read_shada_file,
- nvim_argv=nvim_argv,
}
diff --git a/test/functional/shada/marks_spec.lua b/test/functional/shada/marks_spec.lua
index e6450e68b3..e319fd9e6b 100644
--- a/test/functional/shada/marks_spec.lua
+++ b/test/functional/shada/marks_spec.lua
@@ -6,11 +6,7 @@ local meths, curwinmeths, curbufmeths, nvim_command, funcs, eq =
local exc_exec, redir_exec = helpers.exc_exec, helpers.redir_exec
local shada_helpers = require('test.functional.shada.helpers')
-local reset, set_additional_cmd, clear =
- shada_helpers.reset, shada_helpers.set_additional_cmd,
- shada_helpers.clear
-local add_argv = shada_helpers.add_argv
-local nvim_argv = shada_helpers.nvim_argv
+local reset, clear = shada_helpers.reset, shada_helpers.clear
local nvim_current_line = function()
return curwinmeths.get_cursor()[1]
@@ -71,8 +67,7 @@ describe('ShaDa support code', function()
nvim_command('2')
nvim_command('kB')
nvim_command('wshada')
- set_additional_cmd('set shada=\'0,f0')
- reset()
+ reset('set shada=\'0,f0')
nvim_command('language C')
nvim_command('normal! `A')
eq(testfilename, funcs.fnamemodify(curbufmeths.get_name(), ':t'))
@@ -223,17 +218,32 @@ describe('ShaDa support code', function()
-- during -c used to add item with zero lnum to jump list.
it('does not create incorrect file for non-existent buffers when writing from -c',
function()
- add_argv('--cmd', 'silent edit ' .. non_existent_testfilename, '-c', 'qall')
- local argv = nvim_argv(nil, false) -- no --embed
+ local argv = helpers.new_argv{
+ args_rm={
+ '-i',
+ '--embed', -- no --embed
+ },
+ args={
+ '-i', meths.get_var('tmpname'), -- Use same shada file as parent.
+ '--cmd', 'silent edit '..non_existent_testfilename,
+ '-c', 'qall'},
+ }
eq('', funcs.system(argv))
eq(0, exc_exec('rshada'))
end)
it('does not create incorrect file for non-existent buffers opened from -c',
function()
- add_argv('-c', 'silent edit ' .. non_existent_testfilename,
- '-c', 'autocmd VimEnter * qall')
- local argv = nvim_argv(nil, false) -- no --embed
+ local argv = helpers.new_argv{
+ args_rm={
+ '-i',
+ '--embed', -- no --embed
+ },
+ args={
+ '-i', meths.get_var('tmpname'), -- Use same shada file as parent.
+ '-c', 'silent edit '..non_existent_testfilename,
+ '-c', 'autocmd VimEnter * qall'},
+ }
eq('', funcs.system(argv))
eq(0, exc_exec('rshada'))
end)
diff --git a/test/functional/shada/registers_spec.lua b/test/functional/shada/registers_spec.lua
index 71af14aba8..1f06cbe350 100644
--- a/test/functional/shada/registers_spec.lua
+++ b/test/functional/shada/registers_spec.lua
@@ -3,9 +3,7 @@ local helpers = require('test.functional.helpers')(after_each)
local nvim_command, funcs, eq = helpers.command, helpers.funcs, helpers.eq
local shada_helpers = require('test.functional.shada.helpers')
-local reset, set_additional_cmd, clear =
- shada_helpers.reset, shada_helpers.set_additional_cmd,
- shada_helpers.clear
+local reset, clear = shada_helpers.reset, shada_helpers.clear
local setreg = function(name, contents, typ)
if type(contents) == 'string' then
@@ -52,9 +50,8 @@ describe('ShaDa support code', function()
setreg('c', {'d', 'e', ''}, 'c')
setreg('l', {'a', 'b', 'cde'}, 'l')
setreg('b', {'bca', 'abc', 'cba'}, 'b3')
- set_additional_cmd('set shada=\'0,<0')
nvim_command('qall')
- reset()
+ reset('set shada=\'0,<0')
eq({{'d', 'e', ''}, 'v'}, getreg('c'))
eq({{'a', 'b', 'cde'}, 'V'}, getreg('l'))
eq({{'bca', 'abc', 'cba'}, '\0223'}, getreg('b'))
@@ -76,9 +73,8 @@ describe('ShaDa support code', function()
setreg('c', {'d', 'e', ''}, 'c')
setreg('l', {'a', 'b', 'cde'}, 'l')
setreg('b', {'bca', 'abc', 'cba'}, 'b3')
- set_additional_cmd('set shada=\'0,\\"0')
nvim_command('qall')
- reset()
+ reset('set shada=\'0,\\"0')
eq({{'d', 'e', ''}, 'v'}, getreg('c'))
eq({{'a', 'b', 'cde'}, 'V'}, getreg('l'))
eq({{'bca', 'abc', 'cba'}, '\0223'}, getreg('b'))
@@ -142,7 +138,6 @@ describe('ShaDa support code', function()
reset()
-- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1
setreg('e', {'\171«'}, 'c')
- set_additional_cmd('')
nvim_command('qall')
reset()
eq({{'\171«'}, 'v'}, getreg('e'))
diff --git a/test/functional/shada/shada_spec.lua b/test/functional/shada/shada_spec.lua
index 5f7daf73e5..ff63aed235 100644
--- a/test/functional/shada/shada_spec.lua
+++ b/test/functional/shada/shada_spec.lua
@@ -15,7 +15,6 @@ local shada_helpers = require('test.functional.shada.helpers')
local reset, clear, get_shada_rw =
shada_helpers.reset, shada_helpers.clear, shada_helpers.get_shada_rw
local read_shada_file = shada_helpers.read_shada_file
-local set_additional_cmd = shada_helpers.set_additional_cmd
local wshada, _, shada_fname, clean =
get_shada_rw('Xtest-functional-shada-shada.shada')
@@ -244,8 +243,7 @@ describe('ShaDa support code', function()
funcs.mkdir(dirname, '', 0)
eq(0, funcs.filewritable(dirname))
- set_additional_cmd('set shada=')
- reset(dirshada)
+ reset{shadafile=dirshada, args={'--cmd', 'set shada='}}
meths.set_option('shada', '\'10')
eq('Vim(wshada):E886: System error while opening ShaDa file '
.. 'Xtest-functional-shada-shada.d/main.shada for reading to merge '
diff --git a/test/functional/shada/variables_spec.lua b/test/functional/shada/variables_spec.lua
index f817bcef74..74bbceddcc 100644
--- a/test/functional/shada/variables_spec.lua
+++ b/test/functional/shada/variables_spec.lua
@@ -4,9 +4,7 @@ local meths, funcs, nvim_command, eq, exc_exec =
helpers.meths, helpers.funcs, helpers.command, helpers.eq, helpers.exc_exec
local shada_helpers = require('test.functional.shada.helpers')
-local reset, set_additional_cmd, clear =
- shada_helpers.reset, shada_helpers.set_additional_cmd,
- shada_helpers.clear
+local reset, clear = shada_helpers.reset, shada_helpers.clear
describe('ShaDa support code', function()
before_each(reset)
@@ -25,8 +23,7 @@ describe('ShaDa support code', function()
local autotest = function(tname, varname, varval, val_is_expr)
it('is able to dump and read back ' .. tname .. ' variable automatically',
function()
- set_additional_cmd('set shada+=!')
- reset()
+ reset('set shada+=!')
if val_is_expr then
nvim_command('let g:' .. varname .. ' = ' .. varval)
varval = meths.get_var(varname)
@@ -36,7 +33,7 @@ describe('ShaDa support code', function()
-- Exit during `reset` is not a regular exit: it does not write shada
-- automatically
nvim_command('qall')
- reset()
+ reset('set shada+=!')
eq(varval, meths.get_var(varname))
end)
end
@@ -55,8 +52,7 @@ describe('ShaDa support code', function()
meths.set_var('STRVAR', 'foo')
nvim_command('set shada+=!')
nvim_command('wshada')
- set_additional_cmd('set shada-=!')
- reset()
+ reset('set shada-=!')
nvim_command('rshada')
eq(0, funcs.exists('g:STRVAR'))
end)
@@ -98,7 +94,6 @@ describe('ShaDa support code', function()
meths.set_var('LSTVAR', {'«'})
meths.set_var('DCTVAR', {['«']='«'})
meths.set_var('NESTEDVAR', {['«']={{'«'}, {['«']='«'}, {a='Test'}}})
- set_additional_cmd('')
nvim_command('qall')
reset()
eq('«', meths.get_var('STRVAR'))
@@ -131,11 +126,10 @@ describe('ShaDa support code', function()
nvim_command('let F = function("tr")')
meths.set_var('U', '10')
nvim_command('set shada+=!')
- set_additional_cmd('set shada+=!')
eq('Vim(wshada):E5004: Error while dumping variable g:F, itself: attempt to dump function reference',
exc_exec('wshada'))
meths.set_option('shada', '')
- reset()
+ reset('set shada+=!')
eq('10', meths.get_var('U'))
end)
@@ -148,8 +142,7 @@ describe('ShaDa support code', function()
eq('Vim(wshada):E5005: Unable to dump variable g:L: container references itself in index 0',
exc_exec('wshada'))
meths.set_option('shada', '')
- set_additional_cmd('set shada+=!')
- reset()
+ reset('set shada+=!')
eq('10', meths.get_var('U'))
end)
end)
diff --git a/test/functional/ui/output_spec.lua b/test/functional/ui/output_spec.lua
index 9a30ea73c4..24bf66e2d8 100644
--- a/test/functional/ui/output_spec.lua
+++ b/test/functional/ui/output_spec.lua
@@ -51,12 +51,7 @@ describe("shell command :!", function()
end)
it("throttles shell-command output greater than ~10KB", function()
- if helpers.skip_fragile(pending,
- (helpers.isCI('travis') and helpers.os_name() == 'osx')) then
- return
- end
- child_session.feed_data(
- ":!for i in $(seq 2 30000); do echo XXXXXXXXXX $i; done\n")
+ child_session.feed_data(":!"..nvim_dir.."/shell-test REP_NODELAY 30001 foo\n")
-- If we observe any line starting with a dot, then throttling occurred.
-- Avoid false failure on slow systems.
@@ -65,10 +60,10 @@ describe("shell command :!", function()
-- Final chunk of output should always be displayed, never skipped.
-- (Throttling is non-deterministic, this test is merely a sanity check.)
screen:expect([[
- XXXXXXXXXX 29997 |
- XXXXXXXXXX 29998 |
- XXXXXXXXXX 29999 |
- XXXXXXXXXX 30000 |
+ 29997: foo |
+ 29998: foo |
+ 29999: foo |
+ 30000: foo |
|
{10:Press ENTER or type command to continue}{1: } |
{3:-- TERMINAL --} |
diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua
index b5d3dd9f47..24dbc65bd0 100644
--- a/test/unit/helpers.lua
+++ b/test/unit/helpers.lua
@@ -456,8 +456,8 @@ else
if bytes_written == -1 then
local err = ffi.errno(0)
if err ~= ffi.C.kPOSIXErrnoEINTR then
- assert(false, ("write() error: %u: %s"):format(
- err, ffi.string(ffi.C.strerror(err))))
+ assert(false, ("write() error: %u: %s ('%s')"):format(
+ err, ffi.string(ffi.C.strerror(err)), s))
end
elseif bytes_written == 0 then
break