aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/cjson/lua_cjson.c20
-rw-r--r--src/mpack/lmpack.c2
-rw-r--r--src/nvim/CMakeLists.txt12
-rw-r--r--src/nvim/api/autocmd.c707
-rw-r--r--src/nvim/api/autocmd.h11
-rw-r--r--src/nvim/api/buffer.c191
-rw-r--r--src/nvim/api/deprecated.c15
-rw-r--r--src/nvim/api/extmark.c162
-rw-r--r--src/nvim/api/keysets.lua73
-rw-r--r--src/nvim/api/private/converter.c23
-rw-r--r--src/nvim/api/private/dispatch.c20
-rw-r--r--src/nvim/api/private/helpers.c371
-rw-r--r--src/nvim/api/private/helpers.h19
-rw-r--r--src/nvim/api/vim.c171
-rw-r--r--src/nvim/api/vimscript.c7
-rw-r--r--src/nvim/api/window.c16
-rw-r--r--src/nvim/aucmd.c123
-rw-r--r--src/nvim/aucmd.h11
-rw-r--r--src/nvim/auevents.lua5
-rw-r--r--src/nvim/autocmd.c1397
-rw-r--r--src/nvim/autocmd.h45
-rw-r--r--src/nvim/buffer.c160
-rw-r--r--src/nvim/buffer_defs.h13
-rw-r--r--src/nvim/change.c51
-rw-r--r--src/nvim/channel.c1
-rw-r--r--src/nvim/channel.h2
-rw-r--r--src/nvim/charset.c10
-rw-r--r--src/nvim/context.c3
-rw-r--r--src/nvim/cursor.c8
-rw-r--r--src/nvim/debugger.c4
-rw-r--r--src/nvim/decoration.c161
-rw-r--r--src/nvim/decoration.h7
-rw-r--r--src/nvim/diff.c225
-rw-r--r--src/nvim/edit.c73
-rw-r--r--src/nvim/eval.c304
-rw-r--r--src/nvim/eval.lua24
-rw-r--r--src/nvim/eval/funcs.c770
-rw-r--r--src/nvim/eval/typval.c44
-rw-r--r--src/nvim/eval/typval.h6
-rw-r--r--src/nvim/eval/userfunc.c1
-rw-r--r--src/nvim/event/loop.c2
-rw-r--r--src/nvim/ex_cmds.c107
-rw-r--r--src/nvim/ex_cmds.lua20
-rw-r--r--src/nvim/ex_cmds2.c240
-rw-r--r--src/nvim/ex_cmds_defs.h32
-rw-r--r--src/nvim/ex_docmd.c470
-rw-r--r--src/nvim/ex_docmd.h20
-rw-r--r--src/nvim/ex_eval.c8
-rw-r--r--src/nvim/ex_getln.c55
-rw-r--r--src/nvim/ex_session.c70
-rw-r--r--src/nvim/extmark.c298
-rw-r--r--src/nvim/extmark.h5
-rw-r--r--src/nvim/extmark_defs.h12
-rw-r--r--src/nvim/file_search.c39
-rw-r--r--src/nvim/fileio.c159
-rw-r--r--src/nvim/fold.c2
-rw-r--r--src/nvim/generators/gen_api_dispatch.lua4
-rw-r--r--src/nvim/generators/gen_keysets.lua16
-rw-r--r--src/nvim/getchar.c527
-rw-r--r--src/nvim/getchar.h4
-rw-r--r--src/nvim/globals.h31
-rw-r--r--src/nvim/hardcopy.c43
-rw-r--r--src/nvim/highlight.c257
-rw-r--r--src/nvim/if_cscope.c6
-rw-r--r--src/nvim/indent_c.c247
-rw-r--r--src/nvim/input.c2
-rw-r--r--src/nvim/keymap.c2
-rw-r--r--src/nvim/keymap.h7
-rw-r--r--src/nvim/lua/converter.c37
-rw-r--r--src/nvim/lua/executor.c673
-rw-r--r--src/nvim/lua/executor.h25
-rw-r--r--src/nvim/lua/spell.c2
-rw-r--r--src/nvim/lua/stdlib.c93
-rw-r--r--src/nvim/lua/treesitter.c4
-rw-r--r--src/nvim/lua/vim.lua105
-rw-r--r--src/nvim/lua/xdiff.c6
-rw-r--r--src/nvim/macros.h3
-rw-r--r--src/nvim/main.c33
-rw-r--r--src/nvim/map.c8
-rw-r--r--src/nvim/map.h11
-rw-r--r--src/nvim/mark.c2
-rw-r--r--src/nvim/marktree.c201
-rw-r--r--src/nvim/marktree.h90
-rw-r--r--src/nvim/mbyte.c57
-rw-r--r--src/nvim/memfile.c6
-rw-r--r--src/nvim/memline.c10
-rw-r--r--src/nvim/menu.c2
-rw-r--r--src/nvim/message.c31
-rw-r--r--src/nvim/move.c10
-rw-r--r--src/nvim/normal.c151
-rw-r--r--src/nvim/ops.c477
-rw-r--r--src/nvim/option.c267
-rw-r--r--src/nvim/option.h20
-rw-r--r--src/nvim/option_defs.h15
-rw-r--r--src/nvim/options.lua16
-rw-r--r--src/nvim/os/env.c7
-rw-r--r--src/nvim/os/fs.c34
-rw-r--r--src/nvim/os/input.c14
-rw-r--r--src/nvim/path.c68
-rw-r--r--src/nvim/plines.c2
-rw-r--r--src/nvim/po/ja.euc-jp.po9
-rw-r--r--src/nvim/po/ja.po9
-rw-r--r--src/nvim/popupmnu.c8
-rw-r--r--src/nvim/pos.h4
-rw-r--r--src/nvim/quickfix.c441
-rw-r--r--src/nvim/quickfix.h1
-rw-r--r--src/nvim/regexp.c37
-rw-r--r--src/nvim/regexp_nfa.c11
-rw-r--r--src/nvim/runtime.c58
-rw-r--r--src/nvim/screen.c125
-rw-r--r--src/nvim/search.c551
-rw-r--r--src/nvim/search.h3
-rw-r--r--src/nvim/shada.c4
-rw-r--r--src/nvim/sign.c8
-rw-r--r--src/nvim/spell.c10
-rw-r--r--src/nvim/spellfile.c6
-rw-r--r--src/nvim/state.c33
-rw-r--r--src/nvim/strings.c28
-rw-r--r--src/nvim/syntax.c134
-rw-r--r--src/nvim/tag.c4
-rw-r--r--src/nvim/terminal.c63
-rw-r--r--src/nvim/testdir/check.vim23
-rwxr-xr-xsrc/nvim/testdir/runnvim.sh6
-rw-r--r--src/nvim/testdir/setup.vim1
-rw-r--r--src/nvim/testdir/shared.vim9
-rw-r--r--src/nvim/testdir/test_alot.vim4
-rw-r--r--src/nvim/testdir/test_alot_utf8.vim1
-rw-r--r--src/nvim/testdir/test_assert.vim174
-rw-r--r--src/nvim/testdir/test_autochdir.vim10
-rw-r--r--src/nvim/testdir/test_autocmd.vim72
-rw-r--r--src/nvim/testdir/test_blockedit.vim48
-rw-r--r--src/nvim/testdir/test_breakindent.vim4
-rw-r--r--src/nvim/testdir/test_buffer.vim31
-rw-r--r--src/nvim/testdir/test_cd.vim9
-rw-r--r--src/nvim/testdir/test_cdo.vim205
-rw-r--r--src/nvim/testdir/test_charsearch_utf8.vim2
-rw-r--r--src/nvim/testdir/test_cindent.vim5197
-rw-r--r--src/nvim/testdir/test_cmdline.vim8
-rw-r--r--src/nvim/testdir/test_compiler.vim3
-rw-r--r--src/nvim/testdir/test_conceal.vim4
-rw-r--r--src/nvim/testdir/test_cscope.vim4
-rw-r--r--src/nvim/testdir/test_cursor_func.vim257
-rw-r--r--src/nvim/testdir/test_diffmode.vim29
-rw-r--r--src/nvim/testdir/test_digraph.vim4
-rw-r--r--src/nvim/testdir/test_display.vim2
-rw-r--r--src/nvim/testdir/test_edit.vim37
-rw-r--r--src/nvim/testdir/test_eval_stuff.vim8
-rw-r--r--src/nvim/testdir/test_ex_mode.vim15
-rw-r--r--src/nvim/testdir/test_excmd.vim68
-rw-r--r--src/nvim/testdir/test_exec_while_if.vim18
-rw-r--r--src/nvim/testdir/test_execute_func.vim27
-rw-r--r--src/nvim/testdir/test_expand.vim83
-rw-r--r--src/nvim/testdir/test_expr.vim65
-rw-r--r--src/nvim/testdir/test_feedkeys.vim12
-rw-r--r--src/nvim/testdir/test_filechanged.vim111
-rw-r--r--src/nvim/testdir/test_filetype.vim567
-rw-r--r--src/nvim/testdir/test_filetype_lua.vim2
-rw-r--r--src/nvim/testdir/test_filter_cmd.vim35
-rw-r--r--src/nvim/testdir/test_filter_map.vim10
-rw-r--r--src/nvim/testdir/test_findfile.vim22
-rw-r--r--src/nvim/testdir/test_fnamemodify.vim19
-rw-r--r--src/nvim/testdir/test_functions.vim381
-rw-r--r--src/nvim/testdir/test_gf.vim12
-rw-r--r--src/nvim/testdir/test_global.vim30
-rw-r--r--src/nvim/testdir/test_help.vim22
-rw-r--r--src/nvim/testdir/test_help_tagjump.vim43
-rw-r--r--src/nvim/testdir/test_highlight.vim25
-rw-r--r--src/nvim/testdir/test_ins_complete.vim75
-rw-r--r--src/nvim/testdir/test_lambda.vim163
-rw-r--r--src/nvim/testdir/test_listchars.vim32
-rw-r--r--src/nvim/testdir/test_listdict.vim53
-rw-r--r--src/nvim/testdir/test_listlbr_utf8.vim10
-rw-r--r--src/nvim/testdir/test_mapping.vim78
-rw-r--r--src/nvim/testdir/test_marks.vim35
-rw-r--r--src/nvim/testdir/test_matchadd_conceal.vim4
-rw-r--r--src/nvim/testdir/test_matchadd_conceal_utf8.vim10
-rw-r--r--src/nvim/testdir/test_matchfuzzy.vim248
-rw-r--r--src/nvim/testdir/test_messages.vim4
-rw-r--r--src/nvim/testdir/test_mksession.vim55
-rw-r--r--src/nvim/testdir/test_normal.vim49
-rw-r--r--src/nvim/testdir/test_number.vim4
-rw-r--r--src/nvim/testdir/test_options.vim77
-rw-r--r--src/nvim/testdir/test_packadd.vim361
-rw-r--r--src/nvim/testdir/test_profile.vim31
-rw-r--r--src/nvim/testdir/test_put.vim101
-rw-r--r--src/nvim/testdir/test_python3.vim1
-rw-r--r--src/nvim/testdir/test_pyx2.vim2
-rw-r--r--src/nvim/testdir/test_quickfix.vim76
-rw-r--r--src/nvim/testdir/test_random.vim51
-rw-r--r--src/nvim/testdir/test_regexp_latin.vim8
-rw-r--r--src/nvim/testdir/test_regexp_utf8.vim13
-rw-r--r--src/nvim/testdir/test_registers.vim318
-rw-r--r--src/nvim/testdir/test_rename.vim4
-rw-r--r--src/nvim/testdir/test_retab.vim24
-rw-r--r--src/nvim/testdir/test_scriptnames.vim6
-rw-r--r--src/nvim/testdir/test_search.vim103
-rw-r--r--src/nvim/testdir/test_search_stat.vim68
-rw-r--r--src/nvim/testdir/test_selectmode.vim57
-rw-r--r--src/nvim/testdir/test_source_utf8.vim27
-rw-r--r--src/nvim/testdir/test_spell.vim8
-rw-r--r--src/nvim/testdir/test_stat.vim42
-rw-r--r--src/nvim/testdir/test_statusline.vim24
-rw-r--r--src/nvim/testdir/test_substitute.vim57
-rw-r--r--src/nvim/testdir/test_suspend.vim4
-rw-r--r--src/nvim/testdir/test_swap.vim20
-rw-r--r--src/nvim/testdir/test_syntax.vim50
-rw-r--r--src/nvim/testdir/test_system.vim30
-rw-r--r--src/nvim/testdir/test_tabline.vim11
-rw-r--r--src/nvim/testdir/test_tagjump.vim178
-rw-r--r--src/nvim/testdir/test_textformat.vim139
-rw-r--r--src/nvim/testdir/test_timers.vim23
-rw-r--r--src/nvim/testdir/test_usercommands.vim28
-rw-r--r--src/nvim/testdir/test_utf8.vim54
-rw-r--r--src/nvim/testdir/test_utf8_comparisons.vim11
-rw-r--r--src/nvim/testdir/test_vartabs.vim12
-rw-r--r--src/nvim/testdir/test_vimscript.vim103
-rw-r--r--src/nvim/testdir/test_virtualedit.vim262
-rw-r--r--src/nvim/testdir/test_visual.vim399
-rw-r--r--src/nvim/testdir/test_winbuf_close.vim19
-rw-r--r--src/nvim/testdir/test_window_cmd.vim124
-rw-r--r--src/nvim/testdir/test_writefile.vim154
-rw-r--r--src/nvim/tui/input.c7
-rw-r--r--src/nvim/tui/tui.c2
-rw-r--r--src/nvim/types.h2
-rw-r--r--src/nvim/ui.c2
-rw-r--r--src/nvim/undo.c4
-rw-r--r--src/nvim/version.c2
-rw-r--r--src/nvim/vim.h1
-rw-r--r--src/nvim/viml/parser/expressions.c6
-rw-r--r--src/nvim/viml/parser/expressions.h2
-rw-r--r--src/nvim/window.c158
-rw-r--r--src/nvim/window.h33
232 files changed, 19398 insertions, 4420 deletions
diff --git a/src/cjson/lua_cjson.c b/src/cjson/lua_cjson.c
index cf9e82c38e..b5f97bc485 100644
--- a/src/cjson/lua_cjson.c
+++ b/src/cjson/lua_cjson.c
@@ -776,7 +776,7 @@ static void json_append_data(lua_State *l, json_config_t *cfg,
if (has_metatable) {
- nlua_pushref(l, nlua_empty_dict_ref);
+ nlua_pushref(l, nlua_get_empty_dict_ref(l));
if (lua_rawequal(l, -2, -1)) {
as_empty_dict = true;
} else {
@@ -822,7 +822,7 @@ static void json_append_data(lua_State *l, json_config_t *cfg,
}
break;
case LUA_TUSERDATA:
- nlua_pushref(l, nlua_nil_ref);
+ nlua_pushref(l, nlua_get_nil_ref(l));
bool is_nil = lua_rawequal(l, -2, -1);
lua_pop(l, 1);
if (is_nil) {
@@ -1285,7 +1285,7 @@ static void json_parse_object_context(lua_State *l, json_parse_t *json)
/* Handle empty objects */
if (token.type == T_OBJ_END) {
- nlua_pushref(l, nlua_empty_dict_ref); \
+ nlua_pushref(l, nlua_get_empty_dict_ref(l)); \
lua_setmetatable(l, -2); \
json_decode_ascend(json);
return;
@@ -1392,7 +1392,7 @@ static void json_process_value(lua_State *l, json_parse_t *json,
if (use_luanil) {
lua_pushnil(l);
} else {
- nlua_pushref(l, nlua_nil_ref);
+ nlua_pushref(l, nlua_get_nil_ref(l));
}
break;;
default:
@@ -1549,7 +1549,15 @@ int lua_cjson_new(lua_State *l)
};
/* Initialise number conversions */
- fpconv_init();
+ lua_getfield(l, LUA_REGISTRYINDEX, "nvim.thread");
+ bool is_thread = lua_toboolean(l, -1);
+ lua_pop(l, 1);
+
+ // Since fpconv_init does not need to be called multiple times and is not
+ // thread safe, it should only be called in the main thread.
+ if (!is_thread) {
+ fpconv_init();
+ }
/* Test if array metatables are in registry */
lua_pushlightuserdata(l, json_lightudata_mask(&json_empty_array));
@@ -1582,7 +1590,7 @@ int lua_cjson_new(lua_State *l)
compat_luaL_setfuncs(l, reg, 1);
/* Set cjson.null */
- nlua_pushref(l, nlua_nil_ref);
+ nlua_pushref(l, nlua_get_nil_ref(l));
lua_setfield(l, -2, "null");
/* Set cjson.empty_array_mt */
diff --git a/src/mpack/lmpack.c b/src/mpack/lmpack.c
index 126f2f3824..53d7092a0c 100644
--- a/src/mpack/lmpack.c
+++ b/src/mpack/lmpack.c
@@ -246,7 +246,7 @@ static mpack_uint32_t lmpack_objlen(lua_State *L, int *is_array)
}
end:
- if ((size_t)-1 > (mpack_uint32_t)-1 && len > (mpack_uint32_t)-1)
+ if ((size_t)-1 > (mpack_uint32_t)-1 && len > (mpack_uint32_t)-1) // -V560
/* msgpack spec doesn't allow lengths > 32 bits */
len = (mpack_uint32_t)-1;
assert(top == lua_gettop(L));
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt
index 9c4b778169..a6505feb6b 100644
--- a/src/nvim/CMakeLists.txt
+++ b/src/nvim/CMakeLists.txt
@@ -62,6 +62,9 @@ set(LUA_SHARED_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/shared.lua)
set(LUA_INSPECT_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/inspect.lua)
set(LUA_F_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/F.lua)
set(LUA_META_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/_meta.lua)
+set(LUA_FILETYPE_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/filetype.lua)
+set(LUA_LOAD_PACKAGE_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/_load_package.lua)
+set(LUA_KEYMAP_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/keymap.lua)
set(CHAR_BLOB_GENERATOR ${GENERATOR_DIR}/gen_char_blob.lua)
set(LINT_SUPPRESS_FILE ${PROJECT_BINARY_DIR}/errors.json)
set(LINT_SUPPRESS_URL_BASE "https://raw.githubusercontent.com/neovim/doc/gh-pages/reports/clint")
@@ -334,6 +337,9 @@ add_custom_command(
${LUA_INSPECT_MODULE_SOURCE} inspect_module
${LUA_F_MODULE_SOURCE} lua_F_module
${LUA_META_MODULE_SOURCE} lua_meta_module
+ ${LUA_FILETYPE_MODULE_SOURCE} lua_filetype_module
+ ${LUA_LOAD_PACKAGE_MODULE_SOURCE} lua_load_package_module
+ ${LUA_KEYMAP_MODULE_SOURCE} lua_keymap_module
DEPENDS
${CHAR_BLOB_GENERATOR}
${LUA_VIM_MODULE_SOURCE}
@@ -341,6 +347,9 @@ add_custom_command(
${LUA_INSPECT_MODULE_SOURCE}
${LUA_F_MODULE_SOURCE}
${LUA_META_MODULE_SOURCE}
+ ${LUA_FILETYPE_MODULE_SOURCE}
+ ${LUA_LOAD_PACKAGE_MODULE_SOURCE}
+ ${LUA_KEYMAP_MODULE_SOURCE}
VERBATIM
)
@@ -490,6 +499,9 @@ add_executable(nvim ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS}
${EXTERNAL_SOURCES} ${EXTERNAL_HEADERS})
target_link_libraries(nvim ${NVIM_EXEC_LINK_LIBRARIES})
install_helper(TARGETS nvim)
+if(MSVC)
+ install(FILES $<TARGET_PDB_FILE:nvim> DESTINATION ${CMAKE_INSTALL_BINDIR} OPTIONAL)
+endif()
set_property(TARGET nvim APPEND PROPERTY
INCLUDE_DIRECTORIES ${LUA_PREFERRED_INCLUDE_DIRS})
diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c
new file mode 100644
index 0000000000..9a8eccd22c
--- /dev/null
+++ b/src/nvim/api/autocmd.c
@@ -0,0 +1,707 @@
+// 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
+
+#include <stdbool.h>
+#include <stdio.h>
+
+#include "lauxlib.h"
+#include "nvim/api/autocmd.h"
+#include "nvim/api/private/defs.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/ascii.h"
+#include "nvim/buffer.h"
+#include "nvim/eval/typval.h"
+#include "nvim/fileio.h"
+#include "nvim/lua/executor.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "api/autocmd.c.generated.h"
+#endif
+
+#define AUCMD_MAX_PATTERNS 256
+
+// Copy string or array of strings into an empty array.
+// Get the event number, unless it is an error. Then goto `goto_name`.
+#define GET_ONE_EVENT(event_nr, event_str, goto_name) \
+ char_u *__next_ev; \
+ event_T event_nr = \
+ event_name2nr((char_u *)event_str.data.string.data, &__next_ev); \
+ if (event_nr >= NUM_EVENTS) { \
+ api_set_error(err, kErrorTypeValidation, "unexpected event"); \
+ goto goto_name; \
+ }
+
+
+// ID for associating autocmds created via nvim_create_autocmd
+// Used to delete autocmds from nvim_del_autocmd
+static int64_t next_autocmd_id = 1;
+
+/// Get autocmds that match the requirements passed to {opts}.
+///
+/// @param opts Optional Parameters:
+/// - event : Name or list of name of events to match against
+/// - group (string): Name of group to match against
+/// - pattern: Pattern or list of patterns to match against
+///
+/// @return A list of autocmds that match
+Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
+ FUNC_API_SINCE(9)
+{
+ // TODO(tjdevries): Would be cool to add nvim_get_autocmds({ id = ... })
+
+ Array autocmd_list = ARRAY_DICT_INIT;
+ char_u *pattern_filters[AUCMD_MAX_PATTERNS];
+ char_u pattern_buflocal[BUFLOCAL_PAT_LEN];
+
+ bool event_set[NUM_EVENTS] = { false };
+ bool check_event = false;
+
+ int group = 0;
+
+ if (opts->group.type != kObjectTypeNil) {
+ Object v = opts->group;
+ if (v.type != kObjectTypeString) {
+ api_set_error(err, kErrorTypeValidation, "group must be a string.");
+ goto cleanup;
+ }
+
+ group = augroup_find(v.data.string.data);
+
+ if (group < 0) {
+ api_set_error(err, kErrorTypeValidation, "invalid augroup passed.");
+ goto cleanup;
+ }
+ }
+
+ if (opts->event.type != kObjectTypeNil) {
+ check_event = true;
+
+ Object v = opts->event;
+ if (v.type == kObjectTypeString) {
+ GET_ONE_EVENT(event_nr, v, cleanup);
+ event_set[event_nr] = true;
+ } else if (v.type == kObjectTypeArray) {
+ FOREACH_ITEM(v.data.array, event_v, {
+ if (event_v.type != kObjectTypeString) {
+ api_set_error(err,
+ kErrorTypeValidation,
+ "Every event must be a string in 'event'");
+ goto cleanup;
+ }
+
+ GET_ONE_EVENT(event_nr, event_v, cleanup);
+ event_set[event_nr] = true;
+ })
+ } else {
+ api_set_error(err,
+ kErrorTypeValidation,
+ "Not a valid 'event' value. Must be a string or an array");
+ goto cleanup;
+ }
+ }
+
+ int pattern_filter_count = 0;
+ if (opts->pattern.type != kObjectTypeNil) {
+ Object v = opts->pattern;
+ if (v.type == kObjectTypeString) {
+ pattern_filters[pattern_filter_count] = (char_u *)v.data.string.data;
+ pattern_filter_count += 1;
+ } else if (v.type == kObjectTypeArray) {
+ FOREACH_ITEM(v.data.array, item, {
+ pattern_filters[pattern_filter_count] = (char_u *)item.data.string.data;
+ pattern_filter_count += 1;
+ });
+ } else {
+ api_set_error(err,
+ kErrorTypeValidation,
+ "Not a valid 'pattern' value. Must be a string or an array");
+ goto cleanup;
+ }
+
+ if (pattern_filter_count >= AUCMD_MAX_PATTERNS) {
+ api_set_error(err,
+ kErrorTypeValidation,
+ "Too many patterns. Please limit yourself to less");
+ goto cleanup;
+ }
+ }
+
+ FOR_ALL_AUEVENTS(event) {
+ if (check_event && !event_set[event]) {
+ continue;
+ }
+
+ for (AutoPat *ap = au_get_autopat_for_event(event);
+ ap != NULL;
+ ap = ap->next) {
+ if (ap == NULL || ap->cmds == NULL) {
+ continue;
+ }
+
+ // Skip autocmds from invalid groups if passed.
+ if (group != 0 && ap->group != group) {
+ continue;
+ }
+
+ // Skip 'pattern' from invalid patterns if passed.
+ if (pattern_filter_count > 0) {
+ bool passed = false;
+ for (int i = 0; i < pattern_filter_count; i++) {
+ assert(i < AUCMD_MAX_PATTERNS);
+ assert(pattern_filters[i]);
+
+ char_u *pat = pattern_filters[i];
+ int patlen = (int)STRLEN(pat);
+
+ if (aupat_is_buflocal(pat, patlen)) {
+ aupat_normalize_buflocal_pat(pattern_buflocal,
+ pat,
+ patlen,
+ aupat_get_buflocal_nr(pat, patlen));
+
+ pat = pattern_buflocal;
+ }
+
+ if (strequal((char *)ap->pat, (char *)pat)) {
+ passed = true;
+ break;
+ }
+ }
+
+ if (!passed) {
+ continue;
+ }
+ }
+
+ for (AutoCmd *ac = ap->cmds; ac != NULL; ac = ac->next) {
+ if (aucmd_exec_is_deleted(ac->exec)) {
+ continue;
+ }
+
+ Dictionary autocmd_info = ARRAY_DICT_INIT;
+
+ if (ap->group != AUGROUP_DEFAULT) {
+ PUT(autocmd_info, "group", INTEGER_OBJ(ap->group));
+ }
+
+ if (ac->id > 0) {
+ PUT(autocmd_info, "id", INTEGER_OBJ(ac->id));
+ }
+
+ if (ac->desc != NULL) {
+ PUT(autocmd_info, "desc", CSTR_TO_OBJ(ac->desc));
+ }
+
+ PUT(autocmd_info,
+ "command",
+ STRING_OBJ(cstr_to_string(aucmd_exec_to_string(ac, ac->exec))));
+
+ PUT(autocmd_info,
+ "pattern",
+ STRING_OBJ(cstr_to_string((char *)ap->pat)));
+
+ PUT(autocmd_info,
+ "event",
+ STRING_OBJ(cstr_to_string((char *)event_nr2name(event))));
+
+ PUT(autocmd_info, "once", BOOLEAN_OBJ(ac->once));
+
+ if (ap->buflocal_nr) {
+ PUT(autocmd_info, "buflocal", BOOLEAN_OBJ(true));
+ PUT(autocmd_info, "buffer", INTEGER_OBJ(ap->buflocal_nr));
+ } else {
+ PUT(autocmd_info, "buflocal", BOOLEAN_OBJ(false));
+ }
+
+ // TODO(sctx): It would be good to unify script_ctx to actually work with lua
+ // right now it's just super weird, and never really gives you the info that
+ // you would expect from this.
+ //
+ // I think we should be able to get the line number, filename, etc. from lua
+ // when we're executing something, and it should be easy to then save that
+ // info here.
+ //
+ // I think it's a big loss not getting line numbers of where options, autocmds,
+ // etc. are set (just getting "Sourced (lua)" or something is not that helpful.
+ //
+ // Once we do that, we can put these into the autocmd_info, but I don't think it's
+ // useful to do that at this time.
+ //
+ // PUT(autocmd_info, "sid", INTEGER_OBJ(ac->script_ctx.sc_sid));
+ // PUT(autocmd_info, "lnum", INTEGER_OBJ(ac->script_ctx.sc_lnum));
+
+ ADD(autocmd_list, DICTIONARY_OBJ(autocmd_info));
+ }
+ }
+ }
+
+cleanup:
+ return autocmd_list;
+}
+
+/// Create an autocmd.
+///
+/// @param event The event or events to register this autocmd
+/// Required keys:
+/// event: string | ArrayOf(string)
+///
+/// Examples:
+/// - event: "pat1,pat2,pat3",
+/// - event: "pat1"
+/// - event: { "pat1" }
+/// - event: { "pat1", "pat2", "pat3" }
+///
+/// @param opts Optional Parameters:
+/// - callback: (string|function)
+/// - (string): The name of the viml function to execute when triggering this autocmd
+/// - (function): The lua function to execute when triggering this autocmd
+/// - NOTE: Cannot be used with {command}
+/// - command: (string) command
+/// - vimscript command
+/// - NOTE: Cannot be used with {callback}
+/// Eg. command = "let g:value_set = v:true"
+/// - pattern: (string|table)
+/// - pattern or patterns to match against
+/// - defaults to "*".
+/// - NOTE: Cannot be used with {buffer}
+/// - buffer: (bufnr)
+/// - create a |autocmd-buflocal| autocmd.
+/// - NOTE: Cannot be used with {pattern}
+/// - group: (string) The augroup name
+/// - once: (boolean) - See |autocmd-once|
+/// - nested: (boolean) - See |autocmd-nested|
+/// - desc: (string) - Description of the autocmd
+///
+/// @returns opaque value to use with nvim_del_autocmd
+Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autocmd) *opts,
+ Error *err)
+ FUNC_API_SINCE(9)
+{
+ int64_t autocmd_id = -1;
+
+ const char_u pattern_buflocal[BUFLOCAL_PAT_LEN];
+ int au_group = AUGROUP_DEFAULT;
+ char *desc = NULL;
+
+ Array patterns = ARRAY_DICT_INIT;
+ Array event_array = ARRAY_DICT_INIT;
+
+ AucmdExecutable aucmd = AUCMD_EXECUTABLE_INIT;
+ Callback cb = CALLBACK_NONE;
+
+
+ if (!unpack_string_or_array(&event_array, &event, "event", err)) {
+ goto cleanup;
+ }
+
+ if (opts->callback.type != kObjectTypeNil && opts->command.type != kObjectTypeNil) {
+ api_set_error(err, kErrorTypeValidation,
+ "cannot pass both: 'callback' and 'command' for the same autocmd");
+ goto cleanup;
+ } else if (opts->callback.type != kObjectTypeNil) {
+ // TODO(tjdevries): It's possible we could accept callable tables,
+ // but we don't do that many other places, so for the moment let's
+ // not do that.
+
+ Object *callback = &opts->callback;
+ if (callback->type == kObjectTypeLuaRef) {
+ if (callback->data.luaref == LUA_NOREF) {
+ api_set_error(err,
+ kErrorTypeValidation,
+ "must pass an actual value");
+ goto cleanup;
+ }
+
+ if (!nlua_ref_is_function(callback->data.luaref)) {
+ api_set_error(err,
+ kErrorTypeValidation,
+ "must pass a function for callback");
+ goto cleanup;
+ }
+
+ cb.type = kCallbackLua;
+ cb.data.luaref = api_new_luaref(callback->data.luaref);
+ } else if (callback->type == kObjectTypeString) {
+ cb.type = kCallbackFuncref;
+ cb.data.funcref = vim_strsave((char_u *)callback->data.string.data);
+ } else {
+ api_set_error(err,
+ kErrorTypeException,
+ "'callback' must be a lua function or name of vim function");
+ goto cleanup;
+ }
+
+ aucmd.type = CALLABLE_CB;
+ aucmd.callable.cb = cb;
+ } else if (opts->command.type != kObjectTypeNil) {
+ Object *command = &opts->command;
+ if (command->type == kObjectTypeString) {
+ aucmd.type = CALLABLE_EX;
+ aucmd.callable.cmd = vim_strsave((char_u *)command->data.string.data);
+ } else {
+ api_set_error(err,
+ kErrorTypeValidation,
+ "'command' must be a string");
+ goto cleanup;
+ }
+ } else {
+ api_set_error(err, kErrorTypeValidation, "must pass one of: 'command', 'callback'");
+ goto cleanup;
+ }
+
+ bool is_once = api_object_to_bool(opts->once, "once", false, err);
+ bool is_nested = api_object_to_bool(opts->nested, "nested", false, err);
+
+ // TODO(tjdevries): accept number for namespace instead
+ if (opts->group.type != kObjectTypeNil) {
+ Object *v = &opts->group;
+ if (v->type != kObjectTypeString) {
+ api_set_error(err, kErrorTypeValidation, "'group' must be a string");
+ goto cleanup;
+ }
+
+ au_group = augroup_find(v->data.string.data);
+
+ if (au_group == AUGROUP_ERROR) {
+ api_set_error(err,
+ kErrorTypeException,
+ "invalid augroup: %s", v->data.string.data);
+
+ goto cleanup;
+ }
+ }
+
+ if (opts->pattern.type != kObjectTypeNil && opts->buffer.type != kObjectTypeNil) {
+ api_set_error(err, kErrorTypeValidation,
+ "cannot pass both: 'pattern' and 'buffer' for the same autocmd");
+ goto cleanup;
+ } else if (opts->pattern.type != kObjectTypeNil) {
+ Object *v = &opts->pattern;
+
+ if (v->type == kObjectTypeString) {
+ char_u *pat = (char_u *)v->data.string.data;
+ size_t patlen = aucmd_pattern_length(pat);
+ while (patlen) {
+ ADD(patterns, STRING_OBJ(cbuf_to_string((char *)pat, patlen)));
+
+ pat = aucmd_next_pattern(pat, patlen);
+ patlen = aucmd_pattern_length(pat);
+ }
+ } else if (v->type == kObjectTypeArray) {
+ if (!check_autocmd_string_array(patterns, "pattern", err)) {
+ goto cleanup;
+ }
+
+ Array array = v->data.array;
+ for (size_t i = 0; i < array.size; i++) {
+ char_u *pat = (char_u *)array.items[i].data.string.data;
+ size_t patlen = aucmd_pattern_length(pat);
+ while (patlen) {
+ ADD(patterns, STRING_OBJ(cbuf_to_string((char *)pat, patlen)));
+
+ pat = aucmd_next_pattern(pat, patlen);
+ patlen = aucmd_pattern_length(pat);
+ }
+ }
+ } else {
+ api_set_error(err,
+ kErrorTypeValidation,
+ "'pattern' must be a string");
+ goto cleanup;
+ }
+ } else if (opts->buffer.type != kObjectTypeNil) {
+ if (opts->buffer.type != kObjectTypeInteger) {
+ api_set_error(err,
+ kErrorTypeValidation,
+ "'buffer' must be an integer");
+ goto cleanup;
+ }
+
+ buf_T *buf = find_buffer_by_handle((Buffer)opts->buffer.data.integer, err);
+ if (ERROR_SET(err)) {
+ goto cleanup;
+ }
+
+ snprintf((char *)pattern_buflocal, BUFLOCAL_PAT_LEN, "<buffer=%d>", (int)buf->handle);
+ ADD(patterns, STRING_OBJ(cstr_to_string((char *)pattern_buflocal)));
+ }
+
+ if (aucmd.type == CALLABLE_NONE) {
+ api_set_error(err,
+ kErrorTypeValidation,
+ "'command' or 'callback' is required");
+ goto cleanup;
+ }
+
+ if (opts->desc.type != kObjectTypeNil) {
+ if (opts->desc.type == kObjectTypeString) {
+ desc = opts->desc.data.string.data;
+ } else {
+ api_set_error(err,
+ kErrorTypeValidation,
+ "'desc' must be a string");
+ goto cleanup;
+ }
+ }
+
+ if (patterns.size == 0) {
+ ADD(patterns, STRING_OBJ(STATIC_CSTR_TO_STRING("*")));
+ }
+
+ if (event_array.size == 0) {
+ api_set_error(err, kErrorTypeValidation, "'event' is a required key");
+ goto cleanup;
+ }
+
+ autocmd_id = next_autocmd_id++;
+ FOREACH_ITEM(event_array, event_str, {
+ GET_ONE_EVENT(event_nr, event_str, cleanup);
+
+ int retval;
+
+ for (size_t i = 0; i < patterns.size; i++) {
+ Object pat = patterns.items[i];
+
+ // See: TODO(sctx)
+ WITH_SCRIPT_CONTEXT(channel_id, {
+ retval = autocmd_register(autocmd_id,
+ event_nr,
+ (char_u *)pat.data.string.data,
+ (int)pat.data.string.size,
+ au_group,
+ is_once,
+ is_nested,
+ desc,
+ aucmd);
+ });
+
+ if (retval == FAIL) {
+ api_set_error(err, kErrorTypeException, "Failed to set autocmd");
+ goto cleanup;
+ }
+ }
+ });
+
+
+cleanup:
+ aucmd_exec_free(&aucmd);
+ api_free_array(event_array);
+ api_free_array(patterns);
+
+ return autocmd_id;
+}
+
+/// Delete an autocmd by {id}. Autocmds only return IDs when created
+/// via the API. Will not error if called and no autocmds match
+/// the {id}.
+///
+/// @param id Integer The ID returned by nvim_create_autocmd
+void nvim_del_autocmd(Integer id)
+ FUNC_API_SINCE(9)
+{
+ autocmd_delete_id(id);
+}
+
+/// Create or get an augroup.
+///
+/// To get an existing augroup ID, do:
+/// <pre>
+/// local id = vim.api.nvim_create_augroup(name, {
+/// clear = false
+/// })
+/// </pre>
+///
+/// @param name String: The name of the augroup to create
+/// @param opts Parameters
+/// - clear (bool): Whether to clear existing commands or not.
+/// Defaults to true.
+/// See |autocmd-groups|
+///
+/// @returns opaque value to use with nvim_del_augroup_by_id
+Integer nvim_create_augroup(uint64_t channel_id, String name, Dict(create_augroup) *opts,
+ Error *err)
+ FUNC_API_SINCE(9)
+{
+ char *augroup_name = name.data;
+ bool clear_autocmds = api_object_to_bool(opts->clear, "clear", true, err);
+
+ int augroup = -1;
+ WITH_SCRIPT_CONTEXT(channel_id, {
+ augroup = augroup_add(augroup_name);
+ if (augroup == AUGROUP_ERROR) {
+ api_set_error(err, kErrorTypeException, "Failed to set augroup");
+ return -1;
+ }
+
+ if (clear_autocmds) {
+ FOR_ALL_AUEVENTS(event) {
+ aupat_del_for_event_and_group(event, augroup);
+ }
+ }
+ });
+
+ return augroup;
+}
+
+/// Delete an augroup by {id}. {id} can only be returned when augroup was
+/// created with |nvim_create_augroup|.
+///
+/// NOTE: behavior differs from augroup-delete.
+///
+/// When deleting an augroup, autocmds contained by this augroup will also be deleted and cleared.
+/// This augroup will no longer exist
+void nvim_del_augroup_by_id(Integer id)
+ FUNC_API_SINCE(9)
+{
+ char *name = augroup_name((int)id);
+ augroup_del(name, false);
+}
+
+/// Delete an augroup by {name}.
+///
+/// NOTE: behavior differs from augroup-delete.
+///
+/// When deleting an augroup, autocmds contained by this augroup will also be deleted and cleared.
+/// This augroup will no longer exist
+void nvim_del_augroup_by_name(String name)
+ FUNC_API_SINCE(9)
+{
+ augroup_del(name.data, false);
+}
+
+/// Do one autocmd.
+///
+/// @param event The event or events to execute
+/// @param opts Optional Parameters:
+/// - buffer (number) - buffer number
+/// - NOTE: Cannot be used with {pattern}
+/// - pattern (string|table) - optional, defaults to "*".
+/// - NOTE: Cannot be used with {buffer}
+/// - group (string) - autocmd group name
+/// - modeline (boolean) - Default true, see |<nomodeline>|
+void nvim_do_autocmd(Object event, Dict(do_autocmd) *opts, Error *err)
+ FUNC_API_SINCE(9)
+{
+ int au_group = AUGROUP_ALL;
+ bool modeline = true;
+
+ buf_T *buf = curbuf;
+ bool set_buf = false;
+
+ char_u *pattern = NULL;
+ bool set_pattern = false;
+
+ Array event_array = ARRAY_DICT_INIT;
+
+ if (!unpack_string_or_array(&event_array, &event, "event", err)) {
+ goto cleanup;
+ }
+
+ if (opts->group.type != kObjectTypeNil) {
+ if (opts->group.type != kObjectTypeString) {
+ api_set_error(err, kErrorTypeValidation, "'group' must be a string");
+ goto cleanup;
+ }
+
+ au_group = augroup_find(opts->group.data.string.data);
+
+ if (au_group == AUGROUP_ERROR) {
+ api_set_error(err,
+ kErrorTypeException,
+ "invalid augroup: %s", opts->group.data.string.data);
+
+ goto cleanup;
+ }
+ }
+
+ if (opts->buffer.type != kObjectTypeNil) {
+ Object buf_obj = opts->buffer;
+ if (buf_obj.type != kObjectTypeInteger && buf_obj.type != kObjectTypeBuffer) {
+ api_set_error(err, kErrorTypeException, "invalid buffer: %d", buf_obj.type);
+ goto cleanup;
+ }
+
+ buf = find_buffer_by_handle((Buffer)buf_obj.data.integer, err);
+ set_buf = true;
+
+ if (ERROR_SET(err)) {
+ goto cleanup;
+ }
+ }
+
+ if (opts->pattern.type != kObjectTypeNil) {
+ if (opts->pattern.type != kObjectTypeString) {
+ api_set_error(err, kErrorTypeValidation, "'pattern' must be a string");
+ goto cleanup;
+ }
+
+ pattern = vim_strsave((char_u *)opts->pattern.data.string.data);
+ set_pattern = true;
+ }
+
+ modeline = api_object_to_bool(opts->modeline, "modeline", true, err);
+
+ if (set_pattern && set_buf) {
+ api_set_error(err, kErrorTypeValidation, "must not set 'buffer' and 'pattern'");
+ goto cleanup;
+ }
+
+ bool did_aucmd = false;
+ FOREACH_ITEM(event_array, event_str, {
+ GET_ONE_EVENT(event_nr, event_str, cleanup)
+
+ did_aucmd |= apply_autocmds_group(event_nr, pattern, NULL, true, au_group, buf, NULL);
+ })
+
+ if (did_aucmd && modeline) {
+ do_modelines(0);
+ }
+
+cleanup:
+ api_free_array(event_array);
+ XFREE_CLEAR(pattern);
+}
+
+static bool check_autocmd_string_array(Array arr, char *k, Error *err)
+{
+ for (size_t i = 0; i < arr.size; i++) {
+ if (arr.items[i].type != kObjectTypeString) {
+ api_set_error(err,
+ kErrorTypeValidation,
+ "All entries in '%s' must be strings",
+ k);
+ return false;
+ }
+
+ // Disallow newlines in the middle of the line.
+ const String l = arr.items[i].data.string;
+ if (memchr(l.data, NL, l.size)) {
+ api_set_error(err, kErrorTypeValidation,
+ "String cannot contain newlines");
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool unpack_string_or_array(Array *array, Object *v, char *k, Error *err)
+{
+ if (v->type == kObjectTypeString) {
+ ADD(*array, copy_object(*v));
+ } else if (v->type == kObjectTypeArray) {
+ if (!check_autocmd_string_array(v->data.array, k, err)) {
+ return false;
+ }
+ *array = copy_array(v->data.array);
+ } else {
+ api_set_error(err,
+ kErrorTypeValidation,
+ "'%s' must be an array or a string.",
+ k);
+ return false;
+ }
+
+ return true;
+}
+
+#undef GET_ONE_EVENT
diff --git a/src/nvim/api/autocmd.h b/src/nvim/api/autocmd.h
new file mode 100644
index 0000000000..f9432830d9
--- /dev/null
+++ b/src/nvim/api/autocmd.h
@@ -0,0 +1,11 @@
+#ifndef NVIM_API_AUTOCMD_H
+#define NVIM_API_AUTOCMD_H
+
+#include <stdint.h>
+
+#include "nvim/api/private/defs.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "api/autocmd.h.generated.h"
+#endif
+#endif // NVIM_API_AUTOCMD_H
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index ce9c4e27ad..3dd647e76f 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -287,8 +287,8 @@ ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id,
}
bool oob = false;
- start = normalize_index(buf, start, &oob);
- end = normalize_index(buf, end, &oob);
+ start = normalize_index(buf, start, true, &oob);
+ end = normalize_index(buf, end, true, &oob);
if (strict_indexing && oob) {
api_set_error(err, kErrorTypeValidation, "Index out of bounds");
@@ -374,15 +374,14 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ
}
bool oob = false;
- start = normalize_index(buf, start, &oob);
- end = normalize_index(buf, end, &oob);
+ start = normalize_index(buf, start, true, &oob);
+ end = normalize_index(buf, end, true, &oob);
if (strict_indexing && oob) {
api_set_error(err, kErrorTypeValidation, "Index out of bounds");
return;
}
-
if (start > end) {
api_set_error(err,
kErrorTypeValidation,
@@ -554,13 +553,13 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
// check range is ordered and everything!
// start_row, end_row within buffer len (except add text past the end?)
- start_row = normalize_index(buf, start_row, &oob);
+ start_row = normalize_index(buf, start_row, false, &oob);
if (oob || start_row == buf->b_ml.ml_line_count + 1) {
api_set_error(err, kErrorTypeValidation, "start_row out of bounds");
return;
}
- end_row = normalize_index(buf, end_row, &oob);
+ end_row = normalize_index(buf, end_row, false, &oob);
if (oob || end_row == buf->b_ml.ml_line_count + 1) {
api_set_error(err, kErrorTypeValidation, "end_row out of bounds");
return;
@@ -757,6 +756,108 @@ end:
try_end(err);
}
+/// Gets a range from the buffer.
+///
+/// This differs from |nvim_buf_get_lines()| in that it allows retrieving only
+/// portions of a line.
+///
+/// Indexing is zero-based. Column indices are end-exclusive.
+///
+/// Prefer |nvim_buf_get_lines()| when retrieving entire lines.
+///
+/// @param channel_id
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param start_row First line index
+/// @param start_col Starting byte offset of first line
+/// @param end_row Last line index
+/// @param end_col Ending byte offset of last line (exclusive)
+/// @param opts Optional parameters. Currently unused.
+/// @param[out] err Error details, if any
+/// @return Array of lines, or empty array for unloaded buffer.
+ArrayOf(String) nvim_buf_get_text(uint64_t channel_id, Buffer buffer,
+ Integer start_row, Integer start_col,
+ Integer end_row, Integer end_col,
+ Dictionary opts, Error *err)
+ FUNC_API_SINCE(9)
+{
+ Array rv = ARRAY_DICT_INIT;
+
+ if (opts.size > 0) {
+ api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
+ return rv;
+ }
+
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+
+ if (!buf) {
+ return rv;
+ }
+
+ // return sentinel value if the buffer isn't loaded
+ if (buf->b_ml.ml_mfp == NULL) {
+ return rv;
+ }
+
+ bool oob = false;
+ start_row = normalize_index(buf, start_row, false, &oob);
+ end_row = normalize_index(buf, end_row, false, &oob);
+
+ if (oob) {
+ api_set_error(err, kErrorTypeValidation, "Index out of bounds");
+ return rv;
+ }
+
+ // nvim_buf_get_lines doesn't care if the start row is greater than the end
+ // row (it will just return an empty array), but nvim_buf_get_text does in
+ // order to maintain symmetry with nvim_buf_set_text.
+ if (start_row > end_row) {
+ api_set_error(err, kErrorTypeValidation, "start is higher than end");
+ return rv;
+ }
+
+ bool replace_nl = (channel_id != VIML_INTERNAL_CALL);
+
+ if (start_row == end_row) {
+ String line = buf_get_text(buf, start_row, start_col, end_col, replace_nl, err);
+ if (ERROR_SET(err)) {
+ return rv;
+ }
+
+ ADD(rv, STRING_OBJ(line));
+ return rv;
+ }
+
+ rv.size = (size_t)(end_row - start_row) + 1;
+ rv.items = xcalloc(rv.size, sizeof(Object));
+
+ rv.items[0] = STRING_OBJ(buf_get_text(buf, start_row, start_col, MAXCOL-1, replace_nl, err));
+ if (ERROR_SET(err)) {
+ goto end;
+ }
+
+ if (rv.size > 2) {
+ Array tmp = ARRAY_DICT_INIT;
+ tmp.items = &rv.items[1];
+ if (!buf_collect_lines(buf, rv.size - 2, start_row + 1, replace_nl, &tmp, err)) {
+ goto end;
+ }
+ }
+
+ rv.items[rv.size-1] = STRING_OBJ(buf_get_text(buf, end_row, 0, end_col, replace_nl, err));
+ if (ERROR_SET(err)) {
+ goto end;
+ }
+
+end:
+ if (ERROR_SET(err)) {
+ api_free_array(rv);
+ rv.size = 0;
+ rv.items = NULL;
+ }
+
+ return rv;
+}
+
/// Returns the byte offset of a line (0-indexed). |api-indexing|
///
/// Line 1 (index=0) has offset 0. UTF-8 bytes are counted. EOL is one byte.
@@ -835,7 +936,7 @@ Integer nvim_buf_get_changedtick(Buffer buffer, Error *err)
/// @param[out] err Error details, if any
/// @returns Array of maparg()-like dictionaries describing mappings.
/// The "buffer" key holds the associated buffer handle.
-ArrayOf(Dictionary) nvim_buf_get_keymap(Buffer buffer, String mode, Error *err)
+ArrayOf(Dictionary) nvim_buf_get_keymap(uint64_t channel_id, Buffer buffer, String mode, Error *err)
FUNC_API_SINCE(3)
{
buf_T *buf = find_buffer_by_handle(buffer, err);
@@ -844,7 +945,7 @@ ArrayOf(Dictionary) nvim_buf_get_keymap(Buffer buffer, String mode, Error *err)
return (Array)ARRAY_DICT_INIT;
}
- return keymap_array(mode, buf);
+ return keymap_array(mode, buf, channel_id == LUA_INTERNAL_CALL);
}
/// Sets a buffer-local |mapping| for the given mode.
@@ -852,11 +953,11 @@ ArrayOf(Dictionary) nvim_buf_get_keymap(Buffer buffer, String mode, Error *err)
/// @see |nvim_set_keymap()|
///
/// @param buffer Buffer handle, or 0 for current buffer
-void nvim_buf_set_keymap(Buffer buffer, String mode, String lhs, String rhs, Dict(keymap) *opts,
- Error *err)
+void nvim_buf_set_keymap(uint64_t channel_id, Buffer buffer, String mode, String lhs, String rhs,
+ Dict(keymap) *opts, Error *err)
FUNC_API_SINCE(6)
{
- modify_keymap(buffer, false, mode, lhs, rhs, opts, err);
+ modify_keymap(channel_id, buffer, false, mode, lhs, rhs, opts, err);
}
/// Unmaps a buffer-local |mapping| for the given mode.
@@ -864,11 +965,12 @@ void nvim_buf_set_keymap(Buffer buffer, String mode, String lhs, String rhs, Dic
/// @see |nvim_del_keymap()|
///
/// @param buffer Buffer handle, or 0 for current buffer
-void nvim_buf_del_keymap(Buffer buffer, String mode, String lhs, Error *err)
+void nvim_buf_del_keymap(uint64_t channel_id, Buffer buffer, String mode,
+ String lhs, Error *err)
FUNC_API_SINCE(6)
{
String rhs = { .data = "", .size = 0 };
- modify_keymap(buffer, true, mode, lhs, rhs, NULL, err);
+ modify_keymap(channel_id, buffer, true, mode, lhs, rhs, NULL, err);
}
/// Gets a map of buffer-local |user-commands|.
@@ -1273,6 +1375,63 @@ Object nvim_buf_call(Buffer buffer, LuaRef fun, Error *err)
return res;
}
+/// Create a new user command |user-commands| in the given buffer.
+///
+/// @param buffer Buffer handle, or 0 for current buffer.
+/// @param[out] err Error details, if any.
+/// @see nvim_add_user_command
+void nvim_buf_add_user_command(Buffer buffer, String name, Object command,
+ Dict(user_command) *opts, Error *err)
+ FUNC_API_SINCE(9)
+{
+ buf_T *target_buf = find_buffer_by_handle(buffer, err);
+ if (ERROR_SET(err)) {
+ return;
+ }
+
+ buf_T *save_curbuf = curbuf;
+ curbuf = target_buf;
+ add_user_command(name, command, opts, UC_BUFFER, err);
+ curbuf = save_curbuf;
+}
+
+/// Delete a buffer-local user-defined command.
+///
+/// Only commands created with |:command-buffer| or
+/// |nvim_buf_add_user_command()| can be deleted with this function.
+///
+/// @param buffer Buffer handle, or 0 for current buffer.
+/// @param name Name of the command to delete.
+/// @param[out] err Error details, if any.
+void nvim_buf_del_user_command(Buffer buffer, String name, Error *err)
+ FUNC_API_SINCE(9)
+{
+ garray_T *gap;
+ if (buffer == -1) {
+ gap = &ucmds;
+ } else {
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ gap = &buf->b_ucmds;
+ }
+
+ for (int i = 0; i < gap->ga_len; i++) {
+ ucmd_T *cmd = USER_CMD_GA(gap, i);
+ if (!STRCMP(name.data, cmd->uc_name)) {
+ free_ucmd(cmd);
+
+ gap->ga_len -= 1;
+
+ if (i < gap->ga_len) {
+ memmove(cmd, cmd + 1, (size_t)(gap->ga_len - i) * sizeof(ucmd_T));
+ }
+
+ return;
+ }
+ }
+
+ api_set_error(err, kErrorTypeException, "No such user-defined command: %s", name.data);
+}
+
Dictionary nvim__buf_stats(Buffer buffer, Error *err)
{
Dictionary rv = ARRAY_DICT_INIT;
@@ -1329,11 +1488,11 @@ static void fix_cursor(linenr_T lo, linenr_T hi, linenr_T extra)
}
// Normalizes 0-based indexes to buffer line numbers
-static int64_t normalize_index(buf_T *buf, int64_t index, bool *oob)
+static int64_t normalize_index(buf_T *buf, int64_t index, bool end_exclusive, bool *oob)
{
int64_t line_count = buf->b_ml.ml_line_count;
// Fix if < 0
- index = index < 0 ? line_count + index +1 : index;
+ index = index < 0 ? line_count + index + (int)end_exclusive : index;
// Check for oob
if (index > line_count) {
diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c
index 76b699800e..d2013c597c 100644
--- a/src/nvim/api/deprecated.c
+++ b/src/nvim/api/deprecated.c
@@ -22,11 +22,11 @@
/// @deprecated
/// @see nvim_exec
-String nvim_command_output(String command, Error *err)
+String nvim_command_output(uint64_t channel_id, String command, Error *err)
FUNC_API_SINCE(1)
FUNC_API_DEPRECATED_SINCE(7)
{
- return nvim_exec(command, true, err);
+ return nvim_exec(channel_id, command, true, err);
}
/// @deprecated Use nvim_exec_lua() instead.
@@ -130,7 +130,7 @@ Integer nvim_buf_set_virtual_text(Buffer buffer, Integer src_id, Integer line, A
return 0;
}
- uint64_t ns_id = src2ns(&src_id);
+ uint32_t ns_id = src2ns(&src_id);
int width;
VirtText virt_text = parse_virt_text(chunks, err, &width);
@@ -148,11 +148,12 @@ Integer nvim_buf_set_virtual_text(Buffer buffer, Integer src_id, Integer line, A
return src_id;
}
- Decoration *decor = xcalloc(1, sizeof(*decor));
- decor->virt_text = virt_text;
- decor->virt_text_width = width;
+ Decoration decor = DECORATION_INIT;
+ decor.virt_text = virt_text;
+ decor.virt_text_width = width;
+ decor.priority = 0;
- extmark_set(buf, ns_id, NULL, (int)line, 0, -1, -1, decor, true,
+ extmark_set(buf, ns_id, NULL, (int)line, 0, -1, -1, &decor, true,
false, kExtmarkNoUndo);
return src_id;
}
diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c
index 742b953c2a..3a968f07ab 100644
--- a/src/nvim/api/extmark.c
+++ b/src/nvim/api/extmark.c
@@ -85,12 +85,12 @@ const char *describe_ns(NS ns_id)
}
// Is the Namespace in use?
-static bool ns_initialized(uint64_t ns)
+static bool ns_initialized(uint32_t ns)
{
if (ns < 1) {
return false;
}
- return ns < (uint64_t)next_namespace_id;
+ return ns < (uint32_t)next_namespace_id;
}
@@ -106,22 +106,55 @@ static Array extmark_to_array(ExtmarkInfo extmark, bool id, bool add_dict)
if (add_dict) {
Dictionary dict = ARRAY_DICT_INIT;
+ PUT(dict, "right_gravity", BOOLEAN_OBJ(extmark.right_gravity));
+
if (extmark.end_row >= 0) {
PUT(dict, "end_row", INTEGER_OBJ(extmark.end_row));
PUT(dict, "end_col", INTEGER_OBJ(extmark.end_col));
+ PUT(dict, "end_right_gravity", BOOLEAN_OBJ(extmark.end_right_gravity));
}
- if (extmark.decor) {
- Decoration *decor = extmark.decor;
- if (decor->hl_id) {
- String name = cstr_to_string((const char *)syn_id2name(decor->hl_id));
- PUT(dict, "hl_group", STRING_OBJ(name));
+ Decoration *decor = &extmark.decor;
+ if (decor->hl_id) {
+ String name = cstr_to_string((const char *)syn_id2name(decor->hl_id));
+ PUT(dict, "hl_group", STRING_OBJ(name));
+ PUT(dict, "hl_eol", BOOLEAN_OBJ(decor->hl_eol));
+ }
+ if (decor->hl_mode) {
+ PUT(dict, "hl_mode", STRING_OBJ(cstr_to_string(hl_mode_str[decor->hl_mode])));
+ }
+
+ if (kv_size(decor->virt_text)) {
+ Array chunks = ARRAY_DICT_INIT;
+ for (size_t i = 0; i < decor->virt_text.size; i++) {
+ Array chunk = ARRAY_DICT_INIT;
+ VirtTextChunk *vtc = &decor->virt_text.items[i];
+ ADD(chunk, STRING_OBJ(cstr_to_string(vtc->text)));
+ if (vtc->hl_id > 0) {
+ ADD(chunk,
+ STRING_OBJ(cstr_to_string((const char *)syn_id2name(vtc->hl_id))));
+ }
+ ADD(chunks, ARRAY_OBJ(chunk));
}
- if (kv_size(decor->virt_text)) {
+ PUT(dict, "virt_text", ARRAY_OBJ(chunks));
+ PUT(dict, "virt_text_hide", BOOLEAN_OBJ(decor->virt_text_hide));
+ if (decor->virt_text_pos == kVTWinCol) {
+ PUT(dict, "virt_text_win_col", INTEGER_OBJ(decor->col));
+ }
+ PUT(dict, "virt_text_pos",
+ STRING_OBJ(cstr_to_string(virt_text_pos_str[decor->virt_text_pos])));
+ }
+
+ if (kv_size(decor->virt_lines)) {
+ Array all_chunks = ARRAY_DICT_INIT;
+ bool virt_lines_leftcol = false;
+ for (size_t i = 0; i < decor->virt_lines.size; i++) {
Array chunks = ARRAY_DICT_INIT;
- for (size_t i = 0; i < decor->virt_text.size; i++) {
+ VirtText *vt = &decor->virt_lines.items[i].line;
+ virt_lines_leftcol = decor->virt_lines.items[i].left_col;
+ for (size_t j = 0; j < vt->size; j++) {
Array chunk = ARRAY_DICT_INIT;
- VirtTextChunk *vtc = &decor->virt_text.items[i];
+ VirtTextChunk *vtc = &vt->items[j];
ADD(chunk, STRING_OBJ(cstr_to_string(vtc->text)));
if (vtc->hl_id > 0) {
ADD(chunk,
@@ -129,9 +162,14 @@ static Array extmark_to_array(ExtmarkInfo extmark, bool id, bool add_dict)
}
ADD(chunks, ARRAY_OBJ(chunk));
}
- PUT(dict, "virt_text", ARRAY_OBJ(chunks));
+ ADD(all_chunks, ARRAY_OBJ(chunks));
}
+ PUT(dict, "virt_lines", ARRAY_OBJ(all_chunks));
+ PUT(dict, "virt_lines_above", BOOLEAN_OBJ(decor->virt_lines_above));
+ PUT(dict, "virt_lines_leftcol", BOOLEAN_OBJ(virt_lines_leftcol));
+ }
+ if (decor->hl_id || kv_size(decor->virt_text)) {
PUT(dict, "priority", INTEGER_OBJ(decor->priority));
}
@@ -166,7 +204,7 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
return rv;
}
- if (!ns_initialized((uint64_t)ns_id)) {
+ if (!ns_initialized((uint32_t)ns_id)) {
api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
return rv;
}
@@ -191,7 +229,7 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
}
- ExtmarkInfo extmark = extmark_from_id(buf, (uint64_t)ns_id, (uint64_t)id);
+ ExtmarkInfo extmark = extmark_from_id(buf, (uint32_t)ns_id, (uint32_t)id);
if (extmark.row < 0) {
return rv;
}
@@ -252,7 +290,7 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
return rv;
}
- if (!ns_initialized((uint64_t)ns_id)) {
+ if (!ns_initialized((uint32_t)ns_id)) {
api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
return rv;
}
@@ -310,7 +348,7 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
}
- ExtmarkInfoArray marks = extmark_get(buf, (uint64_t)ns_id, l_row, l_col,
+ ExtmarkInfoArray marks = extmark_get(buf, (uint32_t)ns_id, l_row, l_col,
u_row, u_col, (int64_t)limit, reverse);
for (size_t i = 0; i < kv_size(marks); i++) {
@@ -404,6 +442,10 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
/// for left). Defaults to false.
/// - priority: a priority value for the highlight group. For
/// example treesitter highlighting uses a value of 100.
+/// - strict: boolean that indicates extmark should not be placed
+/// if the line or column value is past the end of the
+/// buffer or end of the line respectively. Defaults to true.
+///
/// @param[out] err Error details, if any
/// @return Id of the created/updated extmark
Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer col,
@@ -417,14 +459,14 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
goto error;
}
- if (!ns_initialized((uint64_t)ns_id)) {
+ if (!ns_initialized((uint32_t)ns_id)) {
api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
goto error;
}
- uint64_t id = 0;
+ uint32_t id = 0;
if (opts->id.type == kObjectTypeInteger && opts->id.data.integer > 0) {
- id = (uint64_t)opts->id.data.integer;
+ id = (uint32_t)opts->id.data.integer;
} else if (HAS_KEY(opts->id)) {
api_set_error(err, kErrorTypeValidation, "id is not a positive integer");
goto error;
@@ -441,9 +483,18 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
opts->end_row = opts->end_line;
}
+#define OPTION_TO_BOOL(target, name, val) \
+ target = api_object_to_bool(opts->name, #name, val, err); \
+ if (ERROR_SET(err)) { \
+ goto error; \
+ }
+
+ bool strict = true;
+ OPTION_TO_BOOL(strict, strict, true);
+
if (opts->end_row.type == kObjectTypeInteger) {
Integer val = opts->end_row.data.integer;
- if (val < 0 || val > buf->b_ml.ml_line_count) {
+ if (val < 0 || (val > buf->b_ml.ml_line_count && strict)) {
api_set_error(err, kErrorTypeValidation, "end_row value outside range");
goto error;
} else {
@@ -512,12 +563,6 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
goto error;
}
-#define OPTION_TO_BOOL(target, name, val) \
- target = api_object_to_bool(opts->name, #name, val, err); \
- if (ERROR_SET(err)) { \
- goto error; \
- }
-
OPTION_TO_BOOL(decor.virt_text_hide, virt_text_hide, false);
OPTION_TO_BOOL(decor.hl_eol, hl_eol, false);
@@ -596,16 +641,30 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
bool ephemeral = false;
OPTION_TO_BOOL(ephemeral, ephemeral, false);
- if (line < 0 || line > buf->b_ml.ml_line_count) {
+ if (line < 0) {
api_set_error(err, kErrorTypeValidation, "line value outside range");
goto error;
+ } else if (line > buf->b_ml.ml_line_count) {
+ if (strict) {
+ api_set_error(err, kErrorTypeValidation, "line value outside range");
+ goto error;
+ } else {
+ line = buf->b_ml.ml_line_count;
+ }
} else if (line < buf->b_ml.ml_line_count) {
len = ephemeral ? MAXCOL : STRLEN(ml_get_buf(buf, (linenr_T)line+1, false));
}
if (col == -1) {
col = (Integer)len;
- } else if (col < -1 || col > (Integer)len) {
+ } else if (col > (Integer)len) {
+ if (strict) {
+ api_set_error(err, kErrorTypeValidation, "col value outside range");
+ goto error;
+ } else {
+ col = (Integer)len;
+ }
+ } else if (col < -1) {
api_set_error(err, kErrorTypeValidation, "col value outside range");
goto error;
}
@@ -621,27 +680,17 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
line2 = (int)line;
}
if (col2 > (Integer)len) {
- api_set_error(err, kErrorTypeValidation, "end_col value outside range");
- goto error;
+ if (strict) {
+ api_set_error(err, kErrorTypeValidation, "end_col value outside range");
+ goto error;
+ } else {
+ col2 = (int)len;
+ }
}
} else if (line2 >= 0) {
col2 = 0;
}
- Decoration *d = NULL;
-
- if (ephemeral) {
- d = &decor;
- } else if (kv_size(decor.virt_text) || kv_size(decor.virt_lines)
- || decor.priority != DECOR_PRIORITY_BASE
- || decor.hl_eol) {
- // TODO(bfredl): this is a bit sketchy. eventually we should
- // have predefined decorations for both marks/ephemerals
- d = xcalloc(1, sizeof(*d));
- *d = decor;
- } else if (decor.hl_id) {
- d = decor_hl(decor.hl_id);
- }
// TODO(bfredl): synergize these two branches even more
if (ephemeral && decor_state.buf == buf) {
@@ -652,12 +701,8 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
goto error;
}
- extmark_set(buf, (uint64_t)ns_id, &id, (int)line, (colnr_T)col, line2, col2,
- d, right_gravity, end_right_gravity, kExtmarkNoUndo);
-
- if (kv_size(decor.virt_lines)) {
- redraw_buf_line_later(buf, MIN(buf->b_ml.ml_line_count, line+1+(decor.virt_lines_above?0:1)));
- }
+ extmark_set(buf, (uint32_t)ns_id, &id, (int)line, (colnr_T)col, line2, col2,
+ &decor, right_gravity, end_right_gravity, kExtmarkNoUndo);
}
return (Integer)id;
@@ -682,23 +727,23 @@ Boolean nvim_buf_del_extmark(Buffer buffer, Integer ns_id, Integer id, Error *er
if (!buf) {
return false;
}
- if (!ns_initialized((uint64_t)ns_id)) {
+ if (!ns_initialized((uint32_t)ns_id)) {
api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
return false;
}
- return extmark_del(buf, (uint64_t)ns_id, (uint64_t)id);
+ return extmark_del(buf, (uint32_t)ns_id, (uint32_t)id);
}
-uint64_t src2ns(Integer *src_id)
+uint32_t src2ns(Integer *src_id)
{
if (*src_id == 0) {
*src_id = nvim_create_namespace((String)STRING_INIT);
}
if (*src_id < 0) {
- return UINT64_MAX;
+ return (((uint32_t)1) << 31) - 1;
} else {
- return (uint64_t)(*src_id);
+ return (uint32_t)(*src_id);
}
}
@@ -753,7 +798,7 @@ Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, In
col_end = MAXCOL;
}
- uint64_t ns = src2ns(&ns_id);
+ uint32_t ns = src2ns(&ns_id);
if (!(line < buf->b_ml.ml_line_count)) {
// safety check, we can't add marks outside the range
@@ -773,10 +818,13 @@ Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, In
end_line++;
}
+ Decoration decor = DECORATION_INIT;
+ decor.hl_id = hl_id;
+
extmark_set(buf, ns, NULL,
(int)line, (colnr_T)col_start,
end_line, (colnr_T)col_end,
- decor_hl(hl_id), true, false, kExtmarkNoUndo);
+ &decor, true, false, kExtmarkNoUndo);
return ns_id;
}
@@ -808,7 +856,7 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start,
if (line_end < 0 || line_end > MAXLNUM) {
line_end = MAXLNUM;
}
- extmark_clear(buf, (ns_id < 0 ? 0 : (uint64_t)ns_id),
+ extmark_clear(buf, (ns_id < 0 ? 0 : (uint32_t)ns_id),
(int)line_start, 0,
(int)line_end-1, MAXCOL);
}
diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua
index f3e7f2f1dc..b05816f8ac 100644
--- a/src/nvim/api/keysets.lua
+++ b/src/nvim/api/keysets.lua
@@ -21,6 +21,7 @@ return {
"virt_lines";
"virt_lines_above";
"virt_lines_leftcol";
+ "strict";
};
keymap = {
"noremap";
@@ -29,10 +30,25 @@ return {
"script";
"expr";
"unique";
+ "callback";
+ "desc";
};
get_commands = {
"builtin";
};
+ user_command = {
+ "addr";
+ "bang";
+ "bar";
+ "complete";
+ "count";
+ "desc";
+ "force";
+ "keepscript";
+ "nargs";
+ "range";
+ "register";
+ };
float_config = {
"row";
"col";
@@ -62,5 +78,62 @@ return {
option = {
"scope";
};
+ highlight = {
+ "bold";
+ "standout";
+ "strikethrough";
+ "underline";
+ "undercurl";
+ "italic";
+ "reverse";
+ "nocombine";
+ "default";
+ "global";
+ "cterm";
+ "foreground"; "fg";
+ "background"; "bg";
+ "ctermfg";
+ "ctermbg";
+ "special"; "sp";
+ "link";
+ "fallback";
+ "blend";
+ "temp";
+ };
+ highlight_cterm = {
+ "bold";
+ "standout";
+ "strikethrough";
+ "underline";
+ "undercurl";
+ "italic";
+ "reverse";
+ "nocombine";
+ };
+ -- Autocmds
+ create_autocmd = {
+ "buffer";
+ "callback";
+ "command";
+ "desc";
+ "group";
+ "once";
+ "nested";
+ "pattern";
+ };
+ do_autocmd = {
+ "buffer";
+ "group";
+ "modeline";
+ "pattern";
+ };
+ get_autocmds = {
+ "event";
+ "group";
+ "pattern";
+ };
+ create_augroup = {
+ "clear";
+ };
}
diff --git a/src/nvim/api/private/converter.c b/src/nvim/api/private/converter.c
index 36da6c13a9..49e3cf7df7 100644
--- a/src/nvim/api/private/converter.c
+++ b/src/nvim/api/private/converter.c
@@ -10,6 +10,9 @@
#include "nvim/api/private/helpers.h"
#include "nvim/assert.h"
#include "nvim/eval/typval.h"
+#include "nvim/eval/userfunc.h"
+#include "nvim/lua/converter.h"
+#include "nvim/lua/executor.h"
/// Helper structure for vim_to_object
typedef struct {
@@ -54,7 +57,7 @@ typedef struct {
const size_t len_ = (size_t)(len); \
const blob_T *const blob_ = (blob); \
kvi_push(edata->stack, STRING_OBJ(((String) { \
- .data = len_ != 0 ? xmemdup(blob_->bv_ga.ga_data, len_) : NULL, \
+ .data = len_ != 0 ? xmemdupz(blob_->bv_ga.ga_data, len_) : xstrdup(""), \
.size = len_ \
}))); \
} while (0)
@@ -228,6 +231,14 @@ static inline void typval_encode_dict_end(EncodedData *const edata)
/// @return The converted value
Object vim_to_object(typval_T *obj)
{
+ if (obj->v_type == VAR_FUNC) {
+ ufunc_T *fp = find_func(obj->vval.v_string);
+ assert(fp != NULL);
+ if (fp->uf_cb == nlua_CFunction_func_call) {
+ LuaRef ref = api_new_luaref(((LuaCFunctionState *)fp->uf_cb_state)->lua_callable.func_ref);
+ return LUAREF_OBJ(ref);
+ }
+ }
EncodedData edata;
kvi_init(edata.stack);
const int evo_ret = encode_vim_to_object(&edata, obj,
@@ -340,6 +351,16 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err)
tv->vval.v_dict = dict;
break;
}
+
+ case kObjectTypeLuaRef: {
+ LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState));
+ state->lua_callable.func_ref = api_new_luaref(obj.data.luaref);
+ char_u *name = register_cfunc(&nlua_CFunction_func_call, &nlua_CFunction_func_free, state);
+ tv->v_type = VAR_FUNC;
+ tv->vval.v_string = vim_strsave(name);
+ break;
+ }
+
default:
abort();
}
diff --git a/src/nvim/api/private/dispatch.c b/src/nvim/api/private/dispatch.c
index 8ab7743e01..f670f06357 100644
--- a/src/nvim/api/private/dispatch.c
+++ b/src/nvim/api/private/dispatch.c
@@ -6,22 +6,30 @@
#include <msgpack.h>
#include <stdbool.h>
-#include "nvim/api/buffer.h"
#include "nvim/api/deprecated.h"
-#include "nvim/api/extmark.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/log.h"
+#include "nvim/map.h"
+#include "nvim/msgpack_rpc/helpers.h"
+#include "nvim/vim.h"
+
+// ===========================================================================
+// NEW API FILES MUST GO HERE.
+//
+// When creating a new API file, you must include it here,
+// so that the dispatcher can find the C functions that you are creating!
+// ===========================================================================
+#include "nvim/api/autocmd.h"
+#include "nvim/api/buffer.h"
+#include "nvim/api/extmark.h"
#include "nvim/api/tabpage.h"
#include "nvim/api/ui.h"
#include "nvim/api/vim.h"
#include "nvim/api/vimscript.h"
#include "nvim/api/win_config.h"
#include "nvim/api/window.h"
-#include "nvim/log.h"
-#include "nvim/map.h"
-#include "nvim/msgpack_rpc/helpers.h"
-#include "nvim/vim.h"
static Map(String, MsgpackRpcRequestHandler) methods = MAP_INIT;
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 9b407eab8b..35e8589255 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -139,10 +139,10 @@ bool try_end(Error *err)
got_int = false;
} else if (msg_list != NULL && *msg_list != NULL) {
int should_free;
- char *msg = (char *)get_exception_string(*msg_list,
- ET_ERROR,
- NULL,
- &should_free);
+ char *msg = get_exception_string(*msg_list,
+ ET_ERROR,
+ NULL,
+ &should_free);
api_set_error(err, kErrorTypeException, "%s", msg);
free_global_msglist();
@@ -396,19 +396,14 @@ void set_option_to(uint64_t channel_id, void *to, int type, String name, Object
stringval = value.data.string.data;
}
- const sctx_T save_current_sctx = current_sctx;
- current_sctx.sc_sid =
- channel_id == LUA_INTERNAL_CALL ? SID_LUA : SID_API_CLIENT;
- current_sctx.sc_lnum = 0;
- current_channel_id = channel_id;
+ WITH_SCRIPT_CONTEXT(channel_id, {
+ const int opt_flags = (type == SREQ_WIN && !(flags & SOPT_GLOBAL))
+ ? 0 : (type == SREQ_GLOBAL)
+ ? OPT_GLOBAL : OPT_LOCAL;
- const int opt_flags = (type == SREQ_WIN && !(flags & SOPT_GLOBAL))
- ? 0 : (type == SREQ_GLOBAL)
- ? OPT_GLOBAL : OPT_LOCAL;
- set_option_value_for(name.data, numval, stringval,
- opt_flags, type, to, err);
-
- current_sctx = save_current_sctx;
+ set_option_value_for(name.data, numval, stringval,
+ opt_flags, type, to, err);
+ });
}
@@ -591,9 +586,10 @@ Array string_to_array(const String input, bool crlf)
/// @param buffer Buffer handle for a specific buffer, or 0 for the current
/// buffer, or -1 to signify global behavior ("all buffers")
/// @param is_unmap When true, removes the mapping that matches {lhs}.
-void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String rhs,
- Dict(keymap) *opts, Error *err)
+void modify_keymap(uint64_t channel_id, Buffer buffer, bool is_unmap, String mode, String lhs,
+ String rhs, Dict(keymap) *opts, Error *err)
{
+ LuaRef lua_funcref = LUA_NOREF;
bool global = (buffer == -1);
if (global) {
buffer = 0;
@@ -604,6 +600,12 @@ void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String
return;
}
+ const sctx_T save_current_sctx = api_set_sctx(channel_id);
+
+ if (opts != NULL && opts->callback.type == kObjectTypeLuaRef) {
+ lua_funcref = opts->callback.data.luaref;
+ opts->callback.data.luaref = LUA_NOREF;
+ }
MapArguments parsed_args = MAP_ARGUMENTS_INIT;
if (opts) {
#define KEY_TO_BOOL(name) \
@@ -623,9 +625,13 @@ void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String
parsed_args.buffer = !global;
set_maparg_lhs_rhs((char_u *)lhs.data, lhs.size,
- (char_u *)rhs.data, rhs.size,
+ (char_u *)rhs.data, rhs.size, lua_funcref,
CPO_TO_CPO_FLAGS, &parsed_args);
-
+ if (opts != NULL && opts->desc.type == kObjectTypeString) {
+ parsed_args.desc = xstrdup(opts->desc.data.string.data);
+ } else {
+ parsed_args.desc = NULL;
+ }
if (parsed_args.lhs_len > MAXMAPLEN) {
api_set_error(err, kErrorTypeValidation, "LHS exceeds maximum map length: %s", lhs.data);
goto fail_and_free;
@@ -658,7 +664,8 @@ void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String
bool is_noremap = parsed_args.noremap;
assert(!(is_unmap && is_noremap));
- if (!is_unmap && (parsed_args.rhs_len == 0 && !parsed_args.rhs_is_noop)) {
+ if (!is_unmap && lua_funcref == LUA_NOREF
+ && (parsed_args.rhs_len == 0 && !parsed_args.rhs_is_noop)) {
if (rhs.size == 0) { // assume that the user wants RHS to be a <Nop>
parsed_args.rhs_is_noop = true;
} else {
@@ -668,9 +675,13 @@ void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String
api_set_error(err, kErrorTypeValidation, "Parsing of nonempty RHS failed: %s", rhs.data);
goto fail_and_free;
}
- } else if (is_unmap && parsed_args.rhs_len) {
- api_set_error(err, kErrorTypeValidation,
- "Gave nonempty RHS in unmap command: %s", parsed_args.rhs);
+ } else if (is_unmap && (parsed_args.rhs_len || parsed_args.rhs_lua != LUA_NOREF)) {
+ if (parsed_args.rhs_len) {
+ api_set_error(err, kErrorTypeValidation,
+ "Gave nonempty RHS in unmap command: %s", parsed_args.rhs);
+ } else {
+ api_set_error(err, kErrorTypeValidation, "Gave nonempty RHS for unmap");
+ }
goto fail_and_free;
}
@@ -700,10 +711,13 @@ void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String
goto fail_and_free;
} // switch
+ parsed_args.rhs_lua = LUA_NOREF; // don't clear ref on success
fail_and_free:
+ current_sctx = save_current_sctx;
+ NLUA_CLEAR_REF(parsed_args.rhs_lua);
xfree(parsed_args.rhs);
xfree(parsed_args.orig_rhs);
- return;
+ XFREE_CLEAR(parsed_args.desc);
}
/// Collects `n` buffer lines into array `l`, optionally replacing newlines
@@ -742,6 +756,52 @@ bool buf_collect_lines(buf_T *buf, size_t n, int64_t start, bool replace_nl, Arr
return true;
}
+/// Returns a substring of a buffer line
+///
+/// @param buf Buffer handle
+/// @param lnum Line number (1-based)
+/// @param start_col Starting byte offset into line (0-based)
+/// @param end_col Ending byte offset into line (0-based, exclusive)
+/// @param replace_nl Replace newlines ('\n') with null ('\0')
+/// @param err Error object
+/// @return The text between start_col and end_col on line lnum of buffer buf
+String buf_get_text(buf_T *buf, int64_t lnum, int64_t start_col, int64_t end_col, bool replace_nl,
+ Error *err)
+{
+ String rv = STRING_INIT;
+
+ if (lnum >= MAXLNUM) {
+ api_set_error(err, kErrorTypeValidation, "Line index is too high");
+ return rv;
+ }
+
+ const char *bufstr = (char *)ml_get_buf(buf, (linenr_T)lnum, false);
+ size_t line_length = strlen(bufstr);
+
+ start_col = start_col < 0 ? (int64_t)line_length + start_col + 1 : start_col;
+ end_col = end_col < 0 ? (int64_t)line_length + end_col + 1 : end_col;
+
+ if (start_col >= MAXCOL || end_col >= MAXCOL) {
+ api_set_error(err, kErrorTypeValidation, "Column index is too high");
+ return rv;
+ }
+
+ if (start_col > end_col) {
+ api_set_error(err, kErrorTypeValidation, "start_col must be less than end_col");
+ return rv;
+ }
+
+ if ((size_t)start_col >= line_length) {
+ return rv;
+ }
+
+ rv = cstrn_to_string(&bufstr[start_col], (size_t)(end_col - start_col));
+ if (replace_nl) {
+ strchrsub(rv.data, '\n', '\0');
+ }
+
+ return rv;
+}
void api_free_string(String value)
{
@@ -961,6 +1021,10 @@ Object copy_object(Object obj)
case kObjectTypeDictionary:
return DICTIONARY_OBJ(copy_dictionary(obj.data.dictionary));
+
+ case kObjectTypeLuaRef:
+ return LUAREF_OBJ(api_new_luaref(obj.data.luaref));
+
default:
abort();
}
@@ -969,18 +1033,16 @@ Object copy_object(Object obj)
static void set_option_value_for(char *key, int numval, char *stringval, int opt_flags,
int opt_type, void *from, Error *err)
{
- win_T *save_curwin = NULL;
- tabpage_T *save_curtab = NULL;
+ switchwin_T switchwin;
aco_save_T aco;
try_start();
switch (opt_type)
{
case SREQ_WIN:
- if (switch_win_noblock(&save_curwin, &save_curtab, (win_T *)from,
- win_find_tabpage((win_T *)from), true)
+ if (switch_win_noblock(&switchwin, (win_T *)from, win_find_tabpage((win_T *)from), true)
== FAIL) {
- restore_win_noblock(save_curwin, save_curtab, true);
+ restore_win_noblock(&switchwin, true);
if (try_end(err)) {
return;
}
@@ -990,7 +1052,7 @@ static void set_option_value_for(char *key, int numval, char *stringval, int opt
return;
}
set_option_value_err(key, numval, stringval, opt_flags, err);
- restore_win_noblock(save_curwin, save_curtab, true);
+ restore_win_noblock(&switchwin, true);
break;
case SREQ_BUF:
aucmd_prepbuf(&aco, (buf_T *)from);
@@ -1048,8 +1110,9 @@ void api_set_error(Error *err, ErrorType errType, const char *format, ...)
///
/// @param mode The abbreviation for the mode
/// @param buf The buffer to get the mapping array. NULL for global
+/// @param from_lua Whether it is called from internal lua api.
/// @returns Array of maparg()-like dictionaries describing mappings
-ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf)
+ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf, bool from_lua)
{
Array mappings = ARRAY_DICT_INIT;
dict_T *const dict = tv_dict_alloc();
@@ -1069,8 +1132,19 @@ ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf)
// Check for correct mode
if (int_mode & current_maphash->m_mode) {
mapblock_fill_dict(dict, current_maphash, buffer_value, false);
- ADD(mappings, vim_to_object((typval_T[]) { { .v_type = VAR_DICT, .vval.v_dict = dict } }));
-
+ Object api_dict = vim_to_object((typval_T[]) { { .v_type = VAR_DICT,
+ .vval.v_dict = dict } });
+ if (from_lua) {
+ Dictionary d = api_dict.data.dictionary;
+ for (size_t j = 0; j < d.size; j++) {
+ if (strequal("callback", d.items[j].key.data)) {
+ d.items[j].value.type = kObjectTypeLuaRef;
+ d.items[j].value.data.luaref = api_new_luaref((LuaRef)d.items[j].value.data.integer);
+ break;
+ }
+ }
+ }
+ ADD(mappings, api_dict);
tv_dict_clear(dict);
}
}
@@ -1108,7 +1182,7 @@ bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, int
return false;
}
- ExtmarkInfo extmark = extmark_from_id(buf, (uint64_t)ns_id, (uint64_t)id);
+ ExtmarkInfo extmark = extmark_from_id(buf, (uint32_t)ns_id, (uint32_t)id);
if (extmark.row >= 0) {
*row = extmark.row;
*col = extmark.col;
@@ -1342,3 +1416,230 @@ const char *get_default_stl_hl(win_T *wp)
return "StatusLineNC";
}
}
+
+void add_user_command(String name, Object command, Dict(user_command) *opts, int flags, Error *err)
+{
+ uint32_t argt = 0;
+ long def = -1;
+ cmd_addr_T addr_type_arg = ADDR_NONE;
+ int compl = EXPAND_NOTHING;
+ char *compl_arg = NULL;
+ char *rep = NULL;
+ LuaRef luaref = LUA_NOREF;
+ LuaRef compl_luaref = LUA_NOREF;
+
+ if (!uc_validate_name(name.data)) {
+ api_set_error(err, kErrorTypeValidation, "Invalid command name");
+ goto err;
+ }
+
+ if (mb_islower(name.data[0])) {
+ api_set_error(err, kErrorTypeValidation, "'name' must begin with an uppercase letter");
+ goto err;
+ }
+
+ if (HAS_KEY(opts->range) && HAS_KEY(opts->count)) {
+ api_set_error(err, kErrorTypeValidation, "'range' and 'count' are mutually exclusive");
+ goto err;
+ }
+
+ if (opts->nargs.type == kObjectTypeInteger) {
+ switch (opts->nargs.data.integer) {
+ case 0:
+ // Default value, nothing to do
+ break;
+ case 1:
+ argt |= EX_EXTRA | EX_NOSPC | EX_NEEDARG;
+ break;
+ default:
+ api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
+ goto err;
+ }
+ } else if (opts->nargs.type == kObjectTypeString) {
+ if (opts->nargs.data.string.size > 1) {
+ api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
+ goto err;
+ }
+
+ switch (opts->nargs.data.string.data[0]) {
+ case '*':
+ argt |= EX_EXTRA;
+ break;
+ case '?':
+ argt |= EX_EXTRA | EX_NOSPC;
+ break;
+ case '+':
+ argt |= EX_EXTRA | EX_NEEDARG;
+ break;
+ default:
+ api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
+ goto err;
+ }
+ } else if (HAS_KEY(opts->nargs)) {
+ api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
+ goto err;
+ }
+
+ if (HAS_KEY(opts->complete) && !argt) {
+ api_set_error(err, kErrorTypeValidation, "'complete' used without 'nargs'");
+ goto err;
+ }
+
+ if (opts->range.type == kObjectTypeBoolean) {
+ if (opts->range.data.boolean) {
+ argt |= EX_RANGE;
+ addr_type_arg = ADDR_LINES;
+ }
+ } else if (opts->range.type == kObjectTypeString) {
+ if (opts->range.data.string.data[0] == '%' && opts->range.data.string.size == 1) {
+ argt |= EX_RANGE | EX_DFLALL;
+ addr_type_arg = ADDR_LINES;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "Invalid value for 'range'");
+ goto err;
+ }
+ } else if (opts->range.type == kObjectTypeInteger) {
+ argt |= EX_RANGE | EX_ZEROR;
+ def = opts->range.data.integer;
+ addr_type_arg = ADDR_LINES;
+ } else if (HAS_KEY(opts->range)) {
+ api_set_error(err, kErrorTypeValidation, "Invalid value for 'range'");
+ goto err;
+ }
+
+ if (opts->count.type == kObjectTypeBoolean) {
+ if (opts->count.data.boolean) {
+ argt |= EX_COUNT | EX_ZEROR | EX_RANGE;
+ addr_type_arg = ADDR_OTHER;
+ def = 0;
+ }
+ } else if (opts->count.type == kObjectTypeInteger) {
+ argt |= EX_COUNT | EX_ZEROR | EX_RANGE;
+ addr_type_arg = ADDR_OTHER;
+ def = opts->count.data.integer;
+ } else if (HAS_KEY(opts->count)) {
+ api_set_error(err, kErrorTypeValidation, "Invalid value for 'count'");
+ goto err;
+ }
+
+ if (opts->addr.type == kObjectTypeString) {
+ if (parse_addr_type_arg((char_u *)opts->addr.data.string.data, (int)opts->addr.data.string.size,
+ &addr_type_arg) != OK) {
+ api_set_error(err, kErrorTypeValidation, "Invalid value for 'addr'");
+ goto err;
+ }
+
+ if (addr_type_arg != ADDR_LINES) {
+ argt |= EX_ZEROR;
+ }
+ } else if (HAS_KEY(opts->addr)) {
+ api_set_error(err, kErrorTypeValidation, "Invalid value for 'addr'");
+ goto err;
+ }
+
+ if (api_object_to_bool(opts->bang, "bang", false, err)) {
+ argt |= EX_BANG;
+ } else if (ERROR_SET(err)) {
+ goto err;
+ }
+
+ if (api_object_to_bool(opts->bar, "bar", false, err)) {
+ argt |= EX_TRLBAR;
+ } else if (ERROR_SET(err)) {
+ goto err;
+ }
+
+
+ if (api_object_to_bool(opts->register_, "register", false, err)) {
+ argt |= EX_REGSTR;
+ } else if (ERROR_SET(err)) {
+ goto err;
+ }
+
+ if (api_object_to_bool(opts->keepscript, "keepscript", false, err)) {
+ argt |= EX_KEEPSCRIPT;
+ } else if (ERROR_SET(err)) {
+ goto err;
+ }
+
+ bool force = api_object_to_bool(opts->force, "force", true, err);
+ if (ERROR_SET(err)) {
+ goto err;
+ }
+
+ if (opts->complete.type == kObjectTypeLuaRef) {
+ compl = EXPAND_USER_LUA;
+ compl_luaref = api_new_luaref(opts->complete.data.luaref);
+ } else if (opts->complete.type == kObjectTypeString) {
+ if (parse_compl_arg((char_u *)opts->complete.data.string.data,
+ (int)opts->complete.data.string.size, &compl, &argt,
+ (char_u **)&compl_arg) != OK) {
+ api_set_error(err, kErrorTypeValidation, "Invalid value for 'complete'");
+ goto err;
+ }
+ } else if (HAS_KEY(opts->complete)) {
+ api_set_error(err, kErrorTypeValidation, "Invalid value for 'complete'");
+ goto err;
+ }
+
+ switch (command.type) {
+ case kObjectTypeLuaRef:
+ luaref = api_new_luaref(command.data.luaref);
+ if (opts->desc.type == kObjectTypeString) {
+ rep = opts->desc.data.string.data;
+ } else {
+ snprintf((char *)IObuff, IOSIZE, "<Lua function %d>", luaref);
+ rep = (char *)IObuff;
+ }
+ break;
+ case kObjectTypeString:
+ rep = command.data.string.data;
+ break;
+ default:
+ api_set_error(err, kErrorTypeValidation, "'command' must be a string or Lua function");
+ goto err;
+ }
+
+ if (uc_add_command((char_u *)name.data, name.size, (char_u *)rep, argt, def, flags,
+ compl, (char_u *)compl_arg, compl_luaref, addr_type_arg, luaref,
+ force) != OK) {
+ api_set_error(err, kErrorTypeException, "Failed to create user command");
+ goto err;
+ }
+
+ return;
+
+err:
+ NLUA_CLEAR_REF(luaref);
+ NLUA_CLEAR_REF(compl_luaref);
+}
+
+int find_sid(uint64_t channel_id)
+{
+ switch (channel_id) {
+ case VIML_INTERNAL_CALL:
+ // TODO(autocmd): Figure out what this should be
+ // return SID_API_CLIENT;
+ case LUA_INTERNAL_CALL:
+ return SID_LUA;
+ default:
+ return SID_API_CLIENT;
+ }
+}
+
+/// Sets sctx for API calls.
+///
+/// @param channel_id api clients id. Used to determine if it's a internal
+/// call or a rpc call.
+/// @return returns previous value of current_sctx. To be used
+/// to be used for restoring sctx to previous state.
+sctx_T api_set_sctx(uint64_t channel_id)
+{
+ sctx_T old_current_sctx = current_sctx;
+ if (channel_id != VIML_INTERNAL_CALL) {
+ current_sctx.sc_sid =
+ channel_id == LUA_INTERNAL_CALL ? SID_LUA : SID_API_CLIENT;
+ current_sctx.sc_lnum = 0;
+ }
+ return old_current_sctx;
+}
diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h
index 6d0aec9c90..bc7c2e6a60 100644
--- a/src/nvim/api/private/helpers.h
+++ b/src/nvim/api/private/helpers.h
@@ -138,10 +138,29 @@ typedef struct {
msg_list = saved_msg_list; /* Restore the exception context. */ \
} while (0)
+// Useful macro for executing some `code` for each item in an array.
+#define FOREACH_ITEM(a, __foreach_item, code) \
+ for (size_t __foreach_i = 0; __foreach_i < (a).size; __foreach_i++) { \
+ Object __foreach_item = (a).items[__foreach_i]; \
+ code; \
+ }
+
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/private/helpers.h.generated.h"
# include "keysets.h.generated.h"
#endif
+#define WITH_SCRIPT_CONTEXT(channel_id, code) \
+ do { \
+ const sctx_T save_current_sctx = current_sctx; \
+ current_sctx.sc_sid = \
+ (channel_id) == LUA_INTERNAL_CALL ? SID_LUA : SID_API_CLIENT; \
+ current_sctx.sc_lnum = 0; \
+ current_channel_id = channel_id; \
+ code; \
+ current_sctx = save_current_sctx; \
+ } while (0);
+
#endif // NVIM_API_PRIVATE_HELPERS_H
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index f81cdf9deb..9d0b096a36 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -19,6 +19,7 @@
#include "nvim/ascii.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
+#include "nvim/charset.h"
#include "nvim/context.h"
#include "nvim/decoration.h"
#include "nvim/edit.h"
@@ -30,7 +31,9 @@
#include "nvim/file_search.h"
#include "nvim/fileio.h"
#include "nvim/getchar.h"
+#include "nvim/globals.h"
#include "nvim/highlight.h"
+#include "nvim/highlight_defs.h"
#include "nvim/lua/executor.h"
#include "nvim/mark.h"
#include "nvim/memline.h"
@@ -121,7 +124,13 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Error *err)
/// Set a highlight group.
///
-/// @param ns_id number of namespace for this highlight
+/// Note: unlike the `:highlight` command which can update a highlight group,
+/// this function completely replaces the definition. For example:
+/// `nvim_set_hl(0, 'Visual', {})` will clear the highlight group 'Visual'.
+///
+/// @param ns_id number of namespace for this highlight. Use value 0
+/// to set a highlight group in the global (`:highlight`)
+/// namespace.
/// @param name highlight group name, like ErrorMsg
/// @param val highlight definition map, like |nvim_get_hl_by_name|.
/// in addition the following keys are also recognized:
@@ -135,9 +144,8 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Error *err)
/// same as attributes of gui color
/// @param[out] err Error details, if any
///
-/// TODO: ns_id = 0, should modify :highlight namespace
-/// TODO val should take update vs reset flag
-void nvim_set_hl(Integer ns_id, String name, Dictionary val, Error *err)
+// TODO(bfredl): val should take update vs reset flag
+void nvim_set_hl(Integer ns_id, String name, Dict(highlight) *val, Error *err)
FUNC_API_SINCE(7)
{
int hl_id = syn_check_group(name.data, (int)name.size);
@@ -145,7 +153,7 @@ void nvim_set_hl(Integer ns_id, String name, Dictionary val, Error *err)
HlAttrs attrs = dict2hlattrs(val, true, &link_id, err);
if (!ERROR_SET(err)) {
- ns_hl_def((NS)ns_id, hl_id, attrs, link_id);
+ ns_hl_def((NS)ns_id, hl_id, attrs, link_id, val);
}
}
@@ -169,7 +177,7 @@ void nvim__set_hl_ns(Integer ns_id, Error *err)
// event path for redraws caused by "fast" events. This could tie in with
// better throttling of async events causing redraws, such as non-batched
// nvim_buf_set_extmark calls from async contexts.
- if (!provider_active && !ns_hl_changed) {
+ if (!provider_active && !ns_hl_changed && must_redraw < NOT_VALID) {
multiqueue_put(main_loop.events, on_redraw_event, 0);
}
ns_hl_changed = true;
@@ -187,21 +195,23 @@ static void on_redraw_event(void **argv)
/// On execution error: does not fail, but updates v:errmsg.
///
/// To input sequences like <C-o> use |nvim_replace_termcodes()| (typically
-/// with escape_csi=true) to replace |keycodes|, then pass the result to
+/// with escape_ks=false) to replace |keycodes|, then pass the result to
/// nvim_feedkeys().
///
/// Example:
/// <pre>
/// :let key = nvim_replace_termcodes("<C-o>", v:true, v:false, v:true)
-/// :call nvim_feedkeys(key, 'n', v:true)
+/// :call nvim_feedkeys(key, 'n', v:false)
/// </pre>
///
/// @param keys to be typed
/// @param mode behavior flags, see |feedkeys()|
-/// @param escape_csi If true, escape K_SPECIAL/CSI bytes in `keys`
+/// @param escape_ks If true, escape K_SPECIAL bytes in `keys`
+/// This should be false if you already used
+/// |nvim_replace_termcodes()|, and true otherwise.
/// @see feedkeys()
-/// @see vim_strsave_escape_csi
-void nvim_feedkeys(String keys, String mode, Boolean escape_csi)
+/// @see vim_strsave_escape_ks
+void nvim_feedkeys(String keys, String mode, Boolean escape_ks)
FUNC_API_SINCE(1)
{
bool remap = true;
@@ -232,10 +242,10 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_csi)
}
char *keys_esc;
- if (escape_csi) {
- // Need to escape K_SPECIAL and CSI before putting the string in the
+ if (escape_ks) {
+ // Need to escape K_SPECIAL before putting the string in the
// typeahead buffer.
- keys_esc = (char *)vim_strsave_escape_csi((char_u *)keys.data);
+ keys_esc = (char *)vim_strsave_escape_ks((char_u *)keys.data);
} else {
keys_esc = keys.data;
}
@@ -245,7 +255,7 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_csi)
typebuf_was_filled = true;
}
- if (escape_csi) {
+ if (escape_ks) {
xfree(keys_esc);
}
@@ -383,7 +393,7 @@ error:
/// @param str String to be converted.
/// @param from_part Legacy Vim parameter. Usually true.
/// @param do_lt Also translate <lt>. Ignored if `special` is false.
-/// @param special Replace |keycodes|, e.g. <CR> becomes a "\n" char.
+/// @param special Replace |keycodes|, e.g. <CR> becomes a "\r" char.
/// @see replace_termcodes
/// @see cpoptions
String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt, Boolean special)
@@ -491,8 +501,12 @@ ArrayOf(String) nvim_get_runtime_file(String name, Boolean all, Error *err)
int flags = DIP_DIRFILE | (all ? DIP_ALL : 0);
- do_in_runtimepath((char_u *)(name.size ? name.data : ""),
- flags, find_runtime_cb, &rv);
+ TRY_WRAP({
+ try_start();
+ do_in_runtimepath((char_u *)(name.size ? name.data : ""),
+ flags, find_runtime_cb, &rv);
+ try_end(err);
+ });
return rv;
}
@@ -540,20 +554,19 @@ void nvim_set_current_dir(String dir, Error *err)
return;
}
- char string[MAXPATHL];
+ char_u string[MAXPATHL];
memcpy(string, dir.data, dir.size);
string[dir.size] = NUL;
try_start();
- if (vim_chdir((char_u *)string)) {
+ if (!changedir_func(string, kCdScopeGlobal)) {
if (!try_end(err)) {
api_set_error(err, kErrorTypeException, "Failed to change directory");
}
return;
}
- post_chdir(kCdScopeGlobal, true);
try_end(err);
}
@@ -596,7 +609,19 @@ void nvim_del_current_line(Error *err)
Object nvim_get_var(String name, Error *err)
FUNC_API_SINCE(1)
{
- return dict_get_value(&globvardict, name, err);
+ dictitem_T *di = tv_dict_find(&globvardict, name.data, (ptrdiff_t)name.size);
+ if (di == NULL) { // try to autoload script
+ if (!script_autoload(name.data, name.size, false) || aborting()) {
+ api_set_error(err, kErrorTypeValidation, "Key not found: %s", name.data);
+ return (Object)OBJECT_INIT;
+ }
+ di = tv_dict_find(&globvardict, name.data, (ptrdiff_t)name.size);
+ }
+ if (di == NULL) {
+ api_set_error(err, kErrorTypeValidation, "Key not found: %s", name.data);
+ return (Object)OBJECT_INIT;
+ }
+ return vim_to_object(&di->di_tv);
}
/// Sets a global (g:) variable.
@@ -662,7 +687,7 @@ Object nvim_get_option(String name, Error *err)
///
/// @param name Option name
/// @param opts Optional parameters
-/// - scope: One of 'global' or 'local'. Analagous to
+/// - scope: One of 'global' or 'local'. Analogous to
/// |:setglobal| and |:setlocal|, respectively.
/// @param[out] err Error details, if any
/// @return Option value
@@ -724,7 +749,7 @@ end:
/// @param name Option name
/// @param value New option value
/// @param opts Optional parameters
-/// - scope: One of 'global' or 'local'. Analagous to
+/// - scope: One of 'global' or 'local'. Analogous to
/// |:setglobal| and |:setlocal|, respectively.
/// @param[out] err Error details, if any
void nvim_set_option_value(String name, Object value, Dict(option) *opts, Error *err)
@@ -1163,7 +1188,7 @@ static void term_close(void *data)
/// Send data to channel `id`. For a job, it writes it to the
/// stdin of the process. For the stdio channel |channel-stdio|,
/// it writes to Nvim's stdout. For an internal terminal instance
-/// (|nvim_open_term()|) it writes directly to terimal output.
+/// (|nvim_open_term()|) it writes directly to terminal output.
/// See |channel-bytes| for more information.
///
/// This function writes raw data, not RPC messages. If the channel
@@ -1538,10 +1563,10 @@ Dictionary nvim_get_mode(void)
/// @param mode Mode short-name ("n", "i", "v", ...)
/// @returns Array of maparg()-like dictionaries describing mappings.
/// The "buffer" key is always zero.
-ArrayOf(Dictionary) nvim_get_keymap(String mode)
+ArrayOf(Dictionary) nvim_get_keymap(uint64_t channel_id, String mode)
FUNC_API_SINCE(3)
{
- return keymap_array(mode, NULL);
+ return keymap_array(mode, NULL, channel_id == LUA_INTERNAL_CALL);
}
/// Sets a global |mapping| for the given mode.
@@ -1561,18 +1586,23 @@ ArrayOf(Dictionary) nvim_get_keymap(String mode)
/// nmap <nowait> <Space><NL> <Nop>
/// </pre>
///
+/// @param channel_id
/// @param mode Mode short-name (map command prefix: "n", "i", "v", "x", …)
/// or "!" for |:map!|, or empty string for |:map|.
/// @param lhs Left-hand-side |{lhs}| of the mapping.
/// @param rhs Right-hand-side |{rhs}| of the mapping.
/// @param opts Optional parameters map. Accepts all |:map-arguments|
-/// as keys excluding |<buffer>| but including |noremap|.
+/// as keys excluding |<buffer>| but including |noremap| and "desc".
+/// "desc" can be used to give a description to keymap.
+/// When called from Lua, also accepts a "callback" key that takes
+/// a Lua function to call when the mapping is executed.
/// Values are Booleans. Unknown key is an error.
/// @param[out] err Error details, if any.
-void nvim_set_keymap(String mode, String lhs, String rhs, Dict(keymap) *opts, Error *err)
+void nvim_set_keymap(uint64_t channel_id, String mode, String lhs, String rhs, Dict(keymap) *opts,
+ Error *err)
FUNC_API_SINCE(6)
{
- modify_keymap(-1, false, mode, lhs, rhs, opts, err);
+ modify_keymap(channel_id, -1, false, mode, lhs, rhs, opts, err);
}
/// Unmaps a global |mapping| for the given mode.
@@ -1580,10 +1610,10 @@ void nvim_set_keymap(String mode, String lhs, String rhs, Dict(keymap) *opts, Er
/// To unmap a buffer-local mapping, use |nvim_buf_del_keymap()|.
///
/// @see |nvim_set_keymap()|
-void nvim_del_keymap(String mode, String lhs, Error *err)
+void nvim_del_keymap(uint64_t channel_id, String mode, String lhs, Error *err)
FUNC_API_SINCE(6)
{
- nvim_buf_del_keymap(-1, mode, lhs, err);
+ nvim_buf_del_keymap(channel_id, -1, mode, lhs, err);
}
/// Gets a map of global (non-buffer-local) Ex commands.
@@ -1741,7 +1771,7 @@ Array nvim_list_chans(void)
/// 1. To perform several requests from an async context atomically, i.e.
/// without interleaving redraws, RPC requests from other clients, or user
/// interactions (however API methods may trigger autocommands or event
-/// processing which have such side-effects, e.g. |:sleep| may wake timers).
+/// processing which have such side effects, e.g. |:sleep| may wake timers).
/// 2. To minimize RPC overhead (roundtrips) of a sequence of many requests.
///
/// @param channel_id
@@ -1927,7 +1957,7 @@ Dictionary nvim__stats(void)
Dictionary rv = ARRAY_DICT_INIT;
PUT(rv, "fsync", INTEGER_OBJ(g_stats.fsync));
PUT(rv, "redraw", INTEGER_OBJ(g_stats.redraw));
- PUT(rv, "lua_refcount", INTEGER_OBJ(nlua_refcount));
+ PUT(rv, "lua_refcount", INTEGER_OBJ(nlua_get_global_ref_count()));
return rv;
}
@@ -2101,7 +2131,7 @@ void nvim__screenshot(String path)
}
-/// Deletes a uppercase/file named mark. See |mark-motions|.
+/// Deletes an uppercase/file named mark. See |mark-motions|.
///
/// @note fails with error if a lowercase or buffer local named mark is used.
/// @param name Mark name
@@ -2231,7 +2261,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
Dictionary result = ARRAY_DICT_INIT;
int maxwidth;
- char fillchar = 0;
+ int fillchar = 0;
Window window = 0;
bool use_tabline = false;
bool highlights = false;
@@ -2246,12 +2276,12 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
}
if (HAS_KEY(opts->fillchar)) {
- if (opts->fillchar.type != kObjectTypeString || opts->fillchar.data.string.size > 1) {
- api_set_error(err, kErrorTypeValidation, "fillchar must be an ASCII character");
+ if (opts->fillchar.type != kObjectTypeString || opts->fillchar.data.string.size == 0
+ || char2cells(fillchar = utf_ptr2char((char_u *)opts->fillchar.data.string.data)) != 1
+ || (size_t)utf_char2len(fillchar) != opts->fillchar.data.string.size) {
+ api_set_error(err, kErrorTypeValidation, "fillchar must be a single-width character");
return result;
}
-
- fillchar = opts->fillchar.data.string.data[0];
}
if (HAS_KEY(opts->highlights)) {
@@ -2278,11 +2308,16 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
fillchar = ' ';
} else {
wp = find_window_by_handle(window, err);
+
+ if (wp == NULL) {
+ api_set_error(err, kErrorTypeException, "unknown winid %d", window);
+ return result;
+ }
ewp = wp;
if (fillchar == 0) {
int attr;
- fillchar = (char)fillchar_status(&attr, wp);
+ fillchar = fillchar_status(&attr, wp);
}
}
@@ -2310,7 +2345,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
sizeof(buf),
(char_u *)str.data,
false,
- (char_u)fillchar,
+ fillchar,
maxwidth,
hltab_ptr,
NULL);
@@ -2363,3 +2398,55 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
return result;
}
+
+/// Create a new user command |user-commands|
+///
+/// {name} is the name of the new command. The name must begin with an uppercase letter.
+///
+/// {command} is the replacement text or Lua function to execute.
+///
+/// Example:
+/// <pre>
+/// :call nvim_add_user_command('SayHello', 'echo "Hello world!"', {})
+/// :SayHello
+/// Hello world!
+/// </pre>
+///
+/// @param name Name of the new user command. Must begin with an uppercase letter.
+/// @param command Replacement command to execute when this user command is executed. When called
+/// from Lua, the command can also be a Lua function. The function is called with a
+/// single table argument that contains the following keys:
+/// - args: (string) The args passed to the command, if any |<args>|
+/// - fargs: (table) The args split by unescaped whitespace (when more than one
+/// argument is allowed), if any |<f-args>|
+/// - bang: (boolean) "true" if the command was executed with a ! modifier |<bang>|
+/// - line1: (number) The starting line of the command range |<line1>|
+/// - line2: (number) The final line of the command range |<line2>|
+/// - range: (number) The number of items in the command range: 0, 1, or 2 |<range>|
+/// - count: (number) Any count supplied |<count>|
+/// - reg: (string) The optional register, if specified |<reg>|
+/// - mods: (string) Command modifiers, if any |<mods>|
+/// @param opts Optional command attributes. See |command-attributes| for more details. To use
+/// boolean attributes (such as |:command-bang| or |:command-bar|) set the value to
+/// "true". In addition to the string options listed in |:command-complete|, the
+/// "complete" key also accepts a Lua function which works like the "customlist"
+/// completion mode |:command-completion-customlist|. Additional parameters:
+/// - desc: (string) Used for listing the command when a Lua function is used for
+/// {command}.
+/// - force: (boolean, default true) Override any previous definition.
+/// @param[out] err Error details, if any.
+void nvim_add_user_command(String name, Object command, Dict(user_command) *opts, Error *err)
+ FUNC_API_SINCE(9)
+{
+ add_user_command(name, command, opts, 0, err);
+}
+
+/// Delete a user-defined command.
+///
+/// @param name Name of the command to delete.
+/// @param[out] err Error details, if any.
+void nvim_del_user_command(String name, Error *err)
+ FUNC_API_SINCE(9)
+{
+ nvim_buf_del_user_command(-1, name, err);
+}
diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c
index 640144b234..4123674fe7 100644
--- a/src/nvim/api/vimscript.c
+++ b/src/nvim/api/vimscript.c
@@ -37,7 +37,7 @@
/// @param[out] err Error details (Vim error), if any
/// @return Output (non-error, non-shell |:!|) if `output` is true,
/// else empty string.
-String nvim_exec(String src, Boolean output, Error *err)
+String nvim_exec(uint64_t channel_id, String src, Boolean output, Error *err)
FUNC_API_SINCE(7)
{
const int save_msg_silent = msg_silent;
@@ -52,11 +52,16 @@ String nvim_exec(String src, Boolean output, Error *err)
if (output) {
msg_silent++;
}
+
+ const sctx_T save_current_sctx = api_set_sctx(channel_id);
+
do_source_str(src.data, "nvim_exec()");
if (output) {
capture_ga = save_capture_ga;
msg_silent = save_msg_silent;
}
+
+ current_sctx = save_current_sctx;
try_end(err);
if (ERROR_SET(err)) {
diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c
index 6e68c057dc..9c473ff724 100644
--- a/src/nvim/api/window.c
+++ b/src/nvim/api/window.c
@@ -39,7 +39,7 @@ Buffer nvim_win_get_buf(Window window, Error *err)
return win->w_buffer->handle;
}
-/// Sets the current buffer in a window, without side-effects
+/// Sets the current buffer in a window, without side effects
///
/// @param window Window handle, or 0 for current window
/// @param buffer Buffer handle
@@ -71,6 +71,7 @@ ArrayOf(Integer, 2) nvim_win_get_cursor(Window window, Error *err)
}
/// Sets the (1,0)-indexed cursor position in the window. |api-indexing|
+/// Unlike |win_execute()| this scrolls the window.
///
/// @param window Window handle, or 0 for current window
/// @param pos (row, col) tuple representing the new position
@@ -118,6 +119,8 @@ void nvim_win_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err)
update_topline_win(win);
redraw_later(win, VALID);
+ redraw_for_cursorline(win);
+ win->w_redr_status = true;
}
/// Gets the window height
@@ -395,7 +398,7 @@ void nvim_win_hide(Window window, Error *err)
TryState tstate;
try_enter(&tstate);
if (tabpage == curtab) {
- win_close(win, false);
+ win_close(win, false, false);
} else {
win_close_othertab(win, false, tabpage);
}
@@ -455,17 +458,12 @@ Object nvim_win_call(Window window, LuaRef fun, Error *err)
}
tabpage_T *tabpage = win_find_tabpage(win);
- win_T *save_curwin;
- tabpage_T *save_curtab;
-
try_start();
Object res = OBJECT_INIT;
- if (switch_win_noblock(&save_curwin, &save_curtab, win, tabpage, true) ==
- OK) {
+ WIN_EXECUTE(win, tabpage, {
Array args = ARRAY_DICT_INIT;
res = nlua_call_ref(fun, NULL, args, true, err);
- }
- restore_win_noblock(save_curwin, save_curtab, true);
+ });
try_end(err);
return res;
}
diff --git a/src/nvim/aucmd.c b/src/nvim/aucmd.c
deleted file mode 100644
index d7f73fa4a1..0000000000
--- a/src/nvim/aucmd.c
+++ /dev/null
@@ -1,123 +0,0 @@
-// 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
-
-#include "nvim/aucmd.h"
-#include "nvim/buffer.h"
-#include "nvim/eval.h"
-#include "nvim/ex_docmd.h"
-#include "nvim/ex_getln.h"
-#include "nvim/fileio.h"
-#include "nvim/main.h"
-#include "nvim/os/os.h"
-#include "nvim/ui.h"
-#include "nvim/vim.h"
-
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "aucmd.c.generated.h"
-#endif
-
-void do_autocmd_uienter(uint64_t chanid, bool attached)
-{
- static bool recursive = false;
-
- if (recursive) {
- return; // disallow recursion
- }
- recursive = true;
-
- save_v_event_T save_v_event;
- dict_T *dict = get_v_event(&save_v_event);
- assert(chanid < VARNUMBER_MAX);
- tv_dict_add_nr(dict, S_LEN("chan"), (varnumber_T)chanid);
- tv_dict_set_keys_readonly(dict);
- apply_autocmds(attached ? EVENT_UIENTER : EVENT_UILEAVE,
- NULL, NULL, false, curbuf);
- restore_v_event(dict, &save_v_event);
-
- recursive = false;
-}
-
-void init_default_autocmds(void)
-{
- // open terminals when opening files that start with term://
-#define PROTO "term://"
- do_cmdline_cmd("augroup nvim_terminal");
- do_cmdline_cmd("autocmd BufReadCmd " PROTO "* ++nested "
- "if !exists('b:term_title')|call termopen("
- // Capture the command string
- "matchstr(expand(\"<amatch>\"), "
- "'\\c\\m" PROTO "\\%(.\\{-}//\\%(\\d\\+:\\)\\?\\)\\?\\zs.*'), "
- // capture the working directory
- "{'cwd': expand(get(matchlist(expand(\"<amatch>\"), "
- "'\\c\\m" PROTO "\\(.\\{-}\\)//'), 1, ''))})"
- "|endif");
- do_cmdline_cmd("augroup END");
-#undef PROTO
-
- // limit syntax synchronization in the command window
- do_cmdline_cmd("augroup nvim_cmdwin");
- do_cmdline_cmd("autocmd! CmdwinEnter [:>] syntax sync minlines=1 maxlines=1");
- do_cmdline_cmd("augroup END");
-}
-
-static void focusgained_event(void **argv)
-{
- bool *gainedp = argv[0];
- do_autocmd_focusgained(*gainedp);
- xfree(gainedp);
-}
-void aucmd_schedule_focusgained(bool gained)
-{
- bool *gainedp = xmalloc(sizeof(*gainedp));
- *gainedp = gained;
- loop_schedule_deferred(&main_loop,
- event_create(focusgained_event, 1, gainedp));
-}
-
-static void do_autocmd_focusgained(bool gained)
-{
- static bool recursive = false;
- static Timestamp last_time = (time_t)0;
- bool need_redraw = false;
-
- if (recursive) {
- return; // disallow recursion
- }
- recursive = true;
- need_redraw |= apply_autocmds((gained ? EVENT_FOCUSGAINED : EVENT_FOCUSLOST),
- NULL, NULL, false, curbuf);
-
- // When activated: Check if any file was modified outside of Vim.
- // Only do this when not done within the last two seconds as:
- // 1. Some filesystems have modification time granularity in seconds. Fat32
- // has a granularity of 2 seconds.
- // 2. We could get multiple notifications in a row.
- if (gained && last_time + (Timestamp)2000 < os_now()) {
- need_redraw = check_timestamps(true);
- last_time = os_now();
- }
-
- if (need_redraw) {
- // Something was executed, make sure the cursor is put back where it
- // belongs.
- need_wait_return = false;
-
- if (State & CMDLINE) {
- redrawcmdline();
- } else if ((State & NORMAL) || (State & INSERT)) {
- if (must_redraw != 0) {
- update_screen(0);
- }
-
- setcursor();
- }
-
- ui_flush();
- }
-
- if (need_maketitle) {
- maketitle();
- }
-
- recursive = false;
-}
diff --git a/src/nvim/aucmd.h b/src/nvim/aucmd.h
deleted file mode 100644
index 9a4dd79a78..0000000000
--- a/src/nvim/aucmd.h
+++ /dev/null
@@ -1,11 +0,0 @@
-#ifndef NVIM_AUCMD_H
-#define NVIM_AUCMD_H
-
-#include <stdint.h>
-
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "aucmd.h.generated.h"
-#endif
-
-#endif // NVIM_AUCMD_H
-
diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua
index 8fe623fc96..518d0b52b2 100644
--- a/src/nvim/auevents.lua
+++ b/src/nvim/auevents.lua
@@ -40,6 +40,7 @@ return {
'DiagnosticChanged', -- diagnostics in a buffer were modified
'DiffUpdated', -- diffs have been updated
'DirChanged', -- directory changed
+ 'DirChangedPre', -- directory is going to change
'EncodingChanged', -- after changing the 'encoding' option
'ExitPre', -- before exiting
'FileAppendCmd', -- append to a file using command
@@ -132,18 +133,14 @@ return {
nvim_specific = {
BufModifiedSet=true,
DiagnosticChanged=true,
- DirChanged=true,
RecordingEnter=true,
RecordingLeave=true,
Signal=true,
- TabClosed=true,
- TabNew=true,
TabNewEntered=true,
TermClose=true,
TermOpen=true,
UIEnter=true,
UILeave=true,
- WinClosed=true,
WinScrolled=true,
},
}
diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c
index 463bd5e0e6..66222f6a6a 100644
--- a/src/nvim/autocmd.c
+++ b/src/nvim/autocmd.c
@@ -2,7 +2,9 @@
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
// autocmd.c: Autocommand related functions
+#include <signal.h>
+#include "lauxlib.h"
#include "nvim/api/private/helpers.h"
#include "nvim/ascii.h"
#include "nvim/autocmd.h"
@@ -13,8 +15,11 @@
#include "nvim/eval.h"
#include "nvim/eval/userfunc.h"
#include "nvim/ex_docmd.h"
+#include "nvim/ex_getln.h"
#include "nvim/fileio.h"
#include "nvim/getchar.h"
+#include "nvim/lua/executor.h"
+#include "nvim/map.h"
#include "nvim/option.h"
#include "nvim/os/input.h"
#include "nvim/regexp.h"
@@ -28,6 +33,14 @@
# include "autocmd.c.generated.h"
#endif
+// Naming Conventions:
+// - general autocmd behavior start with au_
+// - AutoCmd start with aucmd_
+// - Autocmd.exec stat with aucmd_exec
+// - AutoPat start with aupat_
+// - Groups start with augroup_
+// - Events start with event_
+
//
// The autocommands are stored in a list for each event.
// Autocommands for the same pattern, that are consecutive, are joined
@@ -67,21 +80,20 @@
// Code for automatic commands.
static AutoPatCmd *active_apc_list = NULL; // stack of active autocommands
-/// List of autocmd group names
-static garray_T augroups = { 0, 0, sizeof(char_u *), 10, NULL };
-#define AUGROUP_NAME(i) (((char **)augroups.ga_data)[i])
-#define BUFLOCAL_PAT_LEN 25
+// ID for associating autocmds created via nvim_create_autocmd
+// Used to delete autocmds from nvim_del_autocmd
+static int next_augroup_id = 1;
// use get_deleted_augroup() to get this
static const char *deleted_augroup = NULL;
-// The ID of the current group. Group 0 is the default one.
+// The ID of the current group.
static int current_augroup = AUGROUP_DEFAULT;
-static int au_need_clean = false; // need to delete marked patterns
+// Whether we need to delete marked patterns.
+// While deleting autocmds, they aren't actually remover, just marked.
+static int au_need_clean = false;
-static event_T last_event;
-static int last_group;
static int autocmd_blocked = 0; // block all autocmds
static bool autocmd_nested = false;
@@ -89,6 +101,22 @@ static bool autocmd_include_groups = false;
static char_u *old_termresponse = NULL;
+/// Iterates over all the AutoPats for a particular event
+#define FOR_ALL_AUPATS_IN_EVENT(event, ap) \
+ for (AutoPat *ap = first_autopat[event]; ap != NULL; ap = ap->next) // NOLINT
+
+// Map of autocmd group names.
+// name -> ID
+static Map(String, int) augroup_map = MAP_INIT;
+
+static void augroup_map_del(char *name)
+{
+ String key = map_key(String, int)(&augroup_map, cstr_as_string(name));
+ map_del(String, int)(&augroup_map, key);
+ api_free_string(key);
+}
+
+
static inline const char *get_deleted_augroup(void) FUNC_ATTR_ALWAYS_INLINE
{
if (deleted_augroup == NULL) {
@@ -98,7 +126,7 @@ static inline const char *get_deleted_augroup(void) FUNC_ATTR_ALWAYS_INLINE
}
// Show the autocommands for one AutoPat.
-static void show_autocmd(AutoPat *ap, event_T event)
+static void aupat_show(AutoPat *ap)
{
AutoCmd *ac;
@@ -107,39 +135,21 @@ static void show_autocmd(AutoPat *ap, event_T event)
if (got_int) {
return;
}
+
// pattern has been removed
if (ap->pat == NULL) {
return;
}
- msg_putchar('\n');
- if (got_int) {
- return;
- }
- if (event != last_event || ap->group != last_group) {
- if (ap->group != AUGROUP_DEFAULT) {
- if (AUGROUP_NAME(ap->group) == NULL) {
- msg_puts_attr(get_deleted_augroup(), HL_ATTR(HLF_E));
- } else {
- msg_puts_attr(AUGROUP_NAME(ap->group), HL_ATTR(HLF_T));
- }
- msg_puts(" ");
- }
- msg_puts_attr(event_nr2name(event), HL_ATTR(HLF_T));
- last_event = event;
- last_group = ap->group;
- msg_putchar('\n');
- if (got_int) {
- return;
- }
- }
msg_col = 4;
msg_outtrans(ap->pat);
for (ac = ap->cmds; ac != NULL; ac = ac->next) {
- if (ac->cmd == NULL) { // skip removed commands
+ // skip removed commands
+ if (aucmd_exec_is_deleted(ac->exec)) {
continue;
}
+
if (msg_col >= 14) {
msg_putchar('\n');
}
@@ -147,7 +157,7 @@ static void show_autocmd(AutoPat *ap, event_T event)
if (got_int) {
return;
}
- msg_outtrans(ac->cmd);
+ msg_outtrans((char_u *)aucmd_exec_to_string(ac, ac->exec));
if (p_verbose > 0) {
last_set_msg(ac->script_ctx);
}
@@ -163,27 +173,96 @@ static void show_autocmd(AutoPat *ap, event_T event)
}
}
+static void au_show_for_all_events(int group)
+{
+ FOR_ALL_AUEVENTS(event) {
+ au_show_for_event(group, event);
+ }
+}
+
+static void au_show_for_event(int group, event_T event)
+{
+ // Return early if there are no autocmds for this event
+ if (au_event_is_empty(event)) {
+ return;
+ }
+
+ int previous_group = AUGROUP_ERROR;
+ FOR_ALL_AUPATS_IN_EVENT(event, ap) {
+ if (group != AUGROUP_ALL && group != ap->group) {
+ continue;
+ }
+
+ char *name = augroup_name(ap->group);
+
+ msg_putchar('\n');
+ // When switching groups, we need to show the new group information.
+ if (ap->group != previous_group) {
+ // show the group name, if it's not the default group
+ if (ap->group != AUGROUP_DEFAULT) {
+ if (name == NULL) {
+ msg_puts_attr(get_deleted_augroup(), HL_ATTR(HLF_E));
+ } else {
+ msg_puts_attr(name, HL_ATTR(HLF_T));
+ }
+ msg_puts(" ");
+ }
+
+ // show the event name
+ msg_puts_attr(event_nr2name(event), HL_ATTR(HLF_T));
+ msg_putchar('\n');
+ }
+
+ if (got_int) {
+ return;
+ }
+
+ aupat_show(ap);
+
+ previous_group = ap->group;
+ }
+}
+
// Mark an autocommand handler for deletion.
-static void au_remove_pat(AutoPat *ap)
+static void aupat_del(AutoPat *ap)
{
XFREE_CLEAR(ap->pat);
ap->buflocal_nr = -1;
au_need_clean = true;
}
+void aupat_del_for_event_and_group(event_T event, int group)
+{
+ FOR_ALL_AUPATS_IN_EVENT(event, ap) {
+ if (ap->group == group) {
+ aupat_del(ap);
+ }
+ }
+
+ au_need_clean = true;
+ au_cleanup(); // may really delete removed patterns/commands now
+}
+
// Mark all commands for a pattern for deletion.
-static void au_remove_cmds(AutoPat *ap)
+static void aupat_remove_cmds(AutoPat *ap)
{
for (AutoCmd *ac = ap->cmds; ac != NULL; ac = ac->next) {
- XFREE_CLEAR(ac->cmd);
+ aucmd_exec_free(&ac->exec);
+
+ if (ac->desc != NULL) {
+ XFREE_CLEAR(ac->desc);
+ }
}
au_need_clean = true;
}
// Delete one command from an autocmd pattern.
-static void au_del_cmd(AutoCmd *ac)
+static void aucmd_del(AutoCmd *ac)
{
- XFREE_CLEAR(ac->cmd);
+ aucmd_exec_free(&ac->exec);
+ if (ac->desc != NULL) {
+ XFREE_CLEAR(ac->desc);
+ }
au_need_clean = true;
}
@@ -191,16 +270,15 @@ static void au_del_cmd(AutoCmd *ac)
/// This is only done when not executing autocommands.
static void au_cleanup(void)
{
- AutoPat *ap, **prev_ap;
- event_T event;
+ AutoPat *ap;
+ AutoPat **prev_ap;
if (autocmd_busy || !au_need_clean) {
return;
}
// Loop over all events.
- for (event = (event_T)0; (int)event < NUM_EVENTS;
- event = (event_T)((int)event + 1)) {
+ FOR_ALL_AUEVENTS(event) {
// Loop over all autocommand patterns.
prev_ap = &(first_autopat[(int)event]);
for (ap = *prev_ap; ap != NULL; ap = *prev_ap) {
@@ -211,9 +289,13 @@ static void au_cleanup(void)
for (AutoCmd *ac = *prev_ac; ac != NULL; ac = *prev_ac) {
// Remove the command if the pattern is to be deleted or when
// the command has been marked for deletion.
- if (ap->pat == NULL || ac->cmd == NULL) {
+ if (ap->pat == NULL || aucmd_exec_is_deleted(ac->exec)) {
*prev_ac = ac->next;
- xfree(ac->cmd);
+ aucmd_exec_free(&ac->exec);
+ if (ac->desc != NULL) {
+ XFREE_CLEAR(ac->desc);
+ }
+
xfree(ac);
} else {
has_cmd = true;
@@ -224,7 +306,7 @@ static void au_cleanup(void)
if (ap->pat != NULL && !has_cmd) {
// Pattern was not marked for deletion, but all of its commands were.
// So mark the pattern for deletion.
- au_remove_pat(ap);
+ aupat_del(ap);
}
// Remove the pattern if it has been marked for deletion.
@@ -250,12 +332,17 @@ static void au_cleanup(void)
au_need_clean = false;
}
+
+// Get the first AutoPat for a particular event.
+AutoPat *au_get_autopat_for_event(event_T event)
+{
+ return first_autopat[(int)event];
+}
+
// Called when buffer is freed, to remove/invalidate related buffer-local
// autocmds.
void aubuflocal_remove(buf_T *buf)
{
- AutoPat *ap;
- event_T event;
AutoPatCmd *apc;
// invalidate currently executing autocommands
@@ -266,12 +353,11 @@ void aubuflocal_remove(buf_T *buf)
}
// invalidate buflocals looping through events
- for (event = (event_T)0; (int)event < NUM_EVENTS;
- event = (event_T)((int)event + 1)) {
- // loop over all autocommand patterns
- for (ap = first_autopat[(int)event]; ap != NULL; ap = ap->next) {
+ FOR_ALL_AUEVENTS(event) {
+ FOR_ALL_AUPATS_IN_EVENT(event, ap) {
if (ap->buflocal_nr == buf->b_fnum) {
- au_remove_pat(ap);
+ aupat_del(ap);
+
if (p_verbose >= 6) {
verbose_enter();
smsg(_("auto-removing autocommand: %s <buffer=%d>"),
@@ -284,60 +370,71 @@ void aubuflocal_remove(buf_T *buf)
au_cleanup();
}
-// Add an autocmd group name.
-// Return its ID. Returns AUGROUP_ERROR (< 0) for error.
-static int au_new_group(char_u *name)
+// Add an autocmd group name or return existing group matching name.
+// Return its ID.
+int augroup_add(char *name)
{
- int i = au_find_group(name);
- if (i == AUGROUP_ERROR) { // the group doesn't exist yet, add it.
- // First try using a free entry.
- for (i = 0; i < augroups.ga_len; i++) {
- if (AUGROUP_NAME(i) == NULL) {
- break;
- }
- }
- if (i == augroups.ga_len) {
- ga_grow(&augroups, 1);
- }
+ assert(STRICMP(name, "end") != 0);
- AUGROUP_NAME(i) = xstrdup((char *)name);
- if (i == augroups.ga_len) {
- augroups.ga_len++;
- }
+ int existing_id = augroup_find(name);
+ if (existing_id > 0) {
+ assert(existing_id != AUGROUP_DELETED);
+ return existing_id;
}
- return i;
+ if (existing_id == AUGROUP_DELETED) {
+ augroup_map_del(name);
+ }
+
+ int next_id = next_augroup_id++;
+ String name_copy = cstr_to_string(name);
+ map_put(String, int)(&augroup_map, name_copy, next_id);
+
+ return next_id;
}
-static void au_del_group(char_u *name)
+/// Delete the augroup that matches name.
+/// @param stupid_legacy_mode bool: This paremeter determines whether to run the augroup
+/// deletion in the same fashion as `:augroup! {name}` where if there are any remaining
+/// autocmds left in the augroup, it will change the name of the augroup to `--- DELETED ---`
+/// but leave the autocmds existing. These are _separate_ augroups, so if you do this for
+/// multiple augroups, you will have a bunch of `--- DELETED ---` augroups at the same time.
+/// There is no way, as far as I could tell, how to actually delete them at this point as a user
+///
+/// I did not consider this good behavior, so now when NOT in stupid_legacy_mode, we actually
+/// delete these groups and their commands, like you would expect (and don't leave hanging
+/// `--- DELETED ---` groups around)
+void augroup_del(char *name, bool stupid_legacy_mode)
{
- int i = au_find_group(name);
+ int i = augroup_find(name);
if (i == AUGROUP_ERROR) { // the group doesn't exist
semsg(_("E367: No such group: \"%s\""), name);
} else if (i == current_augroup) {
emsg(_("E936: Cannot delete the current group"));
} else {
- event_T event;
- AutoPat *ap;
- int in_use = false;
-
- for (event = (event_T)0; (int)event < NUM_EVENTS;
- event = (event_T)((int)event + 1)) {
- for (ap = first_autopat[(int)event]; ap != NULL; ap = ap->next) {
- if (ap->group == i && ap->pat != NULL) {
- give_warning((char_u *)_("W19: Deleting augroup that is still in use"), true);
- in_use = true;
- event = NUM_EVENTS;
- break;
+ if (stupid_legacy_mode) {
+ FOR_ALL_AUEVENTS(event) {
+ FOR_ALL_AUPATS_IN_EVENT(event, ap) {
+ if (ap->group == i && ap->pat != NULL) {
+ give_warning((char_u *)_("W19: Deleting augroup that is still in use"), true);
+ map_put(String, int)(&augroup_map, cstr_as_string(name), AUGROUP_DELETED);
+ return;
+ }
}
}
- }
- xfree(AUGROUP_NAME(i));
- if (in_use) {
- AUGROUP_NAME(i) = (char *)get_deleted_augroup();
} else {
- AUGROUP_NAME(i) = NULL;
+ FOR_ALL_AUEVENTS(event) {
+ FOR_ALL_AUPATS_IN_EVENT(event, ap) {
+ if (ap->group == i) {
+ aupat_del(ap);
+ }
+ }
+ }
}
+
+ // Remove the group because it's not currently in use.
+ augroup_map_del(name);
+ au_cleanup();
}
}
@@ -346,25 +443,70 @@ static void au_del_group(char_u *name)
/// @param name augroup name
///
/// @return the ID or AUGROUP_ERROR (< 0) for error.
-static int au_find_group(const char_u *name)
+int augroup_find(const char *name)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
- for (int i = 0; i < augroups.ga_len; i++) {
- if (AUGROUP_NAME(i) != NULL && AUGROUP_NAME(i) != get_deleted_augroup()
- && STRCMP(AUGROUP_NAME(i), name) == 0) {
- return i;
- }
+ int existing_id = map_get(String, int)(&augroup_map, cstr_as_string((char *)name));
+ if (existing_id == AUGROUP_DELETED) {
+ return existing_id;
}
+
+ if (existing_id > 0) {
+ return existing_id;
+ }
+
return AUGROUP_ERROR;
}
+/// Gets the name for a particular group.
+char *augroup_name(int group)
+{
+ assert(group != 0);
+
+ if (group == AUGROUP_DELETED) {
+ return (char *)get_deleted_augroup();
+ }
+
+ if (group == AUGROUP_ALL) {
+ group = current_augroup;
+ }
+
+ // next_augroup_id is the "source of truth" about what autocmds have existed
+ //
+ // The map_size is not the source of truth because groups can be removed from
+ // the map. When this happens, the map size is reduced. That's why this function
+ // relies on next_augroup_id instead.
+
+ // "END" is always considered the last augroup ID.
+ // Used for expand_get_event_name and expand_get_augroup_name
+ if (group == next_augroup_id) {
+ return "END";
+ }
+
+ // If it's larger than the largest group, then it doesn't have a name
+ if (group > next_augroup_id) {
+ return NULL;
+ }
+
+ String key;
+ int value;
+ map_foreach(&augroup_map, key, value, {
+ if (value == group) {
+ return key.data;
+ }
+ });
+
+ // If it's not in the map anymore, then it must have been deleted.
+ return (char *)get_deleted_augroup();
+}
+
/// Return true if augroup "name" exists.
///
/// @param name augroup name
-bool au_has_group(const char_u *name)
+bool augroup_exists(const char *name)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
- return au_find_group(name) != AUGROUP_ERROR;
+ return augroup_find(name) > 0;
}
/// ":augroup {name}".
@@ -374,23 +516,27 @@ void do_augroup(char_u *arg, int del_group)
if (*arg == NUL) {
emsg(_(e_argreq));
} else {
- au_del_group(arg);
+ augroup_del((char *)arg, true);
}
} else if (STRICMP(arg, "end") == 0) { // ":aug end": back to group 0
current_augroup = AUGROUP_DEFAULT;
} else if (*arg) { // ":aug xxx": switch to group xxx
- int i = au_new_group(arg);
- if (i != AUGROUP_ERROR) {
- current_augroup = i;
- }
+ current_augroup = augroup_add((char *)arg);
} else { // ":aug": list the group names
msg_start();
- for (int i = 0; i < augroups.ga_len; i++) {
- if (AUGROUP_NAME(i) != NULL) {
- msg_puts(AUGROUP_NAME(i));
- msg_puts(" ");
+
+ String name;
+ int value;
+ map_foreach(&augroup_map, name, value, {
+ if (value > 0) {
+ msg_puts(name.data);
+ } else {
+ msg_puts(augroup_name(value));
}
- }
+
+ msg_puts(" ");
+ });
+
msg_clr_eos();
msg_end();
}
@@ -399,25 +545,30 @@ void do_augroup(char_u *arg, int del_group)
#if defined(EXITFREE)
void free_all_autocmds(void)
{
- for (current_augroup = -1; current_augroup < augroups.ga_len;
- current_augroup++) {
- do_autocmd((char_u *)"", true);
- }
-
- for (int i = 0; i < augroups.ga_len; i++) {
- char *const s = ((char **)(augroups.ga_data))[i];
- if ((const char *)s != get_deleted_augroup()) {
- xfree(s);
+ FOR_ALL_AUEVENTS(event) {
+ FOR_ALL_AUPATS_IN_EVENT(event, ap) {
+ aupat_del(ap);
}
}
- ga_clear(&augroups);
+
+ au_need_clean = true;
+ au_cleanup(); // may really delete removed patterns/commands now
+
+ // Delete the augroup_map, including free the data
+ String name;
+ int id;
+ map_foreach(&augroup_map, name, id, {
+ (void)id;
+ api_free_string(name);
+ })
+ map_destroy(String, int)(&augroup_map);
}
#endif
// Return the event number for event name "start".
// Return NUM_EVENTS if the event name was not found.
// Return a pointer to the next event name in "end".
-static event_T event_name2nr(const char_u *start, char_u **end)
+event_T event_name2nr(const char_u *start, char_u **end)
{
const char_u *p;
int i;
@@ -447,7 +598,7 @@ static event_T event_name2nr(const char_u *start, char_u **end)
/// @param[in] event Event to return name for.
///
/// @return Event name, static string. Returns "Unknown" for unknown events.
-static const char *event_nr2name(event_T event)
+const char *event_nr2name(event_T event)
FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_CONST
{
int i;
@@ -460,33 +611,6 @@ static const char *event_nr2name(event_T event)
return "Unknown";
}
-/// Scan over the events. "*" stands for all events.
-/// true when group name was found
-static char_u *find_end_event(char_u *arg, int have_group)
-{
- char_u *pat;
- char_u *p;
-
- if (*arg == '*') {
- if (arg[1] && !ascii_iswhite(arg[1])) {
- semsg(_("E215: Illegal character after *: %s"), arg);
- return NULL;
- }
- pat = arg + 1;
- } else {
- for (pat = arg; *pat && *pat != '|' && !ascii_iswhite(*pat); pat = p) {
- if ((int)event_name2nr(pat, &p) >= NUM_EVENTS) {
- if (have_group) {
- semsg(_("E216: No such event: %s"), pat);
- } else {
- semsg(_("E216: No such group or event: %s"), pat);
- }
- return NULL;
- }
- }
- }
- return pat;
-}
/// Return true if "event" is included in 'eventignore'.
///
@@ -595,7 +719,7 @@ void do_autocmd(char_u *arg_in, int forceit)
char_u *envpat = NULL;
char_u *cmd;
int need_free = false;
- int nested = false;
+ bool nested = false;
bool once = false;
int group;
@@ -604,12 +728,12 @@ void do_autocmd(char_u *arg_in, int forceit)
group = AUGROUP_ALL; // no argument, use all groups
} else {
// Check for a legal group name. If not, use AUGROUP_ALL.
- group = au_get_grouparg(&arg);
+ group = arg_augroup_get(&arg);
}
// Scan over the events.
// If we find an illegal name, return here, don't do anything.
- pat = find_end_event(arg, group != AUGROUP_ALL);
+ pat = arg_event_skip(arg, group != AUGROUP_ALL);
if (pat == NULL) {
return;
}
@@ -646,37 +770,22 @@ void do_autocmd(char_u *arg_in, int forceit)
}
cmd = skipwhite(cmd);
+
+ bool invalid_flags = false;
for (size_t i = 0; i < 2; i++) {
if (*cmd != NUL) {
- // Check for "++once" flag.
- if (STRNCMP(cmd, "++once", 6) == 0 && ascii_iswhite(cmd[6])) {
- if (once) {
- semsg(_(e_duparg2), "++once");
- }
- once = true;
- cmd = skipwhite(cmd + 6);
- }
+ invalid_flags |= arg_autocmd_flag_get(&once, &cmd, "++once", 6);
+ invalid_flags |= arg_autocmd_flag_get(&nested, &cmd, "++nested", 8);
- // Check for "++nested" flag.
- if ((STRNCMP(cmd, "++nested", 8) == 0 && ascii_iswhite(cmd[8]))) {
- if (nested) {
- semsg(_(e_duparg2), "++nested");
- }
- nested = true;
- cmd = skipwhite(cmd + 8);
- }
-
- // Check for the old (deprecated) "nested" flag.
- if (STRNCMP(cmd, "nested", 6) == 0 && ascii_iswhite(cmd[6])) {
- if (nested) {
- semsg(_(e_duparg2), "nested");
- }
- nested = true;
- cmd = skipwhite(cmd + 6);
- }
+ // Check the deprecated "nested" flag.
+ invalid_flags |= arg_autocmd_flag_get(&nested, &cmd, "nested", 6);
}
}
+ if (invalid_flags) {
+ return;
+ }
+
// Find the start of the commands.
// Expand <sfile> in it.
if (*cmd != NUL) {
@@ -688,36 +797,37 @@ void do_autocmd(char_u *arg_in, int forceit)
}
}
+ bool is_showing = !forceit && *cmd == NUL;
+
// Print header when showing autocommands.
- if (!forceit && *cmd == NUL) {
+ if (is_showing) {
// Highlight title
msg_puts_title(_("\n--- Autocommands ---"));
- }
- // Loop over the events.
- last_event = (event_T)-1; // for listing the event name
- last_group = AUGROUP_ERROR; // for listing the group name
- if (*arg == '*' || *arg == NUL || *arg == '|') {
- if (!forceit && *cmd != NUL) {
- emsg(_(e_cannot_define_autocommands_for_all_events));
+ if (*arg == '*' || *arg == '|' || *arg == NUL) {
+ au_show_for_all_events(group);
+ } else {
+ event_T event = event_name2nr(arg, &arg);
+ assert(event < NUM_EVENTS);
+ au_show_for_event(group, event);
+ }
+ } else {
+ if (*arg == '*' || *arg == NUL || *arg == '|') {
+ if (!forceit && *cmd != NUL) {
+ emsg(_(e_cannot_define_autocommands_for_all_events));
+ } else {
+ do_all_autocmd_events(pat, once, nested, cmd, forceit, group);
+ }
} else {
- for (event_T event = (event_T)0; event < NUM_EVENTS;
- event = (event_T)(event + 1)) {
+ while (*arg && *arg != '|' && !ascii_iswhite(*arg)) {
+ event_T event = event_name2nr(arg, &arg);
+ assert(event < NUM_EVENTS);
if (do_autocmd_event(event, pat, once, nested, cmd, forceit, group)
== FAIL) {
break;
}
}
}
- } else {
- while (*arg && *arg != '|' && !ascii_iswhite(*arg)) {
- event_T event = event_name2nr(arg, &arg);
- assert(event < NUM_EVENTS);
- if (do_autocmd_event(event, pat, once, nested, cmd, forceit, group)
- == FAIL) {
- break;
- }
- }
}
if (need_free) {
@@ -726,30 +836,14 @@ void do_autocmd(char_u *arg_in, int forceit)
xfree(envpat);
}
-// Find the group ID in a ":autocmd" or ":doautocmd" argument.
-// The "argp" argument is advanced to the following argument.
-//
-// Returns the group ID or AUGROUP_ALL.
-static int au_get_grouparg(char_u **argp)
+void do_all_autocmd_events(char_u *pat, bool once, int nested, char_u *cmd, bool delete, int group)
{
- char_u *group_name;
- char_u *p;
- char_u *arg = *argp;
- int group = AUGROUP_ALL;
-
- for (p = arg; *p && !ascii_iswhite(*p) && *p != '|'; p++) {
- }
- if (p > arg) {
- group_name = vim_strnsave(arg, (size_t)(p - arg));
- group = au_find_group(group_name);
- if (group == AUGROUP_ERROR) {
- group = AUGROUP_ALL; // no match, use all groups
- } else {
- *argp = skipwhite(p); // match, skip over group name
+ FOR_ALL_AUEVENTS(event) {
+ if (do_autocmd_event(event, pat, once, nested, cmd, delete, group)
+ == FAIL) {
+ return;
}
- xfree(group_name);
}
- return group;
}
// do_autocmd() for one event.
@@ -759,216 +853,294 @@ static int au_get_grouparg(char_u **argp)
// If *cmd == NUL: show entries.
// If forceit == true: delete entries.
// If group is not AUGROUP_ALL: only use this group.
-static int do_autocmd_event(event_T event, char_u *pat, bool once, int nested, char_u *cmd,
- int forceit, int group)
+int do_autocmd_event(event_T event, char_u *pat, bool once, int nested, char_u *cmd, bool delete,
+ int group)
+ FUNC_ATTR_NONNULL_ALL
{
+ // Cannot be used to show all patterns. See au_show_for_event or au_show_for_all_events
+ assert(*pat != NUL || delete);
+
AutoPat *ap;
AutoPat **prev_ap;
- AutoCmd *ac;
- AutoCmd **prev_ac;
- int brace_level;
- char_u *endpat;
int findgroup;
- int allgroups;
int patlen;
int is_buflocal;
int buflocal_nr;
char_u buflocal_pat[BUFLOCAL_PAT_LEN]; // for "<buffer=X>"
+ bool is_adding_cmd = *cmd != NUL;
+
if (group == AUGROUP_ALL) {
findgroup = current_augroup;
} else {
findgroup = group;
}
- allgroups = (group == AUGROUP_ALL && !forceit && *cmd == NUL);
- // Show or delete all patterns for an event.
- if (*pat == NUL) {
- for (ap = first_autopat[event]; ap != NULL; ap = ap->next) {
- if (forceit) { // delete the AutoPat, if it's in the current group
- if (ap->group == findgroup) {
- au_remove_pat(ap);
- }
- } else if (group == AUGROUP_ALL || ap->group == group) {
- show_autocmd(ap, event);
- }
- }
+ // Delete all aupat for an event.
+ if (*pat == NUL && delete) {
+ aupat_del_for_event_and_group(event, findgroup);
+ return OK;
}
// Loop through all the specified patterns.
- for (; *pat; pat = (*endpat == ',' ? endpat + 1 : endpat)) {
- // Find end of the pattern.
- // Watch out for a comma in braces, like "*.\{obj,o\}".
- endpat = pat;
- // ignore single comma
- if (*endpat == ',') {
- continue;
- }
- brace_level = 0;
- for (; *endpat && (*endpat != ',' || brace_level || endpat[-1] == '\\');
- endpat++) {
- if (*endpat == '{') {
- brace_level++;
- } else if (*endpat == '}') {
- brace_level--;
- }
- }
- patlen = (int)(endpat - pat);
-
- // detect special <buflocal[=X]> buffer-local patterns
- is_buflocal = false;
+ patlen = (int)aucmd_pattern_length(pat);
+ while (patlen) {
+ // detect special <buffer[=X]> buffer-local patterns
+ is_buflocal = aupat_is_buflocal(pat, patlen);
buflocal_nr = 0;
- if (patlen >= 8 && STRNCMP(pat, "<buffer", 7) == 0
- && pat[patlen - 1] == '>') {
- // "<buffer...>": Error will be printed only for addition.
- // printing and removing will proceed silently.
- is_buflocal = true;
- if (patlen == 8) {
- // "<buffer>"
- buflocal_nr = curbuf->b_fnum;
- } else if (patlen > 9 && pat[7] == '=') {
- if (patlen == 13 && STRNICMP(pat, "<buffer=abuf>", 13) == 0) {
- // "<buffer=abuf>"
- buflocal_nr = autocmd_bufnr;
- } else if (skipdigits(pat + 8) == pat + patlen - 1) {
- // "<buffer=123>"
- buflocal_nr = atoi((char *)pat + 8);
- }
- }
- }
-
if (is_buflocal) {
+ buflocal_nr = aupat_get_buflocal_nr(pat, patlen);
+
// normalize pat into standard "<buffer>#N" form
- snprintf((char *)buflocal_pat,
- BUFLOCAL_PAT_LEN,
- "<buffer=%d>",
- buflocal_nr);
+ aupat_normalize_buflocal_pat(buflocal_pat, pat, patlen, buflocal_nr);
- pat = buflocal_pat; // can modify pat and patlen
- patlen = (int)STRLEN(buflocal_pat); // but not endpat
+ pat = buflocal_pat;
+ patlen = (int)STRLEN(buflocal_pat);
}
- // Find AutoPat entries with this pattern. When adding a command it
- // always goes at or after the last one, so start at the end.
- if (!forceit && *cmd != NUL && last_autopat[(int)event] != NULL) {
- prev_ap = &last_autopat[(int)event];
- } else {
+ if (delete) {
+ assert(*pat != NUL);
+
+ // Find AutoPat entries with this pattern.
+ // always goes at or after the last one, so start at the end.
prev_ap = &first_autopat[(int)event];
- }
- while ((ap = *prev_ap) != NULL) {
- if (ap->pat != NULL) {
- // Accept a pattern when:
- // - a group was specified and it's that group, or a group was
- // not specified and it's the current group, or a group was
- // not specified and we are listing
- // - the length of the pattern matches
- // - the pattern matches.
- // For <buffer[=X]>, this condition works because we normalize
- // all buffer-local patterns.
- if ((allgroups || ap->group == findgroup) && ap->patlen == patlen
- && STRNCMP(pat, ap->pat, patlen) == 0) {
- // Remove existing autocommands.
- // If adding any new autocmd's for this AutoPat, don't
- // delete the pattern from the autopat list, append to
- // this list.
- if (forceit) {
- if (*cmd != NUL && ap->next == NULL) {
- au_remove_cmds(ap);
+ while ((ap = *prev_ap) != NULL) {
+ if (ap->pat != NULL) {
+ // Accept a pattern when:
+ // - a group was specified and it's that group
+ // - the length of the pattern matches
+ // - the pattern matches.
+ // For <buffer[=X]>, this condition works because we normalize
+ // all buffer-local patterns.
+ if (ap->group == findgroup
+ && ap->patlen == patlen
+ && STRNCMP(pat, ap->pat, patlen) == 0) {
+ // Remove existing autocommands.
+ // If adding any new autocmd's for this AutoPat, don't
+ // delete the pattern from the autopat list, append to
+ // this list.
+ if (is_adding_cmd && ap->next == NULL) {
+ aupat_remove_cmds(ap);
break;
}
- au_remove_pat(ap);
- } else if (*cmd == NUL) {
- // Show autocmd's for this autopat, or buflocals <buffer=X>
- show_autocmd(ap, event);
- } else if (ap->next == NULL) {
- // Add autocmd to this autopat, if it's the last one.
- break;
+ aupat_del(ap);
}
}
+ prev_ap = &ap->next;
}
- prev_ap = &ap->next;
}
- // Add a new command.
- if (*cmd != NUL) {
- // If the pattern we want to add a command to does appear at the
- // end of the list (or not is not in the list at all), add the
- // pattern at the end of the list.
- if (ap == NULL) {
- // refuse to add buffer-local ap if buffer number is invalid
- if (is_buflocal
- && (buflocal_nr == 0 || buflist_findnr(buflocal_nr) == NULL)) {
- semsg(_("E680: <buffer=%d>: invalid buffer number "), buflocal_nr);
- return FAIL;
- }
+ if (is_adding_cmd) {
+ AucmdExecutable exec = AUCMD_EXECUTABLE_INIT;
+ exec.type = CALLABLE_EX;
+ exec.callable.cmd = cmd;
+ autocmd_register(0, event, pat, patlen, group, once, nested, NULL, exec);
+ }
- ap = xmalloc(sizeof(AutoPat));
- ap->pat = vim_strnsave(pat, (size_t)patlen);
- ap->patlen = patlen;
+ pat = aucmd_next_pattern(pat, (size_t)patlen);
+ patlen = (int)aucmd_pattern_length(pat);
+ }
- if (is_buflocal) {
- ap->buflocal_nr = buflocal_nr;
- ap->reg_prog = NULL;
- } else {
- char_u *reg_pat;
+ au_cleanup();
+ return OK;
+}
- ap->buflocal_nr = 0;
- reg_pat = file_pat_to_reg_pat(pat, endpat, &ap->allow_dirs, true);
- if (reg_pat != NULL) {
- ap->reg_prog = vim_regcomp(reg_pat, RE_MAGIC);
- }
- xfree(reg_pat);
- if (reg_pat == NULL || ap->reg_prog == NULL) {
- xfree(ap->pat);
- xfree(ap);
- return FAIL;
- }
- }
+int autocmd_register(int64_t id, event_T event, char_u *pat, int patlen, int group, bool once,
+ bool nested, char *desc, AucmdExecutable aucmd)
+{
+ // 0 is not a valid group.
+ assert(group != 0);
- // need to initialize last_mode for the first ModeChanged autocmd
- if (event == EVENT_MODECHANGED && !has_event(EVENT_MODECHANGED)) {
- xfree(last_mode);
- last_mode = get_mode();
- }
+ AutoPat *ap;
+ AutoPat **prev_ap;
+ AutoCmd *ac;
+ AutoCmd **prev_ac;
+ int is_buflocal;
+ int buflocal_nr;
+ int findgroup;
+ char_u buflocal_pat[BUFLOCAL_PAT_LEN]; // for "<buffer=X>"
- // If the event is CursorMoved, update the last cursor position
- // position to avoid immediately triggering the autocommand
- if (event == EVENT_CURSORMOVED && !has_event(EVENT_CURSORMOVED)) {
- curwin->w_last_cursormoved = curwin->w_cursor;
- }
+ if (patlen > (int)STRLEN(pat)) {
+ return FAIL;
+ }
- ap->cmds = NULL;
- *prev_ap = ap;
- last_autopat[(int)event] = ap;
- ap->next = NULL;
- if (group == AUGROUP_ALL) {
- ap->group = current_augroup;
- } else {
- ap->group = group;
+ if (group == AUGROUP_ALL) {
+ findgroup = current_augroup;
+ } else {
+ findgroup = group;
+ }
+
+
+ // detect special <buffer[=X]> buffer-local patterns
+ is_buflocal = aupat_is_buflocal(pat, patlen);
+ buflocal_nr = 0;
+
+ if (is_buflocal) {
+ buflocal_nr = aupat_get_buflocal_nr(pat, patlen);
+
+ // normalize pat into standard "<buffer>#N" form
+ aupat_normalize_buflocal_pat(buflocal_pat, pat, patlen, buflocal_nr);
+
+ pat = buflocal_pat;
+ patlen = (int)STRLEN(buflocal_pat);
+ }
+
+ if (last_autopat[(int)event] != NULL) {
+ prev_ap = &last_autopat[(int)event];
+ } else {
+ prev_ap = &first_autopat[(int)event];
+ }
+
+ while ((ap = *prev_ap) != NULL) {
+ if (ap->pat != NULL) {
+ // Accept a pattern when:
+ // - a group was specified and it's that group
+ // - the length of the pattern matches
+ // - the pattern matches.
+ // For <buffer[=X]>, this condition works because we normalize
+ // all buffer-local patterns.
+ if (ap->group == findgroup
+ && ap->patlen == patlen
+ && STRNCMP(pat, ap->pat, patlen) == 0) {
+ if (ap->next == NULL) {
+ // Add autocmd to this autopat, if it's the last one.
+ break;
}
}
+ }
+ prev_ap = &ap->next;
+ }
+
+ // If the pattern we want to add a command to does appear at the
+ // end of the list (or not is not in the list at all), add the
+ // pattern at the end of the list.
+ if (ap == NULL) {
+ // refuse to add buffer-local ap if buffer number is invalid
+ if (is_buflocal
+ && (buflocal_nr == 0 || buflist_findnr(buflocal_nr) == NULL)) {
+ semsg(_("E680: <buffer=%d>: invalid buffer number "), buflocal_nr);
+ return FAIL;
+ }
+
+ ap = xmalloc(sizeof(AutoPat));
+ ap->pat = vim_strnsave(pat, (size_t)patlen);
+ ap->patlen = patlen;
+
+ if (is_buflocal) {
+ ap->buflocal_nr = buflocal_nr;
+ ap->reg_prog = NULL;
+ } else {
+ char_u *reg_pat;
- // Add the autocmd at the end of the AutoCmd list.
- prev_ac = &(ap->cmds);
- while ((ac = *prev_ac) != NULL) {
- prev_ac = &ac->next;
+ ap->buflocal_nr = 0;
+ reg_pat = file_pat_to_reg_pat(pat, pat + patlen, &ap->allow_dirs, true);
+ if (reg_pat != NULL) {
+ ap->reg_prog = vim_regcomp(reg_pat, RE_MAGIC);
+ }
+ xfree(reg_pat);
+ if (reg_pat == NULL || ap->reg_prog == NULL) {
+ xfree(ap->pat);
+ xfree(ap);
+ return FAIL;
}
- ac = xmalloc(sizeof(AutoCmd));
- ac->cmd = vim_strsave(cmd);
- ac->script_ctx = current_sctx;
- ac->script_ctx.sc_lnum += sourcing_lnum;
- ac->next = NULL;
- *prev_ac = ac;
- ac->once = once;
- ac->nested = nested;
+ }
+
+ // need to initialize last_mode for the first ModeChanged autocmd
+ if (event == EVENT_MODECHANGED && !has_event(EVENT_MODECHANGED)) {
+ xfree(last_mode);
+ last_mode = get_mode();
+ }
+
+ // If the event is CursorMoved, update the last cursor position
+ // position to avoid immediately triggering the autocommand
+ if (event == EVENT_CURSORMOVED && !has_event(EVENT_CURSORMOVED)) {
+ curwin->w_last_cursormoved = curwin->w_cursor;
+ }
+
+ ap->cmds = NULL;
+ *prev_ap = ap;
+ last_autopat[(int)event] = ap;
+ ap->next = NULL;
+ if (group == AUGROUP_ALL) {
+ ap->group = current_augroup;
+ } else {
+ ap->group = group;
}
}
- au_cleanup(); // may really delete removed patterns/commands now
+ // Add the autocmd at the end of the AutoCmd list.
+ prev_ac = &(ap->cmds);
+ while ((ac = *prev_ac) != NULL) {
+ prev_ac = &ac->next;
+ }
+
+ ac = xmalloc(sizeof(AutoCmd));
+ *prev_ac = ac;
+
+ ac->id = id;
+ ac->exec = aucmd_exec_copy(aucmd);
+ ac->script_ctx = current_sctx;
+ ac->script_ctx.sc_lnum += sourcing_lnum;
+ nlua_set_sctx(&ac->script_ctx);
+ ac->next = NULL;
+ ac->once = once;
+ ac->nested = nested;
+ ac->desc = NULL;
+
+ // TODO(tjdevries): What to do about :autocmd and where/how to show lua stuffs there.
+ // perhaps: <lua>DESCRIPTION or similar
+ if (desc != NULL) {
+ ac->desc = xstrdup(desc);
+ } else {
+ ac->desc = aucmd_exec_default_desc(aucmd);
+ }
+
return OK;
}
+size_t aucmd_pattern_length(char_u *pat)
+{
+ if (*pat == NUL) {
+ return 0;
+ }
+
+ char_u *endpat;
+
+ for (; *pat; pat = (*endpat == ',' ? endpat + 1 : endpat)) {
+ // Find end of the pattern.
+ // Watch out for a comma in braces, like "*.\{obj,o\}".
+ endpat = pat;
+ // ignore single comma
+ if (*endpat == ',') {
+ continue;
+ }
+ int brace_level = 0;
+ for (; *endpat && (*endpat != ',' || brace_level || endpat[-1] == '\\');
+ endpat++) {
+ if (*endpat == '{') {
+ brace_level++;
+ } else if (*endpat == '}') {
+ brace_level--;
+ }
+ }
+
+ return (size_t)(endpat - pat);
+ }
+
+ return STRLEN(pat);
+}
+
+char_u *aucmd_next_pattern(char_u *pat, size_t patlen)
+{
+ pat = pat + patlen;
+ if (*pat == ',') {
+ pat = pat + 1;
+ }
+
+ return pat;
+}
+
/// Implementation of ":doautocmd [group] event [fname]".
/// Return OK for success, FAIL for failure;
///
@@ -984,7 +1156,7 @@ int do_doautocmd(char_u *arg, bool do_msg, bool *did_something)
}
// Check for a legal group name. If not, use AUGROUP_ALL.
- group = au_get_grouparg(&arg);
+ group = arg_augroup_get(&arg);
if (*arg == '*') {
emsg(_("E217: Can't execute autocommands for ALL events"));
@@ -993,7 +1165,7 @@ int do_doautocmd(char_u *arg, bool do_msg, bool *did_something)
// Scan over the events.
// If we find an illegal name, return here, don't do anything.
- fname = find_end_event(arg, group != AUGROUP_ALL);
+ fname = arg_event_skip(arg, group != AUGROUP_ALL);
if (fname == NULL) {
return FAIL;
}
@@ -1069,8 +1241,6 @@ void ex_doautoall(exarg_T *eap)
do_modelines(0);
}
}
-
- check_cursor(); // just in case lines got deleted
}
/// Check *argp for <nomodeline>. When it is present return false, otherwise
@@ -1160,7 +1330,10 @@ void aucmd_prepbuf(aco_save_T *aco, buf_T *buf)
// Prevent chdir() call in win_enter_ext(), through do_autochdir()
int save_acd = p_acd;
p_acd = false;
+ // no redrawing and don't set the window title
+ RedrawingDisabled++;
win_enter(aucmd_win, false);
+ RedrawingDisabled--;
p_acd = save_acd;
unblock_autocmds();
curwin = aucmd_win;
@@ -1168,6 +1341,10 @@ void aucmd_prepbuf(aco_save_T *aco, buf_T *buf)
curbuf = buf;
aco->new_curwin_handle = curwin->handle;
set_bufref(&aco->new_curbuf, curbuf);
+
+ // disable the Visual area, the position may be invalid in another buffer
+ aco->save_VIsual_active = VIsual_active;
+ VIsual_active = false;
}
/// Cleanup after executing autocommands for a (hidden) buffer.
@@ -1264,6 +1441,12 @@ win_found:
check_cursor();
}
}
+
+ check_cursor(); // just in case lines got deleted
+ VIsual_active = aco->save_VIsual_active;
+ if (VIsual_active) {
+ check_pos(curbuf, &VIsual);
+ }
}
/// Execute autocommands for "event" and file name "fname".
@@ -1370,8 +1553,8 @@ bool trigger_cursorhold(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
/// @param eap Ex command arguments
///
/// @return true if some commands were executed.
-static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, bool force,
- int group, buf_T *buf, exarg_T *eap)
+bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, bool force, int group,
+ buf_T *buf, exarg_T *eap)
{
char_u *sfname = NULL; // short file name
char_u *tail;
@@ -1394,7 +1577,7 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io,
proftime_T wait_time;
bool did_save_redobuff = false;
save_redo_T save_redo;
- const bool save_KeyTyped = KeyTyped;
+ const bool save_KeyTyped = KeyTyped; // NOLINT
// Quickly return if there are no autocommands for this event or
// autocommands are blocked.
@@ -1509,12 +1692,13 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io,
|| event == EVENT_CMDLINELEAVE || event == EVENT_CMDWINENTER
|| event == EVENT_CMDWINLEAVE || event == EVENT_CMDUNDEFINED
|| event == EVENT_COLORSCHEME || event == EVENT_COLORSCHEMEPRE
- || event == EVENT_DIRCHANGED || event == EVENT_FILETYPE
- || event == EVENT_FUNCUNDEFINED || event == EVENT_MODECHANGED
- || event == EVENT_OPTIONSET || event == EVENT_QUICKFIXCMDPOST
- || event == EVENT_QUICKFIXCMDPRE || event == EVENT_REMOTEREPLY
- || event == EVENT_SPELLFILEMISSING || event == EVENT_SYNTAX
- || event == EVENT_SIGNAL || event == EVENT_TABCLOSED
+ || event == EVENT_DIRCHANGED || event == EVENT_DIRCHANGEDPRE
+ || event == EVENT_FILETYPE || event == EVENT_FUNCUNDEFINED
+ || event == EVENT_MODECHANGED || event == EVENT_OPTIONSET
+ || event == EVENT_QUICKFIXCMDPOST || event == EVENT_QUICKFIXCMDPRE
+ || event == EVENT_REMOTEREPLY || event == EVENT_SPELLFILEMISSING
+ || event == EVENT_SYNTAX || event == EVENT_SIGNAL
+ || event == EVENT_TABCLOSED || event == EVENT_USER
|| event == EVENT_WINCLOSED) {
fname = vim_strsave(fname);
} else {
@@ -1805,6 +1989,11 @@ void auto_next_pat(AutoPatCmd *apc, int stop_at_last)
/// @return allocated string, or NULL for end of autocommands.
char_u *getnextac(int c, void *cookie, int indent, bool do_concat)
{
+ // These arguments are required for do_cmdline.
+ (void)c;
+ (void)indent;
+ (void)do_concat;
+
AutoPatCmd *acp = (AutoPatCmd *)cookie;
char_u *retval;
AutoCmd *ac;
@@ -1817,7 +2006,8 @@ char_u *getnextac(int c, void *cookie, int indent, bool do_concat)
// repeat until we find an autocommand to execute
for (;;) {
// skip removed commands
- while (acp->nextcmd != NULL && acp->nextcmd->cmd == NULL) {
+ while (acp->nextcmd != NULL
+ && aucmd_exec_is_deleted(acp->nextcmd->exec)) {
if (acp->nextcmd->last) {
acp->nextcmd = NULL;
} else {
@@ -1847,14 +2037,34 @@ char_u *getnextac(int c, void *cookie, int indent, bool do_concat)
if (p_verbose >= 9) {
verbose_enter_scroll();
- smsg(_("autocommand %s"), ac->cmd);
+ smsg(_("autocommand %s"), aucmd_exec_to_string(ac, ac->exec));
msg_puts("\n"); // don't overwrite this either
verbose_leave_scroll();
}
- retval = vim_strsave(ac->cmd);
+
+ if (ac->exec.type == CALLABLE_CB) {
+ typval_T argsin = TV_INITIAL_VALUE;
+ typval_T rettv = TV_INITIAL_VALUE;
+ callback_call(&ac->exec.callable.cb, 0, &argsin, &rettv);
+
+ // TODO(tjdevries):
+ //
+ // Major Hack Alert:
+ // We just return "not-null" and continue going.
+ // This would be a good candidate for a refactor. You would need to refactor:
+ // 1. do_cmdline to accept something besides a string
+ // OR
+ // 2. make where we call do_cmdline for autocmds not have to return anything,
+ // and instead we loop over all the matches and just execute one-by-one.
+ // However, my expectation would be that could be expensive.
+ retval = vim_strsave((char_u *)"");
+ } else {
+ retval = vim_strsave(ac->exec.callable.cmd);
+ }
+
// Remove one-shot ("once") autocmd in anticipation of its execution.
if (ac->once) {
- au_del_cmd(ac);
+ aucmd_del(ac);
}
autocmd_nested = ac->nested;
current_sctx = ac->script_ctx;
@@ -1919,19 +2129,12 @@ bool has_autocmd(event_T event, char_u *sfname, buf_T *buf) FUNC_ATTR_WARN_UNUSE
// Function given to ExpandGeneric() to obtain the list of autocommand group
// names.
-char_u *get_augroup_name(expand_T *xp, int idx)
+char_u *expand_get_augroup_name(expand_T *xp, int idx)
{
- if (idx == augroups.ga_len) { // add "END" add the end
- return (char_u *)"END";
- }
- if (idx >= augroups.ga_len) { // end of list
- return NULL;
- }
- if (AUGROUP_NAME(idx) == NULL || AUGROUP_NAME(idx) == get_deleted_augroup()) {
- // skip deleted entries
- return (char_u *)"";
- }
- return (char_u *)AUGROUP_NAME(idx);
+ // Required for ExpandGeneric
+ (void)xp;
+
+ return (char_u *)augroup_name(idx + 1);
}
/// @param doautocmd true for :doauto*, false for :autocmd
@@ -1943,7 +2146,7 @@ char_u *set_context_in_autocmd(expand_T *xp, char_u *arg, int doautocmd)
// check for a group name, skip it if present
autocmd_include_groups = false;
p = arg;
- group = au_get_grouparg(&arg);
+ group = arg_augroup_get(&arg);
// If there only is a group name that's what we expand.
if (*arg == NUL && group != AUGROUP_ALL && !ascii_iswhite(arg[-1])) {
@@ -1984,16 +2187,25 @@ char_u *set_context_in_autocmd(expand_T *xp, char_u *arg, int doautocmd)
}
// Function given to ExpandGeneric() to obtain the list of event names.
-char_u *get_event_name(expand_T *xp, int idx)
+char_u *expand_get_event_name(expand_T *xp, int idx)
{
- if (idx < augroups.ga_len) { // First list group names, if wanted
- if (!autocmd_include_groups || AUGROUP_NAME(idx) == NULL
- || AUGROUP_NAME(idx) == get_deleted_augroup()) {
- return (char_u *)""; // skip deleted entries
+ // xp is a required parameter to be used with ExpandGeneric
+ (void)xp;
+
+
+ // List group names
+ char *name = augroup_name(idx + 1);
+ if (name != NULL) {
+ // skip when not including groups or skip deleted entries
+ if (!autocmd_include_groups || name == get_deleted_augroup()) {
+ return (char_u *)"";
}
- return (char_u *)AUGROUP_NAME(idx);
+
+ return (char_u *)name;
}
- return (char_u *)event_names[idx - augroups.ga_len].name;
+
+ // List event names
+ return (char_u *)event_names[idx - next_augroup_id].name;
}
/// Check whether given autocommand is supported
@@ -2036,7 +2248,7 @@ bool au_exists(const char *const arg) FUNC_ATTR_WARN_UNUSED_RESULT
}
// First, look for an autocmd group name.
- group = au_find_group((char_u *)arg_save);
+ group = augroup_find(arg_save);
char *event_name;
if (group == AUGROUP_ERROR) {
// Didn't match a group name, assume the first argument is an event.
@@ -2100,3 +2312,362 @@ theend:
xfree(arg_save);
return retval;
}
+
+// Checks if a pattern is buflocal
+bool aupat_is_buflocal(char_u *pat, int patlen)
+{
+ return patlen >= 8
+ && STRNCMP(pat, "<buffer", 7) == 0
+ && (pat)[patlen - 1] == '>';
+}
+
+int aupat_get_buflocal_nr(char_u *pat, int patlen)
+{
+ assert(aupat_is_buflocal(pat, patlen));
+
+ // "<buffer>"
+ if (patlen == 8) {
+ return curbuf->b_fnum;
+ }
+
+ if (patlen > 9 && (pat)[7] == '=') {
+ // "<buffer=abuf>"
+ if (patlen == 13 && STRNICMP(pat, "<buffer=abuf>", 13) == 0) {
+ return autocmd_bufnr;
+ }
+
+ // "<buffer=123>"
+ if (skipdigits(pat + 8) == pat + patlen - 1) {
+ return atoi((char *)pat + 8);
+ }
+ }
+
+ return 0;
+}
+
+// normalize buffer pattern
+void aupat_normalize_buflocal_pat(char_u *dest, char_u *pat, int patlen, int buflocal_nr)
+{
+ assert(aupat_is_buflocal(pat, patlen));
+
+ if (buflocal_nr == 0) {
+ buflocal_nr = curbuf->handle;
+ }
+
+ // normalize pat into standard "<buffer>#N" form
+ snprintf((char *)dest,
+ BUFLOCAL_PAT_LEN,
+ "<buffer=%d>",
+ buflocal_nr);
+}
+
+int autocmd_delete_event(int group, event_T event, char_u *pat)
+ FUNC_ATTR_NONNULL_ALL
+{
+ return do_autocmd_event(event, pat, false, false, (char_u *)"", true, group);
+}
+
+/// Deletes an autocmd by ID.
+/// Only autocmds created via the API have IDs associated with them. There
+/// is no way to delete a specific autocmd created via :autocmd
+void autocmd_delete_id(int64_t id)
+{
+ FOR_ALL_AUEVENTS(event) {
+ FOR_ALL_AUPATS_IN_EVENT(event, ap) {
+ for (AutoCmd *ac = ap->cmds; ac != NULL; ac = ac->next) {
+ if (ac->id == id) {
+ aucmd_del(ac);
+ }
+ }
+ }
+ }
+}
+
+// ===========================================================================
+// AucmdExecutable Functions
+// ===========================================================================
+char *aucmd_exec_default_desc(AucmdExecutable acc)
+{
+ size_t msglen = 100;
+
+ switch (acc.type) {
+ case CALLABLE_CB:
+ switch (acc.callable.cb.type) {
+ case kCallbackLua: {
+ char *msg = (char *)xmallocz(msglen);
+ snprintf(msg, msglen, "<Lua function %d>", acc.callable.cb.data.luaref);
+ return msg;
+ }
+ case kCallbackFuncref: {
+ // TODO(tjdevries): Is this enough space for this?
+ char *msg = (char *)xmallocz(msglen);
+ snprintf(msg, msglen, "<vim function: %s>", acc.callable.cb.data.funcref);
+ return msg;
+ }
+ case kCallbackPartial: {
+ char *msg = (char *)xmallocz(msglen);
+ snprintf(msg, msglen, "<vim partial: %s>", acc.callable.cb.data.partial->pt_name);
+ return msg;
+ }
+ default:
+ return NULL;
+ }
+ default:
+ return NULL;
+ }
+
+ abort();
+}
+char *aucmd_exec_to_string(AutoCmd *ac, AucmdExecutable acc)
+{
+ switch (acc.type) {
+ case CALLABLE_EX:
+ return (char *)acc.callable.cmd;
+ case CALLABLE_CB:
+ return ac->desc;
+ case CALLABLE_NONE:
+ return "This is not possible";
+ }
+
+ abort();
+}
+
+void aucmd_exec_free(AucmdExecutable *acc)
+{
+ switch (acc->type) {
+ case CALLABLE_EX:
+ XFREE_CLEAR(acc->callable.cmd);
+ break;
+ case CALLABLE_CB:
+ callback_free(&acc->callable.cb);
+ break;
+ case CALLABLE_NONE:
+ return;
+ }
+
+ acc->type = CALLABLE_NONE;
+}
+
+AucmdExecutable aucmd_exec_copy(AucmdExecutable src)
+{
+ AucmdExecutable dest = AUCMD_EXECUTABLE_INIT;
+
+ switch (src.type) {
+ case CALLABLE_EX:
+ dest.type = CALLABLE_EX;
+ dest.callable.cmd = vim_strsave(src.callable.cmd);
+ return dest;
+ case CALLABLE_CB:
+ dest.type = CALLABLE_CB;
+ callback_copy(&dest.callable.cb, &src.callable.cb);
+ return dest;
+ case CALLABLE_NONE:
+ return dest;
+ }
+
+ abort();
+}
+
+bool aucmd_exec_is_deleted(AucmdExecutable acc)
+{
+ switch (acc.type) {
+ case CALLABLE_EX:
+ return acc.callable.cmd == NULL;
+ case CALLABLE_CB:
+ return callback_is_freed(acc.callable.cb);
+ case CALLABLE_NONE:
+ return true;
+ }
+
+ abort();
+}
+
+bool au_event_is_empty(event_T event)
+{
+ return first_autopat[event] == NULL;
+}
+
+// Arg Parsing Functions
+
+/// Scan over the events. "*" stands for all events.
+/// true when group name was found
+static char_u *arg_event_skip(char_u *arg, int have_group)
+{
+ char_u *pat;
+ char_u *p;
+
+ if (*arg == '*') {
+ if (arg[1] && !ascii_iswhite(arg[1])) {
+ semsg(_("E215: Illegal character after *: %s"), arg);
+ return NULL;
+ }
+ pat = arg + 1;
+ } else {
+ for (pat = arg; *pat && *pat != '|' && !ascii_iswhite(*pat); pat = p) {
+ if ((int)event_name2nr(pat, &p) >= NUM_EVENTS) {
+ if (have_group) {
+ semsg(_("E216: No such event: %s"), pat);
+ } else {
+ semsg(_("E216: No such group or event: %s"), pat);
+ }
+ return NULL;
+ }
+ }
+ }
+ return pat;
+}
+
+// Find the group ID in a ":autocmd" or ":doautocmd" argument.
+// The "argp" argument is advanced to the following argument.
+//
+// Returns the group ID or AUGROUP_ALL.
+static int arg_augroup_get(char_u **argp)
+{
+ char_u *group_name;
+ char_u *p;
+ char_u *arg = *argp;
+ int group = AUGROUP_ALL;
+
+ for (p = arg; *p && !ascii_iswhite(*p) && *p != '|'; p++) {
+ }
+ if (p > arg) {
+ group_name = vim_strnsave(arg, (size_t)(p - arg));
+ group = augroup_find((char *)group_name);
+ if (group == AUGROUP_ERROR) {
+ group = AUGROUP_ALL; // no match, use all groups
+ } else {
+ *argp = skipwhite(p); // match, skip over group name
+ }
+ xfree(group_name);
+ }
+ return group;
+}
+
+/// Handles grabbing arguments from `:autocmd` such as ++once and ++nested
+static bool arg_autocmd_flag_get(bool *flag, char_u **cmd_ptr, char *pattern, int len)
+{
+ if (STRNCMP(*cmd_ptr, pattern, len) == 0 && ascii_iswhite((*cmd_ptr)[len])) {
+ if (*flag) {
+ semsg(_(e_duparg2), pattern);
+ return true;
+ }
+
+ *flag = true;
+ *cmd_ptr = skipwhite((*cmd_ptr) + len);
+ }
+
+ return false;
+}
+
+
+// UI Enter
+void do_autocmd_uienter(uint64_t chanid, bool attached)
+{
+ static bool recursive = false;
+
+ if (recursive) {
+ return; // disallow recursion
+ }
+ recursive = true;
+
+ save_v_event_T save_v_event;
+ dict_T *dict = get_v_event(&save_v_event);
+ assert(chanid < VARNUMBER_MAX);
+ tv_dict_add_nr(dict, S_LEN("chan"), (varnumber_T)chanid);
+ tv_dict_set_keys_readonly(dict);
+ apply_autocmds(attached ? EVENT_UIENTER : EVENT_UILEAVE,
+ NULL, NULL, false, curbuf);
+ restore_v_event(dict, &save_v_event);
+
+ recursive = false;
+}
+
+// FocusGained
+
+static void focusgained_event(void **argv)
+{
+ bool *gainedp = argv[0];
+ do_autocmd_focusgained(*gainedp);
+ xfree(gainedp);
+}
+
+void autocmd_schedule_focusgained(bool gained)
+{
+ bool *gainedp = xmalloc(sizeof(*gainedp));
+ *gainedp = gained;
+ loop_schedule_deferred(&main_loop,
+ event_create(focusgained_event, 1, gainedp));
+}
+
+static void do_autocmd_focusgained(bool gained)
+{
+ static bool recursive = false;
+ static Timestamp last_time = (time_t)0;
+ bool need_redraw = false;
+
+ if (recursive) {
+ return; // disallow recursion
+ }
+ recursive = true;
+ need_redraw |= apply_autocmds((gained ? EVENT_FOCUSGAINED : EVENT_FOCUSLOST),
+ NULL, NULL, false, curbuf);
+
+ // When activated: Check if any file was modified outside of Vim.
+ // Only do this when not done within the last two seconds as:
+ // 1. Some filesystems have modification time granularity in seconds. Fat32
+ // has a granularity of 2 seconds.
+ // 2. We could get multiple notifications in a row.
+ if (gained && last_time + (Timestamp)2000 < os_now()) {
+ need_redraw = check_timestamps(true);
+ last_time = os_now();
+ }
+
+ if (need_redraw) {
+ // Something was executed, make sure the cursor is put back where it
+ // belongs.
+ need_wait_return = false;
+
+ if (State & CMDLINE) {
+ redrawcmdline();
+ } else if ((State & NORMAL) || (State & INSERT)) {
+ if (must_redraw != 0) {
+ update_screen(0);
+ }
+
+ setcursor();
+ }
+
+ ui_flush();
+ }
+
+ if (need_maketitle) {
+ maketitle();
+ }
+
+ recursive = false;
+}
+
+// initialization
+
+void init_default_autocmds(void)
+{
+ // open terminals when opening files that start with term://
+#define PROTO "term://"
+ do_cmdline_cmd("augroup nvim_terminal");
+ do_cmdline_cmd("autocmd BufReadCmd " PROTO "* ++nested "
+ "if !exists('b:term_title')|call termopen("
+ // Capture the command string
+ "matchstr(expand(\"<amatch>\"), "
+ "'\\c\\m" PROTO "\\%(.\\{-}//\\%(\\d\\+:\\)\\?\\)\\?\\zs.*'), "
+ // capture the working directory
+ "{'cwd': expand(get(matchlist(expand(\"<amatch>\"), "
+ "'\\c\\m" PROTO "\\(.\\{-}\\)//'), 1, ''))})"
+ "|endif");
+ do_cmdline_cmd("augroup END");
+#undef PROTO
+
+ // limit syntax synchronization in the command window
+ do_cmdline_cmd("augroup nvim_cmdwin");
+ do_cmdline_cmd("autocmd! CmdwinEnter [:>] syntax sync minlines=1 maxlines=1");
+ do_cmdline_cmd("augroup END");
+}
diff --git a/src/nvim/autocmd.h b/src/nvim/autocmd.h
index ac12e2acf3..53ec7f2bb7 100644
--- a/src/nvim/autocmd.h
+++ b/src/nvim/autocmd.h
@@ -4,6 +4,11 @@
#include "nvim/buffer_defs.h"
#include "nvim/ex_cmds_defs.h"
+// event_T definition
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "auevents_enum.generated.h"
+#endif
+
// Struct to save values in before executing autocommands for a buffer that is
// not the current buffer.
typedef struct {
@@ -14,25 +19,27 @@ typedef struct {
handle_T save_prevwin_handle; ///< ID of saved prevwin
bufref_T new_curbuf; ///< new curbuf
char_u *globaldir; ///< saved value of globaldir
+ bool save_VIsual_active; ///< saved VIsual_active
} aco_save_T;
typedef struct AutoCmd {
- char_u *cmd; // Command to be executed (NULL when
- // command has been removed)
+ AucmdExecutable exec;
bool once; // "One shot": removed after execution
bool nested; // If autocommands nest here
bool last; // last command in list
+ int64_t id; // ID used for uniquely tracking an autocmd.
sctx_T script_ctx; // script context where defined
- struct AutoCmd *next; // Next AutoCmd in list
+ char *desc; // Description for the autocmd.
+ struct AutoCmd *next; // Next AutoCmd in list
} AutoCmd;
typedef struct AutoPat {
- struct AutoPat *next; // next AutoPat in AutoPat list; MUST
- // be the first entry
- char_u *pat; // pattern as typed (NULL when pattern
- // has been removed)
- regprog_T *reg_prog; // compiled regprog for pattern
- AutoCmd *cmds; // list of commands to do
+ struct AutoPat *next; // next AutoPat in AutoPat list; MUST
+ // be the first entry
+ char_u *pat; // pattern as typed (NULL when pattern
+ // has been removed)
+ regprog_T *reg_prog; // compiled regprog for pattern
+ AutoCmd *cmds; // list of commands to do
int group; // group ID
int patlen; // strlen() of pat
int buflocal_nr; // !=0 for buffer-local AutoPat
@@ -40,13 +47,7 @@ typedef struct AutoPat {
char last; // last pattern for apply_autocmds()
} AutoPat;
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "auevents_enum.generated.h"
-#endif
-
-///
/// Struct used to keep status while executing autocommands for an event.
-///
typedef struct AutoPatCmd {
AutoPat *curpat; // next AutoPat to examine
AutoCmd *nextcmd; // next AutoCmd to execute
@@ -74,8 +75,16 @@ EXTERN bool au_did_filetype INIT(= false);
# include "autocmd.h.generated.h"
#endif
-#define AUGROUP_DEFAULT -1 // default autocmd group
-#define AUGROUP_ERROR -2 // erroneous autocmd group
-#define AUGROUP_ALL -3 // all autocmd groups
+#define AUGROUP_DEFAULT (-1) // default autocmd group
+#define AUGROUP_ERROR (-2) // erroneous autocmd group
+#define AUGROUP_ALL (-3) // all autocmd groups
+#define AUGROUP_DELETED (-4) // all autocmd groups
+// #define AUGROUP_NS -5 // TODO(tjdevries): Support namespaced based augroups
+
+#define BUFLOCAL_PAT_LEN 25
+
+/// Iterates over all the events for auto commands
+#define FOR_ALL_AUEVENTS(event) \
+ for (event_T event = (event_T)0; (int)event < (int)NUM_EVENTS; event = (event_T)((int)event + 1)) // NOLINT
#endif // NVIM_AUTOCMD_H
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index 89baea83f8..9e82b4e80b 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -846,7 +846,7 @@ void goto_buffer(exarg_T *eap, int start, int dir, int count)
enter_cleanup(&cs);
// Quitting means closing the split window, nothing else.
- win_close(curwin, true);
+ win_close(curwin, true, false);
swap_exists_action = SEA_NONE;
swap_exists_did_quit = true;
@@ -1237,7 +1237,7 @@ int do_buffer(int action, int start, int dir, int count, int forceit)
while (buf == curbuf
&& !(curwin->w_closing || curwin->w_buffer->b_locked > 0)
&& (!ONE_WINDOW || first_tabpage->tp_next != NULL)) {
- if (win_close(curwin, false) == FAIL) {
+ if (win_close(curwin, false, false) == FAIL) {
break;
}
}
@@ -1441,7 +1441,7 @@ void set_curbuf(buf_T *buf, int action)
set_bufref(&prevbufref, prevbuf);
set_bufref(&newbufref, buf);
- // Autocommands may delete the curren buffer and/or the buffer we want to go
+ // Autocommands may delete the current buffer and/or the buffer we want to go
// to. In those cases don't close the buffer.
if (!apply_autocmds(EVENT_BUFLEAVE, NULL, NULL, false, curbuf)
|| (bufref_valid(&prevbufref) && bufref_valid(&newbufref)
@@ -1454,7 +1454,11 @@ void set_curbuf(buf_T *buf, int action)
}
if (bufref_valid(&prevbufref) && !aborting()) {
win_T *previouswin = curwin;
- if (prevbuf == curbuf) {
+
+ // Do not sync when in Insert mode and the buffer is open in
+ // another window, might be a timer doing something in another
+ // window.
+ if (prevbuf == curbuf && ((State & INSERT) == 0 || curbuf->b_nwindows <= 1)) {
u_sync(false);
}
close_buffer(prevbuf == curwin->w_buffer ? curwin : NULL,
@@ -1494,6 +1498,11 @@ void set_curbuf(buf_T *buf, int action)
*/
void enter_buffer(buf_T *buf)
{
+ // Get the buffer in the current window.
+ curwin->w_buffer = buf;
+ curbuf = buf;
+ curbuf->b_nwindows++;
+
// Copy buffer and window local option values. Not for a help buffer.
buf_copy_options(buf, BCO_ENTER | BCO_NOHELP);
if (!buf->b_help) {
@@ -1504,11 +1513,6 @@ void enter_buffer(buf_T *buf)
}
foldUpdateAll(curwin); // update folds (later).
- // Get the buffer in the current window.
- curwin->w_buffer = buf;
- curbuf = buf;
- curbuf->b_nwindows++;
-
if (curwin->w_p_diff) {
diff_buf_add(curbuf);
}
@@ -1896,10 +1900,8 @@ void free_buf_options(buf_T *buf, int free_p_ff)
clear_string_option(&buf->b_p_flp);
clear_string_option(&buf->b_p_isk);
clear_string_option(&buf->b_p_vsts);
- xfree(buf->b_p_vsts_nopaste);
- buf->b_p_vsts_nopaste = NULL;
- xfree(buf->b_p_vsts_array);
- buf->b_p_vsts_array = NULL;
+ XFREE_CLEAR(buf->b_p_vsts_nopaste);
+ XFREE_CLEAR(buf->b_p_vsts_array);
clear_string_option(&buf->b_p_vts);
XFREE_CLEAR(buf->b_p_vts_array);
clear_string_option(&buf->b_p_keymap);
@@ -3326,7 +3328,7 @@ void maketitle(void)
len = (int)STRLEN(buf_p);
if (len > 100) {
len -= 100;
- len += (*mb_tail_off)(buf_p, buf_p + len) + 1;
+ len += mb_tail_off(buf_p, buf_p + len) + 1;
buf_p += len;
}
STRCPY(icon_str, buf_p);
@@ -3418,7 +3420,7 @@ typedef enum {
///
/// @return The final width of the statusline
int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use_sandbox,
- char_u fillchar, int maxwidth, stl_hlrec_t **hltab, StlClickRecord **tabtab)
+ int fillchar, int maxwidth, stl_hlrec_t **hltab, StlClickRecord **tabtab)
{
static size_t stl_items_len = 20; // Initial value, grows as needed.
static stl_item_t *stl_items = NULL;
@@ -3437,8 +3439,12 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use
if (stl_items == NULL) {
stl_items = xmalloc(sizeof(stl_item_t) * stl_items_len);
stl_groupitems = xmalloc(sizeof(int) * stl_items_len);
- stl_hltab = xmalloc(sizeof(stl_hlrec_t) * stl_items_len);
- stl_tabtab = xmalloc(sizeof(StlClickRecord) * stl_items_len);
+
+ // Allocate one more, because the last element is used to indicate the
+ // end of the list.
+ stl_hltab = xmalloc(sizeof(stl_hlrec_t) * (stl_items_len + 1));
+ stl_tabtab = xmalloc(sizeof(StlClickRecord) * (stl_items_len + 1));
+
stl_separator_locations = xmalloc(sizeof(int) * stl_items_len);
}
@@ -3461,9 +3467,6 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use
if (fillchar == 0) {
fillchar = ' ';
- } else if (utf_char2len(fillchar) > 1) {
- // Can't handle a multi-byte fill character yet.
- fillchar = '-';
}
// The cursor in windows other than the current one isn't always
@@ -3516,8 +3519,8 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use
stl_items = xrealloc(stl_items, sizeof(stl_item_t) * new_len);
stl_groupitems = xrealloc(stl_groupitems, sizeof(int) * new_len);
- stl_hltab = xrealloc(stl_hltab, sizeof(stl_hlrec_t) * new_len);
- stl_tabtab = xrealloc(stl_tabtab, sizeof(StlClickRecord) * new_len);
+ stl_hltab = xrealloc(stl_hltab, sizeof(stl_hlrec_t) * (new_len + 1));
+ stl_tabtab = xrealloc(stl_tabtab, sizeof(StlClickRecord) * (new_len + 1));
stl_separator_locations =
xrealloc(stl_separator_locations, sizeof(int) * new_len);
@@ -3661,7 +3664,7 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use
out_p = out_p - n + 1;
// Fill up space left over by half a double-wide char.
while (++group_len < stl_items[stl_groupitems[groupdepth]].minwid) {
- *out_p++ = fillchar;
+ MB_CHAR2BYTES(fillchar, out_p);
}
// }
@@ -3684,14 +3687,14 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use
if (min_group_width < 0) {
min_group_width = 0 - min_group_width;
while (group_len++ < min_group_width && out_p < out_end_p) {
- *out_p++ = fillchar;
+ MB_CHAR2BYTES(fillchar, out_p);
}
// If the group is right-aligned, shift everything to the right and
// prepend with filler characters.
} else {
// { Move the group to the right
- memmove(t + min_group_width - group_len, t, (size_t)(out_p - t));
- group_len = min_group_width - group_len;
+ group_len = (min_group_width - group_len) * utf_char2len(fillchar);
+ memmove(t + group_len, t, (size_t)(out_p - t));
if (out_p + group_len >= (out_end_p + 1)) {
group_len = (long)(out_end_p - out_p);
}
@@ -3705,7 +3708,7 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use
// Prepend the fill characters
for (; group_len > 0; group_len--) {
- *t++ = fillchar;
+ MB_CHAR2BYTES(fillchar, t);
}
}
}
@@ -4001,14 +4004,7 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use
case STL_VIRTCOL:
case STL_VIRTCOL_ALT: {
- // In list mode virtcol needs to be recomputed
- colnr_T virtcol = wp->w_virtcol;
- if (wp->w_p_list && wp->w_p_lcs_chars.tab1 == NUL) {
- wp->w_p_list = false;
- getvcol(wp, &wp->w_cursor, NULL, &virtcol, NULL);
- wp->w_p_list = true;
- }
- virtcol++;
+ colnr_T virtcol = wp->w_virtcol + 1;
// Don't display %V if it's the same as %c.
if (opt == STL_VIRTCOL_ALT
&& (virtcol == (colnr_T)(!(State & INSERT) && empty_line
@@ -4237,7 +4233,7 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use
if (l + 1 == minwid && fillchar == '-' && ascii_isdigit(*t)) {
*out_p++ = ' ';
} else {
- *out_p++ = fillchar;
+ MB_CHAR2BYTES(fillchar, out_p);
}
}
minwid = 0;
@@ -4248,20 +4244,21 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use
}
// { Copy the string text into the output buffer
- while (*t && out_p < out_end_p) {
- *out_p++ = *t++;
+ for (; *t && out_p < out_end_p; t++) {
// Change a space by fillchar, unless fillchar is '-' and a
// digit follows.
- if (fillable && out_p[-1] == ' '
- && (!ascii_isdigit(*t) || fillchar != '-')) {
- out_p[-1] = fillchar;
+ if (fillable && *t == ' '
+ && (!ascii_isdigit(*(t + 1)) || fillchar != '-')) {
+ MB_CHAR2BYTES(fillchar, out_p);
+ } else {
+ *out_p++ = *t;
}
}
// }
// For left-aligned items, fill any remaining space with the fillchar
for (; l < minwid && out_p < out_end_p; l++) {
- *out_p++ = fillchar;
+ MB_CHAR2BYTES(fillchar, out_p);
}
// Otherwise if the item is a number, copy that to the output buffer.
@@ -4351,7 +4348,7 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use
// Only free the string buffer if we allocated it.
// Note: This is not needed if `str` is pointing at `tmp`
if (opt == STL_VIM_EXPR) {
- xfree(str);
+ XFREE_CLEAR(str);
}
if (num >= 0 || (!itemisflag && str && *str)) {
@@ -4454,7 +4451,7 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use
// Fill up for half a double-wide character.
while (++width < maxwidth) {
- *trunc_p++ = fillchar;
+ MB_CHAR2BYTES(fillchar, trunc_p);
*trunc_p = NUL;
}
// }
@@ -4505,13 +4502,13 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use
standard_spaces * (num_separators - 1);
for (int i = 0; i < num_separators; i++) {
- int dislocation = (i == (num_separators - 1))
- ? final_spaces : standard_spaces;
+ int dislocation = (i == (num_separators - 1)) ? final_spaces : standard_spaces;
+ dislocation *= utf_char2len(fillchar);
char_u *start = stl_items[stl_separator_locations[i]].start;
char_u *seploc = start + dislocation;
STRMOVE(seploc, start);
- for (char_u *s = start; s < seploc; s++) {
- *s = fillchar;
+ for (char_u *s = start; s < seploc; ) {
+ MB_CHAR2BYTES(fillchar, s);
}
for (int item_idx = stl_separator_locations[i] + 1;
@@ -4576,7 +4573,7 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use
cur_tab_rec->def.func = NULL;
}
- // When inside update_screen we do not want redrawing a stausline, ruler,
+ // When inside update_screen we do not want redrawing a statusline, ruler,
// title, etc. to trigger another redraw, it may cause an endless loop.
if (updating_screen) {
must_redraw = save_must_redraw;
@@ -4830,7 +4827,7 @@ void do_arg_all(int count, int forceit, int keep_tabs)
&& (first_tabpage->tp_next == NULL || !had_tab)) {
use_firstwin = true;
} else {
- win_close(wp, !buf_hide(buf) && !bufIsChanged(buf));
+ win_close(wp, !buf_hide(buf) && !bufIsChanged(buf), false);
// check if autocommands removed the next window
if (!win_valid(wpnext)) {
// start all over...
@@ -5021,7 +5018,7 @@ void ex_buffer_all(exarg_T *eap)
&& !ONE_WINDOW
&& !(wp->w_closing
|| wp->w_buffer->b_locked > 0)) {
- win_close(wp, false);
+ win_close(wp, false, false);
wpnext = firstwin; // just in case an autocommand does
// something strange with windows
tpnext = first_tabpage; // start all over...
@@ -5102,7 +5099,7 @@ void ex_buffer_all(exarg_T *eap)
enter_cleanup(&cs);
// User selected Quit at ATTENTION prompt; close this window.
- win_close(curwin, true);
+ win_close(curwin, true, false);
open_wins--;
swap_exists_action = SEA_NONE;
swap_exists_did_quit = true;
@@ -5144,7 +5141,7 @@ void ex_buffer_all(exarg_T *eap)
// BufWrite Autocommands made the window invalid, start over
wp = lastwin;
} else if (r) {
- win_close(wp, !buf_hide(wp->w_buffer));
+ win_close(wp, !buf_hide(wp->w_buffer), false);
open_wins--;
wp = lastwin;
} else {
@@ -5463,33 +5460,54 @@ bool find_win_for_buf(buf_T *buf, win_T **wp, tabpage_T **tp)
return false;
}
-int buf_signcols(buf_T *buf)
+static int buf_signcols_inner(buf_T *buf, int maximum)
{
- if (!buf->b_signcols_valid) {
- sign_entry_T *sign; // a sign in the sign list
- int signcols = 0;
- int linesum = 0;
- linenr_T curline = 0;
-
- FOR_ALL_SIGNS_IN_BUF(buf, sign) {
- if (sign->se_lnum > curline) {
- if (linesum > signcols) {
- signcols = linesum;
+ sign_entry_T *sign; // a sign in the sign list
+ int signcols = 0;
+ int linesum = 0;
+ linenr_T curline = 0;
+
+ FOR_ALL_SIGNS_IN_BUF(buf, sign) {
+ if (sign->se_lnum > curline) {
+ if (linesum > signcols) {
+ signcols = linesum;
+ if (signcols >= maximum) {
+ return maximum;
}
- curline = sign->se_lnum;
- linesum = 0;
- }
- if (sign->se_has_text_or_icon) {
- linesum++;
}
+ curline = sign->se_lnum;
+ linesum = 0;
}
- if (linesum > signcols) {
- signcols = linesum;
+ if (sign->se_has_text_or_icon) {
+ linesum++;
}
+ }
+ if (linesum > signcols) {
+ signcols = linesum;
+ if (signcols >= maximum) {
+ return maximum;
+ }
+ }
+
+ return signcols;
+}
+
+int buf_signcols(buf_T *buf, int maximum)
+{
+ // The maximum can be determined from 'signcolumn' which is window scoped so
+ // need to invalidate signcols if the maximum is greater than the previous
+ // maximum.
+ if (maximum > buf->b_signcols_max) {
+ buf->b_signcols_valid = false;
+ }
+
+ if (!buf->b_signcols_valid) {
+ int signcols = buf_signcols_inner(buf, maximum);
// Check if we need to redraw
if (signcols != buf->b_signcols) {
buf->b_signcols = signcols;
+ buf->b_signcols_max = maximum;
redraw_buf_later(buf, NOT_VALID);
}
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index 49e527e98b..1e0c837056 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -204,6 +204,10 @@ typedef struct {
#define w_p_nu w_onebuf_opt.wo_nu // 'number'
int wo_rnu;
#define w_p_rnu w_onebuf_opt.wo_rnu // 'relativenumber'
+ char_u *wo_ve;
+#define w_p_ve w_onebuf_opt.wo_ve // 'virtualedit'
+ unsigned wo_ve_flags;
+#define w_ve_flags w_onebuf_opt.wo_ve_flags // flags for 'virtualedit'
long wo_nuw;
#define w_p_nuw w_onebuf_opt.wo_nuw // 'numberwidth'
int wo_wfh;
@@ -352,6 +356,7 @@ struct mapblock {
char_u *m_keys; // mapped from, lhs
char_u *m_str; // mapped to, rhs
char_u *m_orig_str; // rhs as entered by the user
+ LuaRef m_luaref; // lua function reference as rhs
int m_keylen; // strlen(m_keys)
int m_mode; // valid mode
int m_noremap; // if non-zero no re-mapping for m_str
@@ -359,6 +364,7 @@ struct mapblock {
char m_nowait; // <nowait> used
char m_expr; // <expr> used, m_str is an expression
sctx_T m_script_ctx; // SCTX where map was defined
+ char *m_desc; // description of keymap
};
/// Used for highlighting in the status line.
@@ -581,7 +587,9 @@ struct file_buffer {
// where invoked
long b_mtime; // last change time of original file
+ long b_mtime_ns; // nanoseconds of last change time
long b_mtime_read; // last change time when reading
+ long b_mtime_read_ns; // nanoseconds of last read time
uint64_t b_orig_size; // size of original file in bytes
int b_orig_mode; // mode of original file
time_t b_last_used; // time when the buffer was last used; used
@@ -856,6 +864,7 @@ struct file_buffer {
sign_entry_T *b_signlist; // list of placed signs
int b_signcols; // last calculated number of sign columns
bool b_signcols_valid; // calculated sign columns is valid
+ int b_signcols_max; // Maximum value b_signcols is valid for.
Terminal *terminal; // Terminal instance associated with the buffer
@@ -864,8 +873,7 @@ struct file_buffer {
int b_mapped_ctrl_c; // modes where CTRL-C is mapped
MarkTree b_marktree[1];
- Map(uint64_t, ExtmarkItem) b_extmark_index[1];
- Map(uint64_t, ExtmarkNs) b_extmark_ns[1]; // extmark namespaces
+ Map(uint32_t, uint32_t) b_extmark_ns[1]; // extmark namespaces
size_t b_virt_line_blocks; // number of virt_line blocks
// array of channel_id:s which have asked to receive updates for this
@@ -1346,6 +1354,7 @@ struct window_S {
// recomputed
int w_nrwidth; // width of 'number' and 'relativenumber'
// column being used
+ int w_scwidth; // width of 'signcolumn'
/*
* === end of cached values ===
diff --git a/src/nvim/change.c b/src/nvim/change.c
index 1dbbfff024..736867b6d3 100644
--- a/src/nvim/change.c
+++ b/src/nvim/change.c
@@ -223,19 +223,20 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, long xtra
// 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);
+ linenr_T last = lnume + xtra - 1; // last line after the change
+ foldUpdate(wp, lnum, last);
// 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. */
+ // 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) {
+ folded = hasFoldingWin(wp, last, NULL, &last, false, NULL);
+ if (wp->w_cursor.lnum == last) {
wp->w_cline_folded = folded;
}
@@ -789,7 +790,7 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine)
// 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) {
+ && (get_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);
@@ -952,11 +953,13 @@ int copy_indent(int size, char_u *src)
///
/// "second_line_indent": indent for after ^^D in Insert mode or if flag
/// OPENLINE_COM_LIST
+/// "did_do_comment" is set to true when intentionally putting the comment
+/// leader in fromt of the new line.
///
/// @param dir FORWARD or BACKWARD
///
/// @return true on success, false on failure
-int open_line(int dir, int flags, int second_line_indent)
+int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment)
{
char_u *next_line = NULL; // copy of the next line
char_u *p_extra = NULL; // what goes to next line
@@ -969,6 +972,7 @@ int open_line(int dir, int flags, int second_line_indent)
bool retval = false; // return value
int extra_len = 0; // length of p_extra string
int lead_len; // length of comment leader
+ int comment_start = 0; // start index of the 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
@@ -977,6 +981,7 @@ int open_line(int dir, int flags, int second_line_indent)
pos_T *pos;
bool do_si = (!p_paste && curbuf->b_p_si && !curbuf->b_p_cin
&& *curbuf->b_p_inde == NUL);
+ bool do_cindent;
bool no_si = false; // reset did_si afterwards
int first_char = NUL; // init for GCC
int vreplace_mode;
@@ -1189,11 +1194,30 @@ int open_line(int dir, int flags, int second_line_indent)
did_ai = true;
}
+ // May do indenting after opening a new line.
+ do_cindent = !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));
+
// 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);
+ if (lead_len == 0 && do_cindent && dir == FORWARD) {
+ // Check for a line comment after code.
+ comment_start = check_linecomment(saved_line);
+ if (comment_start != MAXCOL) {
+ lead_len = get_leader_len(saved_line + comment_start,
+ &lead_flags, false, true);
+ if (lead_len != 0) {
+ lead_len += comment_start;
+ if (did_do_comment != NULL) {
+ *did_do_comment = true;
+ }
+ }
+ }
+ }
} else {
lead_len = 0;
}
@@ -1349,6 +1373,13 @@ int open_line(int dir, int flags, int second_line_indent)
STRLCPY(leader, saved_line, lead_len + 1);
+ // TODO(vim): handle multi-byte and double width chars
+ for (int li = 0; li < comment_start; li++) {
+ if (!ascii_iswhite(leader[li])) {
+ leader[li] = ' ';
+ }
+ }
+
// Replace leader with lead_repl, right or left adjusted
if (lead_repl != NULL) {
int c = 0;
@@ -1758,13 +1789,7 @@ int open_line(int dir, int flags, int second_line_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))) {
+ if (do_cindent) {
do_c_expr_indent();
ai_col = (colnr_T)getwhitecols_curline();
}
diff --git a/src/nvim/channel.c b/src/nvim/channel.c
index cd5134fe5f..d79c0acc4a 100644
--- a/src/nvim/channel.c
+++ b/src/nvim/channel.c
@@ -613,7 +613,6 @@ static void on_channel_output(Stream *stream, Channel *chan, RBuffer *buf, size_
} else {
if (chan->term) {
terminal_receive(chan->term, ptr, count);
- terminal_flush_output(chan->term);
}
rbuffer_consumed(buf, count);
diff --git a/src/nvim/channel.h b/src/nvim/channel.h
index 81b75e2d31..50d6b3600a 100644
--- a/src/nvim/channel.h
+++ b/src/nvim/channel.h
@@ -96,6 +96,8 @@ struct Channel {
EXTERN PMap(uint64_t) channels INIT(= MAP_INIT);
+EXTERN Callback on_print INIT(= CALLBACK_INIT);
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "channel.h.generated.h"
#endif
diff --git a/src/nvim/charset.c b/src/nvim/charset.c
index 599d662993..f4882e57e1 100644
--- a/src/nvim/charset.c
+++ b/src/nvim/charset.c
@@ -217,9 +217,7 @@ int buf_init_chartab(buf_T *buf, int global)
}
} else if (i == 1) {
// (re)set printable
- // For double-byte we keep the cell width, so
- // that we can detect it from the first byte.
- if (((c < ' ') || (c > '~'))) {
+ if (c < ' ' || c > '~') {
if (tilde) {
g_chartab[c] = (uint8_t)((g_chartab[c] & ~CT_CELL_MASK)
+ ((dy_flags & DY_UHEX) ? 4 : 2));
@@ -539,7 +537,7 @@ char_u *transchar_buf(const buf_T *buf, int c)
c = K_SECOND(c);
}
- if ((!chartab_initialized && (((c >= ' ') && (c <= '~'))))
+ if ((!chartab_initialized && (c >= ' ' && c <= '~'))
|| ((c <= 0xFF) && vim_isprintc_strict(c))) {
// printable character
transchar_charbuf[i] = (char_u)c;
@@ -1441,7 +1439,7 @@ bool vim_isblankline(char_u *lbuf)
/// @param unptr Returns the unsigned result.
/// @param maxlen Max length of string to check.
/// @param strict If true, fail if the number has unexpected trailing
-/// alpha-numeric chars: *len is set to 0 and nothing else is
+/// alphanumeric chars: *len is set to 0 and nothing else is
/// returned.
void vim_str2nr(const char_u *const start, int *const prep, int *const len, const int what,
varnumber_T *const nptr, uvarnumber_T *const unptr, const int maxlen,
@@ -1587,7 +1585,7 @@ vim_str2nr_hex:
#undef PARSE_NUMBER
vim_str2nr_proceed:
- // Check for an alpha-numeric character immediately following, that is
+ // Check for an alphanumeric character immediately following, that is
// most likely a typo.
if (strict && ptr - (const char *)start != maxlen && ASCII_ISALNUM(*ptr)) {
return;
diff --git a/src/nvim/context.c b/src/nvim/context.c
index 614a3ce30e..9434b4e0ea 100644
--- a/src/nvim/context.c
+++ b/src/nvim/context.c
@@ -256,7 +256,8 @@ static inline void ctx_save_funcs(Context *ctx, bool scriptonly)
size_t cmd_len = sizeof("func! ") + STRLEN(name);
char *cmd = xmalloc(cmd_len);
snprintf(cmd, cmd_len, "func! %s", name);
- String func_body = nvim_exec(cstr_as_string(cmd), true, &err);
+ String func_body = nvim_exec(VIML_INTERNAL_CALL, cstr_as_string(cmd),
+ true, &err);
xfree(cmd);
if (!ERROR_SET(&err)) {
ADD(ctx->funcs, STRING_OBJ(func_body));
diff --git a/src/nvim/cursor.c b/src/nvim/cursor.c
index 6e2c6232d7..55f55a46b2 100644
--- a/src/nvim/cursor.c
+++ b/src/nvim/cursor.c
@@ -15,6 +15,7 @@
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/move.h"
+#include "nvim/option.h"
#include "nvim/plines.h"
#include "nvim/screen.h"
#include "nvim/state.h"
@@ -110,7 +111,7 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a
|| (State & TERM_FOCUS)
|| restart_edit != NUL
|| (VIsual_active && *p_sel != 'o')
- || ((ve_flags & VE_ONEMORE) && wcol < MAXCOL);
+ || ((get_ve_flags() & VE_ONEMORE) && wcol < MAXCOL);
line = ml_get_buf(curbuf, pos->lnum, false);
if (wcol >= MAXCOL) {
@@ -366,6 +367,7 @@ void check_cursor_col_win(win_T *win)
colnr_T len;
colnr_T oldcol = win->w_cursor.col;
colnr_T oldcoladd = win->w_cursor.col + win->w_cursor.coladd;
+ unsigned int cur_ve_flags = get_ve_flags();
len = (colnr_T)STRLEN(ml_get_buf(win->w_buffer, win->w_cursor.lnum, false));
if (len == 0) {
@@ -377,7 +379,7 @@ void check_cursor_col_win(win_T *win)
* - 'virtualedit' is set */
if ((State & INSERT) || restart_edit
|| (VIsual_active && *p_sel != 'o')
- || (ve_flags & VE_ONEMORE)
+ || (cur_ve_flags & VE_ONEMORE)
|| virtual_active()) {
win->w_cursor.col = len;
} else {
@@ -394,7 +396,7 @@ void check_cursor_col_win(win_T *win)
// line.
if (oldcol == MAXCOL) {
win->w_cursor.coladd = 0;
- } else if (ve_flags == VE_ALL) {
+ } else if (cur_ve_flags == VE_ALL) {
if (oldcoladd > win->w_cursor.col) {
win->w_cursor.coladd = oldcoladd - win->w_cursor.col;
diff --git a/src/nvim/debugger.c b/src/nvim/debugger.c
index b6e35f3047..75ebf0084e 100644
--- a/src/nvim/debugger.c
+++ b/src/nvim/debugger.c
@@ -268,7 +268,7 @@ void do_debug(char_u *cmd)
DOCMD_VERBOSE|DOCMD_EXCRESET);
debug_break_level = n;
}
- lines_left = (int)(Rows - 1);
+ lines_left = Rows - 1;
}
xfree(cmdline);
@@ -277,7 +277,7 @@ void do_debug(char_u *cmd)
redraw_all_later(NOT_VALID);
need_wait_return = false;
msg_scroll = save_msg_scroll;
- lines_left = (int)(Rows - 1);
+ lines_left = Rows - 1;
State = save_State;
debug_mode = false;
did_emsg = save_did_emsg;
diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c
index c0f3c32f93..935b233752 100644
--- a/src/nvim/decoration.c
+++ b/src/nvim/decoration.c
@@ -13,8 +13,6 @@
# include "decoration.c.generated.h"
#endif
-static PMap(uint64_t) hl_decors;
-
/// Add highlighting to a buffer, bounded by two cursor positions,
/// with an offset.
///
@@ -33,9 +31,9 @@ void bufhl_add_hl_pos_offset(buf_T *buf, int src_id, int hl_id, lpos_T pos_start
{
colnr_T hl_start = 0;
colnr_T hl_end = 0;
- Decoration *decor = decor_hl(hl_id);
+ Decoration decor = DECORATION_INIT;
+ decor.hl_id = hl_id;
- decor->priority = DECOR_PRIORITY_BASE;
// TODO(bfredl): if decoration had blocky mode, we could avoid this loop
for (linenr_T lnum = pos_start.lnum; lnum <= pos_end.lnum; lnum++) {
int end_off = 0;
@@ -59,40 +57,23 @@ void bufhl_add_hl_pos_offset(buf_T *buf, int src_id, int hl_id, lpos_T pos_start
hl_start = pos_start.col + offset;
hl_end = pos_end.col + offset;
}
- (void)extmark_set(buf, (uint64_t)src_id, NULL,
- (int)lnum-1, hl_start, (int)lnum-1+end_off, hl_end,
- decor, true, false, kExtmarkNoUndo);
- }
-}
-
-Decoration *decor_hl(int hl_id)
-{
- assert(hl_id > 0);
- Decoration **dp = (Decoration **)pmap_ref(uint64_t)(&hl_decors,
- (uint64_t)hl_id, true);
- if (*dp) {
- return *dp;
+ extmark_set(buf, (uint32_t)src_id, NULL,
+ (int)lnum-1, hl_start, (int)lnum-1+end_off, hl_end,
+ &decor, true, false, kExtmarkNoUndo);
}
-
- Decoration *decor = xcalloc(1, sizeof(*decor));
- decor->hl_id = hl_id;
- decor->shared = true;
- decor->priority = DECOR_PRIORITY_BASE;
- *dp = decor;
- return decor;
}
void decor_redraw(buf_T *buf, int row1, int row2, Decoration *decor)
{
- if (decor->hl_id && row2 >= row1) {
+ if ((!decor || decor->hl_id) && row2 >= row1) {
redraw_buf_range_later(buf, row1+1, row2+1);
}
- if (kv_size(decor->virt_text)) {
+ if (decor && kv_size(decor->virt_text)) {
redraw_buf_line_later(buf, row1+1);
}
- if (kv_size(decor->virt_lines)) {
+ if (decor && kv_size(decor->virt_lines)) {
redraw_buf_line_later(buf, MIN(buf->b_ml.ml_line_count,
row1+1+(decor->virt_lines_above?0:1)));
}
@@ -100,17 +81,17 @@ void decor_redraw(buf_T *buf, int row1, int row2, Decoration *decor)
void decor_remove(buf_T *buf, int row, int row2, Decoration *decor)
{
- if (kv_size(decor->virt_lines)) {
+ decor_redraw(buf, row, row2, decor);
+ if (decor && kv_size(decor->virt_lines)) {
assert(buf->b_virt_line_blocks > 0);
buf->b_virt_line_blocks--;
}
- decor_redraw(buf, row, row2, decor);
decor_free(decor);
}
void decor_free(Decoration *decor)
{
- if (decor && !decor->shared) {
+ if (decor) {
clear_virttext(&decor->virt_text);
for (size_t i = 0; i < kv_size(decor->virt_lines); i++) {
clear_virttext(&kv_A(decor->virt_lines, i).line);
@@ -134,17 +115,16 @@ Decoration *decor_find_virttext(buf_T *buf, int row, uint64_t ns_id)
MarkTreeIter itr[1] = { 0 };
marktree_itr_get(buf->b_marktree, row, 0, itr);
while (true) {
- mtmark_t mark = marktree_itr_current(itr);
- if (mark.row < 0 || mark.row > row) {
+ mtkey_t mark = marktree_itr_current(itr);
+ if (mark.pos.row < 0 || mark.pos.row > row) {
break;
- } else if (marktree_decor_level(mark.id) < kDecorLevelVisible) {
+ } else if (marktree_decor_level(mark) < kDecorLevelVisible) {
goto next_mark;
}
- ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index,
- mark.id, false);
- if (item && (ns_id == 0 || ns_id == item->ns_id)
- && item->decor && kv_size(item->decor->virt_text)) {
- return item->decor;
+ Decoration *decor = mark.decor_full;
+ if ((ns_id == 0 || ns_id == mark.ns)
+ && decor && kv_size(decor->virt_text)) {
+ return decor;
}
next_mark:
marktree_itr_next(buf->b_marktree, itr);
@@ -163,7 +143,20 @@ bool decor_redraw_reset(buf_T *buf, DecorState *state)
}
}
kv_size(state->active) = 0;
- return map_size(buf->b_extmark_index);
+ return buf->b_marktree->n_keys;
+}
+
+Decoration get_decor(mtkey_t mark)
+{
+ if (mark.decor_full) {
+ return *mark.decor_full;
+ } else {
+ Decoration fake = DECORATION_INIT;
+ fake.hl_id = mark.hl_id;
+ fake.priority = mark.priority;
+ fake.hl_eol = (mark.flags & MT_FLAG_HL_EOL);
+ return fake;
+ }
}
@@ -176,42 +169,35 @@ bool decor_redraw_start(buf_T *buf, int top_row, DecorState *state)
}
marktree_itr_rewind(buf->b_marktree, state->itr);
while (true) {
- mtmark_t mark = marktree_itr_current(state->itr);
- if (mark.row < 0) { // || mark.row > end_row
+ mtkey_t mark = marktree_itr_current(state->itr);
+ if (mark.pos.row < 0) { // || mark.row > end_row
break;
}
- if ((mark.row < top_row && mark.id&MARKTREE_END_FLAG)
- || marktree_decor_level(mark.id) < kDecorLevelVisible) {
+ if ((mark.pos.row < top_row && mt_end(mark))
+ || marktree_decor_level(mark) < kDecorLevelVisible) {
goto next_mark;
}
- uint64_t start_id = mark.id & ~MARKTREE_END_FLAG;
- ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index,
- start_id, false);
- if (!item || !item->decor) {
- goto next_mark;
- }
- Decoration *decor = item->decor;
+ Decoration decor = get_decor(mark);
- mtpos_t altpos = marktree_lookup(buf->b_marktree,
- mark.id^MARKTREE_END_FLAG, NULL);
+ mtpos_t altpos = marktree_get_altpos(buf->b_marktree, mark, NULL);
- if ((!(mark.id&MARKTREE_END_FLAG) && altpos.row < top_row
- && !kv_size(decor->virt_text))
- || ((mark.id&MARKTREE_END_FLAG) && altpos.row >= top_row)) {
+ if ((!mt_end(mark) && altpos.row < top_row
+ && !kv_size(decor.virt_text))
+ || (mt_end(mark) && altpos.row >= top_row)) {
goto next_mark;
}
- if (mark.id&MARKTREE_END_FLAG) {
- decor_add(state, altpos.row, altpos.col, mark.row, mark.col,
- decor, false);
+ if (mt_end(mark)) {
+ decor_add(state, altpos.row, altpos.col, mark.pos.row, mark.pos.col,
+ &decor, false);
} else {
if (altpos.row == -1) {
- altpos.row = mark.row;
- altpos.col = mark.col;
+ altpos.row = mark.pos.row;
+ altpos.col = mark.pos.col;
}
- decor_add(state, mark.row, mark.col, altpos.row, altpos.col,
- decor, false);
+ decor_add(state, mark.pos.row, mark.pos.col, altpos.row, altpos.col,
+ &decor, false);
}
next_mark:
@@ -266,43 +252,36 @@ int decor_redraw_col(buf_T *buf, int col, int win_col, bool hidden, DecorState *
while (true) {
// TODO(bfredl): check duplicate entry in "intersection"
// branch
- mtmark_t mark = marktree_itr_current(state->itr);
- if (mark.row < 0 || mark.row > state->row) {
+ mtkey_t mark = marktree_itr_current(state->itr);
+ if (mark.pos.row < 0 || mark.pos.row > state->row) {
break;
- } else if (mark.row == state->row && mark.col > col) {
- state->col_until = mark.col-1;
+ } else if (mark.pos.row == state->row && mark.pos.col > col) {
+ state->col_until = mark.pos.col-1;
break;
}
- if ((mark.id&MARKTREE_END_FLAG)
- || marktree_decor_level(mark.id) < kDecorLevelVisible) {
+ if (mt_end(mark)
+ || marktree_decor_level(mark) < kDecorLevelVisible) {
goto next_mark;
}
- ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index,
- mark.id, false);
- if (!item || !item->decor) {
- goto next_mark;
- }
- Decoration *decor = item->decor;
+ Decoration decor = get_decor(mark);
- mtpos_t endpos = marktree_lookup(buf->b_marktree,
- mark.id|MARKTREE_END_FLAG, NULL);
+ mtpos_t endpos = marktree_get_altpos(buf->b_marktree, mark, NULL);
if (endpos.row == -1) {
- endpos.row = mark.row;
- endpos.col = mark.col;
+ endpos = mark.pos;
}
- if (endpos.row < mark.row
- || (endpos.row == mark.row && endpos.col <= mark.col)) {
- if (!kv_size(decor->virt_text)) {
+ if (endpos.row < mark.pos.row
+ || (endpos.row == mark.pos.row && endpos.col <= mark.pos.col)) {
+ if (!kv_size(decor.virt_text)) {
goto next_mark;
}
}
- decor_add(state, mark.row, mark.col, endpos.row, endpos.col,
- decor, false);
+ decor_add(state, mark.pos.row, mark.pos.col, endpos.row, endpos.col,
+ &decor, false);
next_mark:
marktree_itr_next(buf->b_marktree, state->itr);
@@ -452,18 +431,18 @@ int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines)
MarkTreeIter itr[1] = { 0 };
marktree_itr_get(buf->b_marktree, row, 0, itr);
while (true) {
- mtmark_t mark = marktree_itr_current(itr);
- if (mark.row < 0 || mark.row >= end_row) {
+ mtkey_t mark = marktree_itr_current(itr);
+ if (mark.pos.row < 0 || mark.pos.row >= end_row) {
break;
- } else if (marktree_decor_level(mark.id) < kDecorLevelVirtLine) {
+ } else if (marktree_decor_level(mark) < kDecorLevelVirtLine) {
goto next_mark;
}
- bool above = mark.row > (int)(lnum - 2);
- ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark.id, false);
- if (item && item->decor && item->decor->virt_lines_above == above) {
- virt_lines += (int)kv_size(item->decor->virt_lines);
+ bool above = mark.pos.row > (int)(lnum - 2);
+ Decoration *decor = mark.decor_full;
+ if (decor && decor->virt_lines_above == above) {
+ virt_lines += (int)kv_size(decor->virt_lines);
if (lines) {
- kv_splice(*lines, item->decor->virt_lines);
+ kv_splice(*lines, decor->virt_lines);
}
}
next_mark:
diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h
index 611b4223da..02472d09e4 100644
--- a/src/nvim/decoration.h
+++ b/src/nvim/decoration.h
@@ -17,6 +17,8 @@ typedef enum {
kVTRightAlign,
} VirtTextPos;
+EXTERN const char *const virt_text_pos_str[] INIT(= { "eol", "overlay", "win_col", "right_align" });
+
typedef enum {
kHlModeUnknown,
kHlModeReplace,
@@ -24,6 +26,8 @@ typedef enum {
kHlModeBlend,
} HlMode;
+EXTERN const char *const hl_mode_str[] INIT(= { "", "replace", "combine", "blend" });
+
typedef kvec_t(VirtTextChunk) VirtText;
#define VIRTTEXT_EMPTY ((VirtText)KV_INITIAL_VALUE)
@@ -42,7 +46,6 @@ struct Decoration {
// TODO(bfredl): at some point turn this into FLAGS
bool virt_text_hide;
bool hl_eol;
- bool shared; // shared decoration, don't free
bool virt_lines_above;
// TODO(bfredl): style, signs, etc
DecorPriority priority;
@@ -50,7 +53,7 @@ struct Decoration {
int virt_text_width; // width of virt_text
};
#define DECORATION_INIT { KV_INITIAL_VALUE, KV_INITIAL_VALUE, 0, kVTEndOfLine, kHlModeUnknown, \
- false, false, false, false, DECOR_PRIORITY_BASE, 0, 0 }
+ false, false, false, DECOR_PRIORITY_BASE, 0, 0 }
typedef struct {
int start_row;
diff --git a/src/nvim/diff.c b/src/nvim/diff.c
index 0233b3a5ab..80bd3229c6 100644
--- a/src/nvim/diff.c
+++ b/src/nvim/diff.c
@@ -37,8 +37,8 @@
#include "nvim/path.h"
#include "nvim/screen.h"
#include "nvim/strings.h"
-#include "nvim/undo.h"
#include "nvim/ui.h"
+#include "nvim/undo.h"
#include "nvim/vim.h"
#include "nvim/window.h"
#include "xdiff/xdiff.h"
@@ -82,6 +82,14 @@ typedef struct {
garray_T dout_ga; // used for internal diff
} diffout_T;
+// used for recording hunks from xdiff
+typedef struct {
+ linenr_T lnum_orig;
+ long count_orig;
+ linenr_T lnum_new;
+ long count_new;
+} diffhunk_T;
+
// two diff inputs and one result
typedef struct {
diffin_T dio_orig; // original file input
@@ -674,11 +682,11 @@ void diff_redraw(bool dofold)
}
if (wp_other != NULL && curwin->w_p_scb) {
if (used_max_fill_curwin) {
- // The current window was set to used the maximum number of filler
+ // The current window was set to use the maximum number of filler
// lines, may need to reduce them.
diff_set_topline(wp_other, curwin);
} else if (used_max_fill_other) {
- // The other window was set to used the maximum number of filler
+ // The other window was set to use the maximum number of filler
// lines, may need to reduce them.
diff_set_topline(curwin, wp_other);
}
@@ -782,9 +790,14 @@ static int diff_write(buf_T *buf, diffin_T *din)
// Always use 'fileformat' set to "unix".
char_u *save_ff = buf->b_p_ff;
buf->b_p_ff = vim_strsave((char_u *)FF_UNIX);
+ const bool save_lockmarks = cmdmod.lockmarks;
+ // Writing the buffer is an implementation detail of performing the diff,
+ // so it shouldn't update the '[ and '] marks.
+ cmdmod.lockmarks = true;
int r = buf_write(buf, din->din_fname, NULL,
(linenr_T)1, buf->b_ml.ml_line_count,
NULL, false, false, false, true);
+ cmdmod.lockmarks = save_lockmarks;
free_string_option(buf->b_p_ff);
buf->b_p_ff = save_ff;
return r;
@@ -852,7 +865,7 @@ static void diff_try_update(diffio_T *dio, int idx_orig, exarg_T *eap)
}
// Read the diff output and add each entry to the diff list.
- diff_read(idx_orig, idx_new, &dio->dio_diff);
+ diff_read(idx_orig, idx_new, dio);
clear_diffin(&dio->dio_new);
clear_diffout(&dio->dio_diff);
@@ -1078,7 +1091,7 @@ static int diff_file_internal(diffio_T *diffio)
emit_cfg.ctxlen = 0; // don't need any diff_context here
emit_cb.priv = &diffio->dio_diff;
- emit_cb.out_line = xdiff_out;
+ emit_cfg.hunk_func = xdiff_out;
if (xdl_diff(&diffio->dio_orig.din_mmfile,
&diffio->dio_new.din_mmfile,
&param, &emit_cfg, &emit_cb) < 0) {
@@ -1272,7 +1285,7 @@ void ex_diffpatch(exarg_T *eap)
ex_file(eap);
// Do filetype detection with the new name.
- if (au_has_group((char_u *)"filetypedetect")) {
+ if (augroup_exists("filetypedetect")) {
do_cmdline_cmd(":doau filetypedetect BufRead");
}
}
@@ -1508,7 +1521,7 @@ void ex_diffoff(exarg_T *eap)
diff_clear(curtab);
}
- // Remove "hor" from from 'scrollopt' if there are no diff windows left.
+ // Remove "hor" from 'scrollopt' if there are no diff windows left.
if (!diffwin && (vim_strchr(p_sbo, 'h') != NULL)) {
do_cmdline_cmd("set sbo-=hor");
}
@@ -1519,20 +1532,20 @@ void ex_diffoff(exarg_T *eap)
/// @param idx_orig idx of original file
/// @param idx_new idx of new file
/// @dout diff output
-static void diff_read(int idx_orig, int idx_new, diffout_T *dout)
+static void diff_read(int idx_orig, int idx_new, diffio_T *dio)
{
FILE *fd = NULL;
int line_idx = 0;
diff_T *dprev = NULL;
diff_T *dp = curtab->tp_first_diff;
diff_T *dn, *dpl;
+ diffout_T *dout = &dio->dio_diff;
char_u linebuf[LBUFLEN]; // only need to hold the diff line
char_u *line;
long off;
int i;
- linenr_T lnum_orig, lnum_new;
- long count_orig, count_new;
int notset = true; // block "*dp" not set yet
+ diffhunk_T *hunk;
enum {
DIFF_ED,
DIFF_UNIFIED,
@@ -1549,70 +1562,79 @@ static void diff_read(int idx_orig, int idx_new, diffout_T *dout)
}
}
+ if (!dio->dio_internal) {
+ hunk = xmalloc(sizeof(*hunk));
+ }
+
for (;;) {
- if (fd == NULL) {
+ if (dio->dio_internal) {
if (line_idx >= dout->dout_ga.ga_len) {
break; // did last line
}
- line = ((char_u **)dout->dout_ga.ga_data)[line_idx++];
+ hunk = ((diffhunk_T **)dout->dout_ga.ga_data)[line_idx++];
} else {
- if (vim_fgets(linebuf, LBUFLEN, fd)) {
- break; // end of file
- }
- line = linebuf;
- }
-
- if (diffstyle == DIFF_NONE) {
- // Determine diff style.
- // ed like diff looks like this:
- // {first}[,{last}]c{first}[,{last}]
- // {first}a{first}[,{last}]
- // {first}[,{last}]d{first}
- //
- // unified diff looks like this:
- // --- file1 2018-03-20 13:23:35.783153140 +0100
- // +++ file2 2018-03-20 13:23:41.183156066 +0100
- // @@ -1,3 +1,5 @@
- if (isdigit(*line)) {
- diffstyle = DIFF_ED;
- } else if ((STRNCMP(line, "@@ ", 3) == 0)) {
- diffstyle = DIFF_UNIFIED;
- } else if ((STRNCMP(line, "--- ", 4) == 0) // -V501
- && (vim_fgets(linebuf, LBUFLEN, fd) == 0) // -V501
- && (STRNCMP(line, "+++ ", 4) == 0)
- && (vim_fgets(linebuf, LBUFLEN, fd) == 0) // -V501
- && (STRNCMP(line, "@@ ", 3) == 0)) {
- diffstyle = DIFF_UNIFIED;
+ if (fd == NULL) {
+ if (line_idx >= dout->dout_ga.ga_len) {
+ break; // did last line
+ }
+ line = ((char_u **)dout->dout_ga.ga_data)[line_idx++];
} else {
- // Format not recognized yet, skip over this line. Cygwin diff
- // may put a warning at the start of the file.
- continue;
+ if (vim_fgets(linebuf, LBUFLEN, fd)) {
+ break; // end of file
+ }
+ line = linebuf;
}
- }
- if (diffstyle == DIFF_ED) {
- if (!isdigit(*line)) {
- continue; // not the start of a diff block
- }
- if (parse_diff_ed(line, &lnum_orig, &count_orig,
- &lnum_new, &count_new) == FAIL) {
- continue;
- }
- } else {
- assert(diffstyle == DIFF_UNIFIED);
- if (STRNCMP(line, "@@ ", 3) != 0) {
- continue; // not the start of a diff block
+ if (diffstyle == DIFF_NONE) {
+ // Determine diff style.
+ // ed like diff looks like this:
+ // {first}[,{last}]c{first}[,{last}]
+ // {first}a{first}[,{last}]
+ // {first}[,{last}]d{first}
+ //
+ // unified diff looks like this:
+ // --- file1 2018-03-20 13:23:35.783153140 +0100
+ // +++ file2 2018-03-20 13:23:41.183156066 +0100
+ // @@ -1,3 +1,5 @@
+ if (isdigit(*line)) {
+ diffstyle = DIFF_ED;
+ } else if ((STRNCMP(line, "@@ ", 3) == 0)) {
+ diffstyle = DIFF_UNIFIED;
+ } else if ((STRNCMP(line, "--- ", 4) == 0) // -V501
+ && (vim_fgets(linebuf, LBUFLEN, fd) == 0) // -V501
+ && (STRNCMP(line, "+++ ", 4) == 0)
+ && (vim_fgets(linebuf, LBUFLEN, fd) == 0) // -V501
+ && (STRNCMP(line, "@@ ", 3) == 0)) {
+ diffstyle = DIFF_UNIFIED;
+ } else {
+ // Format not recognized yet, skip over this line. Cygwin diff
+ // may put a warning at the start of the file.
+ continue;
+ }
}
- if (parse_diff_unified(line, &lnum_orig, &count_orig,
- &lnum_new, &count_new) == FAIL) {
- continue;
+
+ if (diffstyle == DIFF_ED) {
+ if (!isdigit(*line)) {
+ continue; // not the start of a diff block
+ }
+ if (parse_diff_ed(line, hunk) == FAIL) {
+ continue;
+ }
+ } else {
+ assert(diffstyle == DIFF_UNIFIED);
+ if (STRNCMP(line, "@@ ", 3) != 0) {
+ continue; // not the start of a diff block
+ }
+ if (parse_diff_unified(line, hunk) == FAIL) {
+ continue;
+ }
}
}
// Go over blocks before the change, for which orig and new are equal.
// Copy blocks from orig to new.
while (dp != NULL
- && lnum_orig > dp->df_lnum[idx_orig] + dp->df_count[idx_orig]) {
+ && hunk->lnum_orig > dp->df_lnum[idx_orig] + dp->df_count[idx_orig]) {
if (notset) {
diff_copy_entry(dprev, dp, idx_orig, idx_new);
}
@@ -1622,19 +1644,19 @@ static void diff_read(int idx_orig, int idx_new, diffout_T *dout)
}
if ((dp != NULL)
- && (lnum_orig <= dp->df_lnum[idx_orig] + dp->df_count[idx_orig])
- && (lnum_orig + count_orig >= dp->df_lnum[idx_orig])) {
+ && (hunk->lnum_orig <= dp->df_lnum[idx_orig] + dp->df_count[idx_orig])
+ && (hunk->lnum_orig + hunk->count_orig >= dp->df_lnum[idx_orig])) {
// New block overlaps with existing block(s).
// First find last block that overlaps.
for (dpl = dp; dpl->df_next != NULL; dpl = dpl->df_next) {
- if (lnum_orig + count_orig < dpl->df_next->df_lnum[idx_orig]) {
+ if (hunk->lnum_orig + hunk->count_orig < dpl->df_next->df_lnum[idx_orig]) {
break;
}
}
// If the newly found block starts before the old one, set the
// start back a number of lines.
- off = dp->df_lnum[idx_orig] - lnum_orig;
+ off = dp->df_lnum[idx_orig] - hunk->lnum_orig;
if (off > 0) {
for (i = idx_orig; i < idx_new; ++i) {
@@ -1642,15 +1664,15 @@ static void diff_read(int idx_orig, int idx_new, diffout_T *dout)
dp->df_lnum[i] -= off;
}
}
- dp->df_lnum[idx_new] = lnum_new;
- dp->df_count[idx_new] = count_new;
+ dp->df_lnum[idx_new] = hunk->lnum_new;
+ dp->df_count[idx_new] = hunk->count_new;
} else if (notset) {
// new block inside existing one, adjust new block
- dp->df_lnum[idx_new] = lnum_new + off;
- dp->df_count[idx_new] = count_new - off;
+ dp->df_lnum[idx_new] = hunk->lnum_new + off;
+ dp->df_count[idx_new] = hunk->count_new - off;
} else {
// second overlap of new block with existing block
- dp->df_count[idx_new] += count_new - count_orig
+ dp->df_count[idx_new] += hunk->count_new - hunk->count_orig
+ dpl->df_lnum[idx_orig] +
dpl->df_count[idx_orig]
- (dp->df_lnum[idx_orig] +
@@ -1659,7 +1681,7 @@ static void diff_read(int idx_orig, int idx_new, diffout_T *dout)
// Adjust the size of the block to include all the lines to the
// end of the existing block or the new diff, whatever ends last.
- off = (lnum_orig + count_orig)
+ off = (hunk->lnum_orig + hunk->count_orig)
- (dpl->df_lnum[idx_orig] + dpl->df_count[idx_orig]);
if (off < 0) {
@@ -1691,10 +1713,10 @@ static void diff_read(int idx_orig, int idx_new, diffout_T *dout)
// Allocate a new diffblock.
dp = diff_alloc_new(curtab, dprev, dp);
- dp->df_lnum[idx_orig] = lnum_orig;
- dp->df_count[idx_orig] = count_orig;
- dp->df_lnum[idx_new] = lnum_new;
- dp->df_count[idx_new] = count_new;
+ dp->df_lnum[idx_orig] = hunk->lnum_orig;
+ dp->df_count[idx_orig] = hunk->count_orig;
+ dp->df_lnum[idx_new] = hunk->lnum_new;
+ dp->df_count[idx_new] = hunk->count_new;
// Set values for other buffers, these must be equal to the
// original buffer, otherwise there would have been a change
@@ -1718,6 +1740,10 @@ static void diff_read(int idx_orig, int idx_new, diffout_T *dout)
notset = true;
}
+ if (!dio->dio_internal) {
+ xfree(hunk);
+ }
+
if (fd != NULL) {
fclose(fd);
}
@@ -3026,8 +3052,7 @@ linenr_T diff_lnum_win(linenr_T lnum, win_T *wp)
/// Handle an ED style diff line.
/// Return FAIL if the line does not contain diff info.
///
-static int parse_diff_ed(char_u *line, linenr_T *lnum_orig, long *count_orig, linenr_T *lnum_new,
- long *count_new)
+static int parse_diff_ed(char_u *line, diffhunk_T *hunk)
{
char_u *p;
long f1, l1, f2, l2;
@@ -3061,18 +3086,18 @@ static int parse_diff_ed(char_u *line, linenr_T *lnum_orig, long *count_orig, li
}
if (difftype == 'a') {
- *lnum_orig = f1 + 1;
- *count_orig = 0;
+ hunk->lnum_orig = f1 + 1;
+ hunk->count_orig = 0;
} else {
- *lnum_orig = f1;
- *count_orig = l1 - f1 + 1;
+ hunk->lnum_orig = f1;
+ hunk->count_orig = l1 - f1 + 1;
}
if (difftype == 'd') {
- *lnum_new = f2 + 1;
- *count_new = 0;
+ hunk->lnum_new = f2 + 1;
+ hunk->count_new = 0;
} else {
- *lnum_new = f2;
- *count_new = l2 - f2 + 1;
+ hunk->lnum_new = f2;
+ hunk->count_new = l2 - f2 + 1;
}
return OK;
}
@@ -3081,8 +3106,7 @@ static int parse_diff_ed(char_u *line, linenr_T *lnum_orig, long *count_orig, li
/// Parses unified diff with zero(!) context lines.
/// Return FAIL if there is no diff information in "line".
///
-static int parse_diff_unified(char_u *line, linenr_T *lnum_orig, long *count_orig,
- linenr_T *lnum_new, long *count_new)
+static int parse_diff_unified(char_u *line, diffhunk_T *hunk)
{
char_u *p;
long oldline, oldcount, newline, newcount;
@@ -3120,10 +3144,10 @@ static int parse_diff_unified(char_u *line, linenr_T *lnum_orig, long *count_ori
newline = 1;
}
- *lnum_orig = oldline;
- *count_orig = oldcount;
- *lnum_new = newline;
- *count_new = newcount;
+ hunk->lnum_orig = oldline;
+ hunk->count_orig = oldcount;
+ hunk->lnum_new = newline;
+ hunk->count_new = newcount;
return OK;
}
@@ -3135,25 +3159,16 @@ static int parse_diff_unified(char_u *line, linenr_T *lnum_orig, long *count_ori
/// Callback function for the xdl_diff() function.
/// Stores the diff output in a grow array.
///
-static int xdiff_out(void *priv, mmbuffer_t *mb, int nbuf)
+static int xdiff_out(long start_a, long count_a, long start_b, long count_b, void *priv)
{
diffout_T *dout = (diffout_T *)priv;
- char_u *p;
-
- // The header line always comes by itself, text lines in at least two
- // parts. We drop the text part.
- if (nbuf > 1) {
- return 0;
- }
-
- // sanity check
- if (STRNCMP(mb[0].ptr, "@@ ", 3) != 0) {
- return 0;
- }
+ diffhunk_T *p = xmalloc(sizeof(*p));
ga_grow(&dout->dout_ga, 1);
-
- p = vim_strnsave((char_u *)mb[0].ptr, mb[0].size);
- ((char_u **)dout->dout_ga.ga_data)[dout->dout_ga.ga_len++] = p;
+ p->lnum_orig = start_a + 1;
+ p->count_orig = count_a;
+ p->lnum_new = start_b + 1;
+ p->count_new = count_b;
+ ((diffhunk_T **)dout->dout_ga.ga_data)[dout->dout_ga.ga_len++] = p;
return 0;
}
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index 2e3eec3642..00ffa7cba1 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -260,7 +260,7 @@ static colnr_T Insstart_blank_vcol; // vcol for first inserted blank
static bool update_Insstart_orig = true; // set Insstart_orig to Insstart
static char_u *last_insert = NULL; // the text of the previous insert,
- // K_SPECIAL and CSI are escaped
+ // K_SPECIAL is escaped
static int last_insert_skip; // nr of chars in front of previous insert
static int new_insert_skip; // nr of chars in front of current insert
static int did_restart_edit; // "restart_edit" when calling edit()
@@ -663,8 +663,12 @@ static int insert_execute(VimState *state, int key)
InsertState *const s = (InsertState *)state;
if (stop_insert_mode) {
// Insert mode ended, possibly from a callback.
+ if (key != K_IGNORE && key != K_NOP) {
+ vungetc(key);
+ }
s->count = 0;
s->nomove = true;
+ ins_compl_prep(ESC);
return 0;
}
@@ -909,7 +913,7 @@ static int insert_handle_key(InsertState *s)
ins_ctrl_o();
// don't move the cursor left when 'virtualedit' has "onemore".
- if (ve_flags & VE_ONEMORE) {
+ if (get_ve_flags() & VE_ONEMORE) {
ins_at_eol = false;
s->nomove = true;
}
@@ -1076,13 +1080,21 @@ static int insert_handle_key(InsertState *s)
case K_COMMAND: // some command
do_cmdline(NULL, getcmdkeycmd, NULL, 0);
+ goto check_pum;
+
+ case K_LUA:
+ map_execute_lua();
check_pum:
+ // nvim_select_popupmenu_item() can be called from the handling of
+ // K_EVENT, K_COMMAND, or K_LUA.
// TODO(bfredl): Not entirely sure this indirection is necessary
// but doing like this ensures using nvim_select_popupmenu_item is
// equivalent to selecting the item with a typed key.
if (pum_want.active) {
if (pum_visible()) {
+ // Set this to NULL so that ins_complete() will update the message.
+ edit_submode_extra = NULL;
insert_do_complete(s);
if (pum_want.finish) {
// accept the item and stop completion
@@ -1600,8 +1612,8 @@ static void ins_ctrl_v(void)
*/
static int pc_status;
#define PC_STATUS_UNSET 0 // pc_bytes was not set
-#define PC_STATUS_RIGHT 1 // right halve of double-wide char
-#define PC_STATUS_LEFT 2 // left halve of double-wide char
+#define PC_STATUS_RIGHT 1 // right half of double-wide char
+#define PC_STATUS_LEFT 2 // left half of double-wide char
#define PC_STATUS_SET 3 // pc_bytes was filled
static char_u pc_bytes[MB_MAXBYTES + 1]; // saved bytes
static int pc_attr;
@@ -3616,7 +3628,7 @@ static bool ins_compl_prep(int c)
// Ignore end of Select mode mapping and mouse scroll buttons.
if (c == K_SELECT || c == K_MOUSEDOWN || c == K_MOUSEUP
|| c == K_MOUSELEFT || c == K_MOUSERIGHT || c == K_EVENT
- || c == K_COMMAND) {
+ || c == K_COMMAND || c == K_LUA) {
return retval;
}
@@ -4982,7 +4994,7 @@ void ins_compl_check_keys(int frequency, int in_compl_func)
*/
static int ins_compl_key2dir(int c)
{
- if (c == K_EVENT || c == K_COMMAND) {
+ if (c == K_EVENT || c == K_COMMAND || c == K_LUA) {
return pum_want.item < pum_selected_item ? BACKWARD : FORWARD;
}
if (c == Ctrl_P || c == Ctrl_L
@@ -5012,7 +5024,7 @@ static int ins_compl_key2count(int c)
{
int h;
- if (c == K_EVENT || c == K_COMMAND) {
+ if (c == K_EVENT || c == K_COMMAND || c == K_LUA) {
int offset = pum_want.item - pum_selected_item;
return abs(offset);
}
@@ -5046,6 +5058,7 @@ static bool ins_compl_use_match(int c)
return false;
case K_EVENT:
case K_COMMAND:
+ case K_LUA:
return pum_want.active && pum_want.insert;
}
return true;
@@ -5624,8 +5637,12 @@ int get_literal(void)
i = 0;
for (;;) {
nc = plain_vgetc();
- if (!(State & CMDLINE)
- && MB_BYTE2LEN_CHECK(nc) == 1) {
+ if ((mod_mask & ~MOD_MASK_SHIFT) != 0) {
+ // A character with non-Shift modifiers should not be a valid
+ // character for i_CTRL-V_digit.
+ break;
+ }
+ if (!(State & CMDLINE) && MB_BYTE2LEN_CHECK(nc) == 1) {
add_to_showcmd(nc);
}
if (nc == 'x' || nc == 'X') {
@@ -5691,6 +5708,8 @@ int get_literal(void)
--no_mapping;
if (nc) {
vungetc(nc);
+ // A character typed with i_CTRL-V_digit cannot have modifiers.
+ mod_mask = 0;
}
got_int = false; // CTRL-C typed after CTRL-V is not an interrupt
return cc;
@@ -6002,6 +6021,7 @@ static void internal_format(int textwidth, int second_indent, int flags, int for
char_u *saved_text = NULL;
colnr_T col;
colnr_T end_col;
+ bool did_do_comment = false;
virtcol = get_nolist_virtcol()
+ char2cells(c != NUL ? c : gchar_cursor());
@@ -6117,8 +6137,7 @@ static void internal_format(int textwidth, int second_indent, int flags, int for
if (curwin->w_cursor.col <= (colnr_T)wantcol) {
break;
}
- } else if ((cc >= 0x100 || !utf_allow_break_before(cc))
- && fo_multibyte) {
+ } else if ((cc >= 0x100 || !utf_allow_break_before(cc)) && fo_multibyte) {
int ncc;
bool allow_break;
@@ -6275,11 +6294,18 @@ static void internal_format(int textwidth, int second_indent, int flags, int for
+ (fo_white_par ? OPENLINE_KEEPTRAIL : 0)
+ (do_comments ? OPENLINE_DO_COM : 0)
+ ((flags & INSCHAR_COM_LIST) ? OPENLINE_COM_LIST : 0),
- ((flags & INSCHAR_COM_LIST) ? second_indent : old_indent));
+ ((flags & INSCHAR_COM_LIST) ? second_indent : old_indent),
+ &did_do_comment);
if (!(flags & INSCHAR_COM_LIST)) {
old_indent = 0;
}
+ // If a comment leader was inserted, may also do this on a following
+ // line.
+ if (did_do_comment) {
+ no_leader = false;
+ }
+
replace_offset = 0;
if (first_line) {
if (!(flags & INSCHAR_COM_LIST)) {
@@ -6810,7 +6836,7 @@ void free_last_insert(void)
/// Add character "c" to buffer "s"
///
-/// Escapes the special meaning of K_SPECIAL and CSI, handles multi-byte
+/// Escapes the special meaning of K_SPECIAL, handles multi-byte
/// characters.
///
/// @param[in] c Character to add.
@@ -6824,7 +6850,7 @@ char_u *add_char2buf(int c, char_u *s)
const int len = utf_char2bytes(c, temp);
for (int i = 0; i < len; i++) {
c = temp[i];
- // Need to escape K_SPECIAL and CSI like in the typeahead buffer.
+ // Need to escape K_SPECIAL like in the typeahead buffer.
if (c == K_SPECIAL) {
*s++ = K_SPECIAL;
*s++ = KS_SPECIAL;
@@ -6898,8 +6924,7 @@ int oneright(void)
// move "l" bytes right, but don't end up on the NUL, unless 'virtualedit'
// contains "onemore".
- if (ptr[l] == NUL
- && (ve_flags & VE_ONEMORE) == 0) {
+ if (ptr[l] == NUL && (get_ve_flags() & VE_ONEMORE) == 0) {
return FAIL;
}
curwin->w_cursor.col += l;
@@ -7106,9 +7131,7 @@ int stuff_inserted(int c, long count, int no_esc)
stuffReadbuff((const char *)ptr);
// A trailing "0" is inserted as "<C-V>048", "^" as "<C-V>^".
if (last) {
- stuffReadbuff((last == '0'
- ? "\026\060\064\070"
- : "\026^"));
+ stuffReadbuff(last == '0' ? "\026\060\064\070" : "\026^");
}
} while (--count > 0);
@@ -8021,7 +8044,7 @@ static bool ins_esc(long *count, int cmdchar, bool nomove)
&& !VIsual_active
))
&& !revins_on) {
- if (curwin->w_cursor.coladd > 0 || ve_flags == VE_ALL) {
+ if (curwin->w_cursor.coladd > 0 || get_ve_flags() == VE_ALL) {
oneleft();
if (restart_edit != NUL) {
curwin->w_cursor.coladd++;
@@ -8274,6 +8297,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
int in_indent;
int oldState;
int cpc[MAX_MCO]; // composing characters
+ bool call_fix_indent = false;
// can't delete anything in an empty file
// can't backup past first character in buffer
@@ -8417,6 +8441,8 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
beginline(BL_WHITE);
if (curwin->w_cursor.col < save_col) {
mincol = curwin->w_cursor.col;
+ // should now fix the indent to match with the previous line
+ call_fix_indent = true;
}
curwin->w_cursor.col = save_col;
}
@@ -8551,6 +8577,11 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
if (curwin->w_cursor.col <= 1) {
did_ai = false;
}
+
+ if (call_fix_indent) {
+ fix_indent();
+ }
+
// It's a little strange to put backspaces into the redo
// buffer, but it makes auto-indent a lot easier to deal
// with.
@@ -9165,7 +9196,7 @@ static bool ins_eol(int c)
AppendToRedobuff(NL_STR);
bool i = open_line(FORWARD,
has_format_option(FO_RET_COMS) ? OPENLINE_DO_COM : 0,
- old_indent);
+ old_indent, NULL);
old_indent = 0;
can_cindent = true;
// When inserting a line the cursor line must never be in a closed fold.
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 86384bc5b2..d95b9560c2 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -6,6 +6,7 @@
*/
#include <math.h>
+#include <stdlib.h>
#include "auto/config.h"
@@ -69,6 +70,7 @@ static char *e_nowhitespace
static char *e_invalwindow = N_("E957: Invalid window number");
static char *e_lock_unlock = N_("E940: Cannot lock or unlock variable %s");
static char *e_write2 = N_("E80: Error while writing: %s");
+static char *e_string_list_or_blob_required = N_("E1098: String, List or Blob required");
// TODO(ZyX-I): move to eval/executor
static char *e_letwrong = N_("E734: Wrong variable type for %s=");
@@ -112,9 +114,11 @@ typedef struct {
int fi_semicolon; // TRUE if ending in '; var]'
int fi_varcount; // nr of variables in the list
listwatch_T fi_lw; // keep an eye on the item used.
- list_T *fi_list; // list being used
+ list_T *fi_list; // list being used
int fi_bi; // index of blob
blob_T *fi_blob; // blob being used
+ char_u *fi_string; // copy of string being used
+ int fi_byte_idx; // byte index in fi_string
} forinfo_T;
// values for vv_flags:
@@ -763,6 +767,15 @@ static int eval1_emsg(char_u **arg, typval_T *rettv, bool evaluate)
return ret;
}
+/// @return whether a typval is a valid expression to pass to eval_expr_typval()
+/// or eval_expr_to_bool(). An empty string returns false;
+bool eval_expr_valid_arg(const typval_T *const tv)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_CONST
+{
+ return tv->v_type != VAR_UNKNOWN
+ && (tv->v_type != VAR_STRING || (tv->vval.v_string != NULL && *tv->vval.v_string != NUL));
+}
+
int eval_expr_typval(const typval_T *expr, typval_T *argv, int argc, typval_T *rettv)
FUNC_ATTR_NONNULL_ARG(1, 2, 4)
{
@@ -2641,8 +2654,15 @@ void *eval_for_line(const char_u *arg, bool *errp, char_u **nextcmdp, int skip)
fi->fi_blob = btv.vval.v_blob;
}
tv_clear(&tv);
+ } else if (tv.v_type == VAR_STRING) {
+ fi->fi_byte_idx = 0;
+ fi->fi_string = tv.vval.v_string;
+ tv.vval.v_string = NULL;
+ if (fi->fi_string == NULL) {
+ fi->fi_string = vim_strsave((char_u *)"");
+ }
} else {
- emsg(_(e_listblobreq));
+ emsg(_(e_string_list_or_blob_required));
tv_clear(&tv);
}
}
@@ -2679,6 +2699,22 @@ bool next_for_item(void *fi_void, char_u *arg)
fi->fi_semicolon, fi->fi_varcount, false, NULL) == OK;
}
+ if (fi->fi_string != NULL) {
+ const int len = utfc_ptr2len(fi->fi_string + fi->fi_byte_idx);
+ if (len == 0) {
+ return false;
+ }
+ typval_T tv;
+ tv.v_type = VAR_STRING;
+ tv.v_lock = VAR_FIXED;
+ tv.vval.v_string = vim_strnsave(fi->fi_string + fi->fi_byte_idx, len);
+ fi->fi_byte_idx += len;
+ const int result
+ = ex_let_vars(arg, &tv, true, fi->fi_semicolon, fi->fi_varcount, false, NULL) == OK;
+ xfree(tv.vval.v_string);
+ return result;
+ }
+
listitem_T *item = fi->fi_lw.lw_item;
if (item == NULL) {
return false;
@@ -2698,12 +2734,16 @@ void free_for_info(void *fi_void)
{
forinfo_T *fi = (forinfo_T *)fi_void;
- if (fi != NULL && fi->fi_list != NULL) {
+ if (fi == NULL) {
+ return;
+ }
+ if (fi->fi_list != NULL) {
tv_list_watch_remove(fi->fi_list, &fi->fi_lw);
tv_list_unref(fi->fi_list);
- }
- if (fi != NULL && fi->fi_blob != NULL) {
+ } else if (fi->fi_blob != NULL) {
tv_blob_unref(fi->fi_blob);
+ } else {
+ xfree(fi->fi_string);
}
xfree(fi);
}
@@ -3218,9 +3258,8 @@ char_u *get_user_var_name(expand_T *xp, int idx)
// b: variables
// In cmdwin, the alternative buffer should be used.
- hashtab_T *ht = (cmdwin_type != 0 && get_cmdline_type() == NUL)
- ? &prevwin->w_buffer->b_vars->dv_hashtab
- : &curbuf->b_vars->dv_hashtab;
+ hashtab_T *ht
+ = is_in_cmdwin() ? &prevwin->w_buffer->b_vars->dv_hashtab : &curbuf->b_vars->dv_hashtab;
if (bdone < ht->ht_used) {
if (bdone++ == 0) {
hi = ht->ht_array;
@@ -3235,9 +3274,7 @@ char_u *get_user_var_name(expand_T *xp, int idx)
// w: variables
// In cmdwin, the alternative window should be used.
- ht = (cmdwin_type != 0 && get_cmdline_type() == NUL)
- ? &prevwin->w_vars->dv_hashtab
- : &curwin->w_vars->dv_hashtab;
+ ht = is_in_cmdwin() ? &prevwin->w_vars->dv_hashtab : &curwin->w_vars->dv_hashtab;
if (wdone < ht->ht_used) {
if (wdone++ == 0) {
hi = ht->ht_array;
@@ -4402,7 +4439,7 @@ static int eval_lambda(char_u **const arg, typval_T *const rettv, const bool eva
rettv->v_type = VAR_UNKNOWN;
int ret = get_lambda_tv(arg, rettv, evaluate);
- if (ret == NOTDONE) {
+ if (ret != OK) {
return FAIL;
} else if (**arg != '(') {
if (verbose) {
@@ -6468,6 +6505,10 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map)
if (argvars[0].v_type == VAR_DICT) {
vimvars[VV_KEY].vv_type = VAR_STRING;
+ const VarLockStatus prev_lock = d->dv_lock;
+ if (map && d->dv_lock == VAR_UNLOCKED) {
+ d->dv_lock = VAR_LOCKED;
+ }
ht = &d->dv_hashtab;
hash_lock(ht);
todo = (int)ht->ht_used;
@@ -6498,6 +6539,7 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map)
}
}
hash_unlock(ht);
+ d->dv_lock = prev_lock;
} else if (argvars[0].v_type == VAR_BLOB) {
vimvars[VV_KEY].vv_type = VAR_NUMBER;
@@ -6530,6 +6572,10 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map)
assert(argvars[0].v_type == VAR_LIST);
vimvars[VV_KEY].vv_type = VAR_NUMBER;
+ const VarLockStatus prev_lock = tv_list_locked(l);
+ if (map && tv_list_locked(l) == VAR_UNLOCKED) {
+ tv_list_set_lock(l, VAR_LOCKED);
+ }
for (listitem_T *li = tv_list_first(l); li != NULL;) {
if (map
&& var_check_lock(TV_LIST_ITEM_TV(li)->v_lock, arg_errmsg,
@@ -6548,6 +6594,7 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map)
}
idx++;
}
+ tv_list_set_lock(l, prev_lock);
}
restore_vimvar(VV_KEY, &save_key);
@@ -6956,10 +7003,9 @@ win_T *find_tabwin(typval_T *wvp, typval_T *tvp)
/// @param off 1 for gettabwinvar()
void getwinvar(typval_T *argvars, typval_T *rettv, int off)
{
- win_T *win, *oldcurwin;
+ win_T *win;
dictitem_T *v;
tabpage_T *tp = NULL;
- tabpage_T *oldtabpage = NULL;
bool done = false;
if (off == 1) {
@@ -6979,8 +7025,8 @@ void getwinvar(typval_T *argvars, typval_T *rettv, int off)
// otherwise the window is not valid. Only do this when needed,
// autocommands get blocked.
bool need_switch_win = tp != curtab || win != curwin;
- if (!need_switch_win
- || switch_win(&oldcurwin, &oldtabpage, win, tp, true) == OK) {
+ switchwin_T switchwin;
+ if (!need_switch_win || switch_win(&switchwin, win, tp, true) == OK) {
if (*varname == '&') {
if (varname[1] == NUL) {
// get all window-local options in a dict
@@ -7008,7 +7054,7 @@ void getwinvar(typval_T *argvars, typval_T *rettv, int off)
if (need_switch_win) {
// restore previous notion of curwin
- restore_win(oldcurwin, oldtabpage, true);
+ restore_win(&switchwin, true);
}
}
emsg_off--;
@@ -7299,12 +7345,19 @@ void mapblock_fill_dict(dict_T *const dict, const mapblock_T *const mp, long buf
noremap_value = mp->m_noremap == REMAP_SCRIPT ? 2 : !!mp->m_noremap;
}
- if (compatible) {
- tv_dict_add_str(dict, S_LEN("rhs"), (const char *)mp->m_orig_str);
+ if (mp->m_luaref != LUA_NOREF) {
+ tv_dict_add_nr(dict, S_LEN("callback"), mp->m_luaref);
} else {
- tv_dict_add_allocated_str(dict, S_LEN("rhs"),
- str2special_save((const char *)mp->m_str, false,
- true));
+ if (compatible) {
+ tv_dict_add_str(dict, S_LEN("rhs"), (const char *)mp->m_orig_str);
+ } else {
+ tv_dict_add_allocated_str(dict, S_LEN("rhs"),
+ str2special_save((const char *)mp->m_str, false,
+ true));
+ }
+ }
+ if (mp->m_desc != NULL) {
+ tv_dict_add_allocated_str(dict, S_LEN("desc"), xstrdup(mp->m_desc));
}
tv_dict_add_allocated_str(dict, S_LEN("lhs"), lhs);
tv_dict_add_nr(dict, S_LEN("noremap"), noremap_value);
@@ -7503,11 +7556,9 @@ void setwinvar(typval_T *argvars, typval_T *rettv, int off)
typval_T *varp = &argvars[off + 2];
if (win != NULL && varname != NULL && varp != NULL) {
- win_T *save_curwin;
- tabpage_T *save_curtab;
bool need_switch_win = tp != curtab || win != curwin;
- if (!need_switch_win
- || switch_win(&save_curwin, &save_curtab, win, tp, true) == OK) {
+ switchwin_T switchwin;
+ if (!need_switch_win || switch_win(&switchwin, win, tp, true) == OK) {
if (*varname == '&') {
long numval;
bool error = false;
@@ -7529,7 +7580,7 @@ void setwinvar(typval_T *argvars, typval_T *rettv, int off)
}
}
if (need_switch_win) {
- restore_win(save_curwin, save_curtab, true);
+ restore_win(&switchwin, true);
}
}
}
@@ -7696,6 +7747,7 @@ bool callback_from_typval(Callback *const callback, typval_T *const arg)
callback->type = kCallbackFuncref;
}
} else if (nlua_is_table_from_lua(arg)) {
+ // TODO(tjdvries): UnifiedCallback
char_u *name = nlua_register_table_as_callable(arg);
if (name != NULL) {
@@ -7725,6 +7777,7 @@ bool callback_call(Callback *const callback, const int argcount_in, typval_T *co
{
partial_T *partial;
char_u *name;
+ Array args = ARRAY_DICT_INIT;
switch (callback->type) {
case kCallbackFuncref:
name = callback->data.funcref;
@@ -7736,6 +7789,13 @@ bool callback_call(Callback *const callback, const int argcount_in, typval_T *co
name = partial_name(partial);
break;
+ case kCallbackLua:
+ ILOG(" We tryin to call dat dang lua ref ");
+ nlua_call_ref(callback->data.luaref, "aucmd", args, false, NULL);
+
+ return false;
+ break;
+
case kCallbackNone:
return false;
break;
@@ -8140,6 +8200,66 @@ char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl)
return ret;
}
+/// Convert the specified byte index of line 'lnum' in buffer 'buf' to a
+/// character index. Works only for loaded buffers. Returns -1 on failure.
+/// The index of the first byte and the first character is zero.
+int buf_byteidx_to_charidx(buf_T *buf, int lnum, int byteidx)
+{
+ if (buf == NULL || buf->b_ml.ml_mfp == NULL) {
+ return -1;
+ }
+
+ if (lnum > buf->b_ml.ml_line_count) {
+ lnum = buf->b_ml.ml_line_count;
+ }
+
+ char_u *str = ml_get_buf(buf, lnum, false);
+
+ if (*str == NUL) {
+ return 0;
+ }
+
+ // count the number of characters
+ char_u *t = str;
+ int count;
+ for (count = 0; *t != NUL && t <= str + byteidx; count++) {
+ t += utfc_ptr2len(t);
+ }
+
+ // In insert mode, when the cursor is at the end of a non-empty line,
+ // byteidx points to the NUL character immediately past the end of the
+ // string. In this case, add one to the character count.
+ if (*t == NUL && byteidx != 0 && t == str + byteidx) {
+ count++;
+ }
+
+ return count - 1;
+}
+
+/// Convert the specified character index of line 'lnum' in buffer 'buf' to a
+/// byte index. Works only for loaded buffers. Returns -1 on failure.
+/// The index of the first byte and the first character is zero.
+int buf_charidx_to_byteidx(buf_T *buf, int lnum, int charidx)
+{
+ if (buf == NULL || buf->b_ml.ml_mfp == NULL) {
+ return -1;
+ }
+
+ if (lnum > buf->b_ml.ml_line_count) {
+ lnum = buf->b_ml.ml_line_count;
+ }
+
+ char_u *str = ml_get_buf(buf, lnum, false);
+
+ // Convert the character offset to a byte offset
+ char_u *t = str;
+ while (*t != NUL && --charidx > 0) {
+ t += utfc_ptr2len(t);
+ }
+
+ return t - str;
+}
+
/// Translate a VimL object into a position
///
/// Accepts VAR_LIST and VAR_STRING objects. Does not give an error for invalid
@@ -8148,9 +8268,11 @@ char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl)
/// @param[in] tv Object to translate.
/// @param[in] dollar_lnum True when "$" is last line.
/// @param[out] ret_fnum Set to fnum for marks.
+/// @param[in] charcol True to return character column.
///
/// @return Pointer to position or NULL in case of error (e.g. invalid type).
-pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret_fnum)
+pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret_fnum,
+ const bool charcol)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
static pos_T pos;
@@ -8180,7 +8302,11 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret
if (error) {
return NULL;
}
- len = (long)STRLEN(ml_get(pos.lnum));
+ if (charcol) {
+ len = mb_charlen(ml_get(pos.lnum));
+ } else {
+ len = STRLEN(ml_get(pos.lnum));
+ }
// We accept "$" for the column number: last column.
li = tv_list_find(l, 1L);
@@ -8211,19 +8337,31 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret
return NULL;
}
if (name[0] == '.') { // Cursor.
- return &curwin->w_cursor;
+ pos = curwin->w_cursor;
+ if (charcol) {
+ pos.col = buf_byteidx_to_charidx(curbuf, pos.lnum, pos.col);
+ }
+ return &pos;
}
if (name[0] == 'v' && name[1] == NUL) { // Visual start.
if (VIsual_active) {
- return &VIsual;
+ pos = VIsual;
+ } else {
+ pos = curwin->w_cursor;
+ }
+ if (charcol) {
+ pos.col = buf_byteidx_to_charidx(curbuf, pos.lnum, pos.col);
}
- return &curwin->w_cursor;
+ return &pos;
}
if (name[0] == '\'') { // Mark.
pp = getmark_buf_fnum(curbuf, (uint8_t)name[1], false, ret_fnum);
if (pp == NULL || pp == (pos_T *)-1 || pp->lnum <= 0) {
return NULL;
}
+ if (charcol) {
+ pp->col = buf_byteidx_to_charidx(curbuf, pp->lnum, pp->col);
+ }
return pp;
}
@@ -8249,22 +8387,24 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret
pos.col = 0;
} else {
pos.lnum = curwin->w_cursor.lnum;
- pos.col = (colnr_T)STRLEN(get_cursor_line_ptr());
+ if (charcol) {
+ pos.col = (colnr_T)mb_charlen(get_cursor_line_ptr());
+ } else {
+ pos.col = (colnr_T)STRLEN(get_cursor_line_ptr());
+ }
}
return &pos;
}
return NULL;
}
-/*
- * Convert list in "arg" into a position and optional file number.
- * When "fnump" is NULL there is no file number, only 3 items.
- * Note that the column is passed on as-is, the caller may want to decrement
- * it to use 1 for the first column.
- * Return FAIL when conversion is not possible, doesn't check the position for
- * validity.
- */
-int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp)
+/// Convert list in "arg" into a position and optional file number.
+/// When "fnump" is NULL there is no file number, only 3 items.
+/// Note that the column is passed on as-is, the caller may want to decrement
+/// it to use 1 for the first column.
+/// Return FAIL when conversion is not possible, doesn't check the position for
+/// validity.
+int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp, bool charcol)
{
list_T *l;
long i = 0;
@@ -8300,6 +8440,15 @@ int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp)
if (n < 0) {
return FAIL;
}
+ // If character position is specified, then convert to byte position
+ if (charcol) {
+ // Get the text for the specified line in a loaded buffer
+ buf_T *buf = buflist_findnr(fnump == NULL ? curbuf->b_fnum : *fnump);
+ if (buf == NULL || buf->b_ml.ml_mfp == NULL) {
+ return FAIL;
+ }
+ n = buf_charidx_to_byteidx(buf, posp->lnum, n) + 1;
+ }
posp->col = n;
n = tv_list_find_nr(l, i, NULL); // off
@@ -8939,7 +9088,7 @@ static bool tv_is_luafunc(typval_T *tv)
const char *skip_luafunc_name(const char *p)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
- while (ASCII_ISALNUM(*p) || *p == '_' || *p == '.' || *p == '\'') {
+ while (ASCII_ISALNUM(*p) || *p == '_' || *p == '-' || *p == '.' || *p == '\'') {
p++;
}
return p;
@@ -9220,10 +9369,31 @@ static hashtab_T *find_var_ht_dict(const char *name, const size_t name_len, cons
} else if (*name == 'l' && funccal != NULL) { // local variable
*d = &funccal->l_vars;
} else if (*name == 's' // script variable
- && (current_sctx.sc_sid > 0 || current_sctx.sc_sid == SID_STR)
+ && (current_sctx.sc_sid > 0 || current_sctx.sc_sid == SID_STR
+ || current_sctx.sc_sid == SID_LUA)
&& current_sctx.sc_sid <= ga_scripts.ga_len) {
// For anonymous scripts without a script item, create one now so script vars can be used
- if (current_sctx.sc_sid == SID_STR) {
+ if (current_sctx.sc_sid == SID_LUA) {
+ // try to resolve lua filename & line no so it can be shown in lastset messages.
+ nlua_set_sctx(&current_sctx);
+ if (current_sctx.sc_sid != SID_LUA) {
+ // Great we have valid location. Now here this out we'll create a new
+ // script context with the name and lineno of this one. why ?
+ // for behavioral consistency. With this different anonymous exec from
+ // same file can't access each others script local stuff. We need to do
+ // this all other cases except this will act like that otherwise.
+ const LastSet last_set = (LastSet){
+ .script_ctx = current_sctx,
+ .channel_id = LUA_INTERNAL_CALL,
+ };
+ bool should_free;
+ // should_free is ignored as script_sctx will be resolved to a fnmae
+ // & new_script_item will consume it.
+ char_u *sc_name = get_scriptname(last_set, &should_free);
+ new_script_item(sc_name, &current_sctx.sc_sid);
+ }
+ }
+ if (current_sctx.sc_sid == SID_STR || current_sctx.sc_sid == SID_LUA) {
new_script_item(NULL, &current_sctx.sc_sid);
}
*d = &SCRIPT_SV(current_sctx.sc_sid)->sv_dict;
@@ -10508,12 +10678,13 @@ int modify_fname(char_u *src, bool tilde_file, size_t *usedlen, char_u **fnamep,
char_u *s, *p, *pbuf;
char_u dirname[MAXPATHL];
int c;
- int has_fullname = 0;
+ bool has_fullname = false;
+ bool has_homerelative = false;
repeat:
// ":p" - full path/file_name
if (src[*usedlen] == ':' && src[*usedlen + 1] == 'p') {
- has_fullname = 1;
+ has_fullname = true;
valid |= VALID_PATH;
*usedlen += 2;
@@ -10582,8 +10753,8 @@ repeat:
}
pbuf = NULL;
// Need full path first (use expand_env() to remove a "~/")
- if (!has_fullname) {
- if (c == '.' && **fnamep == '~') {
+ if (!has_fullname && !has_homerelative) {
+ if (**fnamep == '~') {
p = pbuf = expand_env_save(*fnamep);
} else {
p = pbuf = (char_u *)FullName_save((char *)*fnamep, FALSE);
@@ -10592,18 +10763,33 @@ repeat:
p = *fnamep;
}
- has_fullname = 0;
+ has_fullname = false;
if (p != NULL) {
if (c == '.') {
os_dirname(dirname, MAXPATHL);
- s = path_shorten_fname(p, dirname);
- if (s != NULL) {
- *fnamep = s;
- if (pbuf != NULL) {
- xfree(*bufp); // free any allocated file name
- *bufp = pbuf;
- pbuf = NULL;
+ if (has_homerelative) {
+ s = vim_strsave(dirname);
+ home_replace(NULL, s, dirname, MAXPATHL, true);
+ xfree(s);
+ }
+ size_t namelen = STRLEN(dirname);
+
+ // Do not call shorten_fname() here since it removes the prefix
+ // even though the path does not have a prefix.
+ if (fnamencmp(p, dirname, namelen) == 0) {
+ p += namelen;
+ if (vim_ispathsep(*p)) {
+ while (*p && vim_ispathsep(*p)) {
+ p++;
+ }
+ *fnamep = p;
+ if (pbuf != NULL) {
+ // free any allocated file name
+ xfree(*bufp);
+ *bufp = pbuf;
+ pbuf = NULL;
+ }
}
}
} else {
@@ -10614,6 +10800,7 @@ repeat:
*fnamep = s;
xfree(*bufp);
*bufp = s;
+ has_homerelative = true;
}
}
xfree(pbuf);
@@ -10990,10 +11177,7 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments, boo
bool eval_has_provider(const char *feat)
{
if (!strequal(feat, "clipboard")
- && !strequal(feat, "python")
&& !strequal(feat, "python3")
- && !strequal(feat, "python_compiled")
- && !strequal(feat, "python_dynamic")
&& !strequal(feat, "python3_compiled")
&& !strequal(feat, "python3_dynamic")
&& !strequal(feat, "perl")
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index e445a08227..05e91a658f 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -71,6 +71,7 @@ return {
chanclose={args={1, 2}},
chansend={args=2},
char2nr={args={1, 2}, base=1},
+ charcol={args=1, base=1},
charidx={args={2, 3}, base=1},
chdir={args=1, base=1},
cindent={args=1, base=1},
@@ -144,6 +145,7 @@ return {
getchangelist={args={0, 1}, base=1},
getchar={args={0, 1}},
getcharmod={},
+ getcharpos={args=1, base=1},
getcharsearch={},
getcharstr={args={0, 1}},
getcmdline={},
@@ -151,7 +153,8 @@ return {
getcmdtype={},
getcmdwintype={},
getcompletion={args={2, 3}, base=1},
- getcurpos={},
+ getcurpos={args={0, 1}, base=1},
+ getcursorcharpos={args={0, 1}, base=1},
getcwd={args={0, 2}, base=1},
getenv={args=1, base=1},
getfontname={args={0, 1}},
@@ -246,6 +249,8 @@ return {
matcharg={args=1, base=1},
matchdelete={args={1, 2}, base=1},
matchend={args={2, 4}, base=1},
+ matchfuzzy={args={2, 3}, base=1},
+ matchfuzzypos={args={2, 3}, base=1},
matchlist={args={2, 4}, base=1},
matchstr={args={2, 4}, base=1},
matchstrpos={args={2,4}, base=1},
@@ -259,7 +264,7 @@ return {
nextnonblank={args=1, base=1},
nr2char={args={1, 2}, base=1},
['or']={args=2, base=1},
- pathshorten={args=1, base=1},
+ pathshorten={args={1, 2}, base=1},
pow={args=2, base=1},
prevnonblank={args=1, base=1},
printf={args=varargs(1), base=2},
@@ -270,12 +275,14 @@ return {
pum_getpos={},
pumvisible={},
py3eval={args=1, base=1},
- pyeval={args=1, base=1},
- pyxeval={args=1, base=1},
+ pyeval={args=1, base=1, func="f_py3eval"},
+ pyxeval={args=1, base=1, func="f_py3eval"},
perleval={args=1, base=1},
+ rand={args={0, 1}, base=1},
range={args={1, 3}, base=1},
readdir={args={1, 2}, base=1},
readfile={args={1, 3}, base=1},
+ reduce={args={2, 3}, base=1},
reg_executing={},
reg_recording={},
reg_recorded={},
@@ -300,19 +307,21 @@ return {
screenpos={args=3, base=1},
screenrow={},
screenstring={args=2, base=1},
- search={args={1, 4}, base=1},
+ search={args={1, 5}, base=1},
searchcount={args={0, 1}, base=1},
searchdecl={args={1, 3}, base=1},
searchpair={args={3, 7}},
searchpairpos={args={3, 7}},
- searchpos={args={1, 4}, base=1},
+ searchpos={args={1, 5}, base=1},
serverlist={},
serverstart={args={0, 1}},
serverstop={args=1},
setbufline={args=3, base=3},
setbufvar={args=3, base=3},
+ setcharpos={args=2, base=2},
setcharsearch={args=1, base=1},
setcmdpos={args=1, base=1},
+ setcursorcharpos={args={1, 3}, base=1},
setenv={args=2, base=2},
setfperm={args=2, base=1},
setline={args=2, base=2},
@@ -348,6 +357,7 @@ return {
spellsuggest={args={1, 3}, base=1},
split={args={1, 3}, base=1},
sqrt={args=1, base=1, func="float_op_wrapper", data="&sqrt"},
+ srand={args={0, 1}, base=1},
stdpath={args=1},
str2float={args=1, base=1},
str2list={args={1, 2}, base=1},
@@ -413,6 +423,8 @@ return {
win_gotoid={args=1, base=1},
win_id2tabwin={args=1, base=1},
win_id2win={args=1, base=1},
+ win_move_separator={args=2, base=1},
+ win_move_statusline={args=2, base=1},
win_screenpos={args=1, base=1},
win_splitmove={args={2, 3}, base=1},
winbufnr={args=1, base=1},
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 32026282cf..49dde537c3 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -22,6 +22,7 @@
#include "nvim/eval/encode.h"
#include "nvim/eval/executor.h"
#include "nvim/eval/funcs.h"
+#include "nvim/eval/typval.h"
#include "nvim/eval/userfunc.h"
#include "nvim/ex_cmds2.h"
#include "nvim/ex_docmd.h"
@@ -97,9 +98,9 @@ PRAGMA_DIAG_POP
#endif
-static char *e_listarg = N_("E686: Argument of %s must be a List");
static char *e_listblobarg = N_("E899: Argument of %s must be a List or Blob");
static char *e_invalwindow = N_("E957: Invalid window number");
+static char *e_reduceempty = N_("E998: Reduce of an empty %s with no initial value");
/// Dummy va_list for passing to vim_snprintf
///
@@ -893,6 +894,7 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr)
partial = argvars[0].vval.v_partial;
func = partial_name(partial);
} else if (nlua_is_table_from_lua(&argvars[0])) {
+ // TODO(tjdevries): UnifiedCallback
func = nlua_register_table_as_callable(&argvars[0]);
owned = true;
} else {
@@ -1019,6 +1021,49 @@ static void f_char2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->vval.v_number = utf_ptr2char((const char_u *)tv_get_string(&argvars[0]));
}
+/// Get the current cursor column and store it in 'rettv'. If 'charcol' is true,
+/// returns the character index of the column. Otherwise, returns the byte index
+/// of the column.
+static void get_col(typval_T *argvars, typval_T *rettv, bool charcol)
+{
+ colnr_T col = 0;
+ pos_T *fp;
+ int fnum = curbuf->b_fnum;
+
+ fp = var2fpos(&argvars[0], false, &fnum, charcol);
+ if (fp != NULL && fnum == curbuf->b_fnum) {
+ if (fp->col == MAXCOL) {
+ // '> can be MAXCOL, get the length of the line then
+ if (fp->lnum <= curbuf->b_ml.ml_line_count) {
+ col = (colnr_T)STRLEN(ml_get(fp->lnum)) + 1;
+ } else {
+ col = MAXCOL;
+ }
+ } else {
+ col = fp->col + 1;
+ // col(".") when the cursor is on the NUL at the end of the line
+ // because of "coladd" can be seen as an extra column.
+ if (virtual_active() && fp == &curwin->w_cursor) {
+ char_u *p = get_cursor_pos_ptr();
+ if (curwin->w_cursor.coladd >=
+ (colnr_T)win_chartabsize(curwin, p, curwin->w_virtcol - curwin->w_cursor.coladd)) {
+ int l;
+ if (*p != NUL && p[(l = utfc_ptr2len(p))] == NUL) {
+ col += l;
+ }
+ }
+ }
+ }
+ }
+ rettv->vval.v_number = col;
+}
+
+/// "charcol()" function
+static void f_charcol(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ get_col(argvars, rettv, true);
+}
+
// "charidx()" function
static void f_charidx(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
@@ -1147,45 +1192,10 @@ static void f_clearmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
-/*
- * "col(string)" function
- */
+/// "col(string)" function
static void f_col(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- colnr_T col = 0;
- pos_T *fp;
- int fnum = curbuf->b_fnum;
-
- fp = var2fpos(&argvars[0], FALSE, &fnum);
- if (fp != NULL && fnum == curbuf->b_fnum) {
- if (fp->col == MAXCOL) {
- // '> can be MAXCOL, get the length of the line then
- if (fp->lnum <= curbuf->b_ml.ml_line_count) {
- col = (colnr_T)STRLEN(ml_get(fp->lnum)) + 1;
- } else {
- col = MAXCOL;
- }
- } else {
- col = fp->col + 1;
- // col(".") when the cursor is on the NUL at the end of the line
- // because of "coladd" can be seen as an extra column.
- if (virtual_active() && fp == &curwin->w_cursor) {
- char_u *p = get_cursor_pos_ptr();
-
- if (curwin->w_cursor.coladd
- >= (colnr_T)win_chartabsize(curwin, p,
- (curwin->w_virtcol
- - curwin->w_cursor.coladd))) {
- int l;
-
- if (*p != NUL && p[(l = utfc_ptr2len(p))] == NUL) {
- col += l;
- }
- }
- }
- }
- }
- rettv->vval.v_number = col;
+ get_col(argvars, rettv, false);
}
/*
@@ -1548,24 +1558,21 @@ static void f_ctxsize(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->vval.v_number = ctx_size();
}
-/// "cursor(lnum, col)" function, or
-/// "cursor(list)"
-///
-/// Moves the cursor to the specified line and column.
-///
-/// @returns 0 when the position could be set, -1 otherwise.
-static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+/// Set the cursor position.
+/// If 'charcol' is true, then use the column number as a character offet.
+/// Otherwise use the column number as a byte offset.
+static void set_cursorpos(typval_T *argvars, typval_T *rettv, bool charcol)
{
long line, col;
long coladd = 0;
bool set_curswant = true;
rettv->vval.v_number = -1;
- if (argvars[1].v_type == VAR_UNKNOWN) {
+ if (argvars[0].v_type == VAR_LIST) {
pos_T pos;
colnr_T curswant = -1;
- if (list2fpos(argvars, &pos, NULL, &curswant) == FAIL) {
+ if (list2fpos(argvars, &pos, NULL, &curswant, charcol) == FAIL) {
emsg(_(e_invarg));
return;
}
@@ -1577,16 +1584,22 @@ static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr)
curwin->w_curswant = curswant - 1;
set_curswant = false;
}
- } else {
+ } else if ((argvars[0].v_type == VAR_NUMBER || argvars[0].v_type == VAR_STRING)
+ && (argvars[1].v_type == VAR_NUMBER || argvars[1].v_type == VAR_STRING)) {
line = tv_get_lnum(argvars);
col = (long)tv_get_number_chk(&argvars[1], NULL);
+ if (charcol) {
+ col = buf_charidx_to_byteidx(curbuf, line, col) + 1;
+ }
if (argvars[2].v_type != VAR_UNKNOWN) {
coladd = (long)tv_get_number_chk(&argvars[2], NULL);
}
+ } else {
+ emsg(_(e_invarg));
+ return;
}
- if (line < 0 || col < 0
- || coladd < 0) {
- return; // type error; errmsg already given
+ if (line < 0 || col < 0 || coladd < 0) {
+ return; // type error; errmsg already given
}
if (line > 0) {
curwin->w_cursor.lnum = line;
@@ -1605,6 +1618,16 @@ static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->vval.v_number = 0;
}
+/// "cursor(lnum, col)" function, or
+/// "cursor(list)"
+///
+/// Moves the cursor to the specified line and column.
+/// Returns 0 when the position could be set, -1 otherwise.
+static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ set_cursorpos(argvars, rettv, false);
+}
+
// "debugbreak()" function
static void f_debugbreak(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
@@ -2176,25 +2199,12 @@ static void f_win_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
tabpage_T *tp;
win_T *wp = win_id2wp_tp(argvars, &tp);
- win_T *save_curwin;
- tabpage_T *save_curtab;
// Return an empty string if something fails.
rettv->v_type = VAR_STRING;
rettv->vval.v_string = NULL;
if (wp != NULL && tp != NULL) {
- pos_T curpos = wp->w_cursor;
- if (switch_win_noblock(&save_curwin, &save_curtab, wp, tp, true) ==
- OK) {
- check_cursor();
- execute_common(argvars, rettv, fptr, 1);
- }
- restore_win_noblock(save_curwin, save_curtab, true);
-
- // Update the status line if the cursor moved.
- if (win_valid(wp) && !equalpos(curpos, wp->w_cursor)) {
- wp->w_redr_status = true;
- }
+ WIN_EXECUTE(wp, tp, execute_common(argvars, rettv, fptr, 1));
}
}
@@ -3180,7 +3190,7 @@ static void getchar_common(typval_T *argvars, typval_T *rettv)
if (argvars[0].v_type == VAR_UNKNOWN) {
// getchar(): blocking wait.
// TODO(bfredl): deduplicate shared logic with state_enter ?
- if (!(char_avail() || using_script() || input_available())) {
+ if (!char_avail()) {
(void)os_inchar(NULL, 0, -1, 0, main_loop.events);
if (!multiqueue_empty(main_loop.events)) {
state_handle_k_event();
@@ -3216,7 +3226,7 @@ static void getchar_common(typval_T *argvars, typval_T *rettv)
set_vim_var_nr(VV_MOUSE_COL, 0);
rettv->vval.v_number = n;
- if (IS_SPECIAL(n) || mod_mask != 0) {
+ if (n != 0 && (IS_SPECIAL(n) || mod_mask != 0)) {
char_u temp[10]; // modifier: 3, mbyte-char: 6, NUL: 1
int i = 0;
@@ -3300,6 +3310,67 @@ static void f_getcharmod(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->vval.v_number = mod_mask;
}
+static void getpos_both(typval_T *argvars, typval_T *rettv, bool getcurpos, bool charcol)
+{
+ pos_T *fp = NULL;
+ pos_T pos;
+ win_T *wp = curwin;
+ int fnum = -1;
+
+ if (getcurpos) {
+ if (argvars[0].v_type != VAR_UNKNOWN) {
+ wp = find_win_by_nr_or_id(&argvars[0]);
+ if (wp != NULL) {
+ fp = &wp->w_cursor;
+ }
+ } else {
+ fp = &curwin->w_cursor;
+ }
+ if (fp != NULL && charcol) {
+ pos = *fp;
+ pos.col = buf_byteidx_to_charidx(wp->w_buffer, pos.lnum, pos.col);
+ fp = &pos;
+ }
+ } else {
+ fp = var2fpos(&argvars[0], true, &fnum, charcol);
+ }
+
+ list_T *const l = tv_list_alloc_ret(rettv, 4 + getcurpos);
+ tv_list_append_number(l, (fnum != -1) ? (varnumber_T)fnum : (varnumber_T)0);
+ tv_list_append_number(l, ((fp != NULL) ? (varnumber_T)fp->lnum : (varnumber_T)0));
+ tv_list_append_number(l, ((fp != NULL)
+ ? (varnumber_T)(fp->col == MAXCOL ? MAXCOL : fp->col + 1)
+ : (varnumber_T)0));
+ tv_list_append_number(l, (fp != NULL) ? (varnumber_T)fp->coladd : (varnumber_T)0);
+ if (getcurpos) {
+ const int save_set_curswant = curwin->w_set_curswant;
+ const colnr_T save_curswant = curwin->w_curswant;
+ const colnr_T save_virtcol = curwin->w_virtcol;
+
+ if (wp == curwin) {
+ update_curswant();
+ }
+ tv_list_append_number(l, (wp == NULL) ? 0 : ((wp->w_curswant == MAXCOL)
+ ? (varnumber_T)MAXCOL
+ : (varnumber_T)wp->w_curswant + 1));
+
+ // Do not change "curswant", as it is unexpected that a get
+ // function has a side effect.
+ if (wp == curwin && save_set_curswant) {
+ curwin->w_set_curswant = save_set_curswant;
+ curwin->w_curswant = save_curswant;
+ curwin->w_virtcol = save_virtcol;
+ curwin->w_valid &= ~VALID_VIRTCOL;
+ }
+ }
+}
+
+/// "getcharpos()" function
+static void f_getcharpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ getpos_both(argvars, rettv, false, true);
+}
+
/*
* "getcharsearch()" function
*/
@@ -3795,7 +3866,7 @@ static void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
// "getmousepos()" function
-void f_getmousepos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+static void f_getmousepos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
dict_T *d;
win_T *wp;
@@ -3855,61 +3926,21 @@ static void f_getpid(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->vval.v_number = os_get_pid();
}
-static void getpos_both(typval_T *argvars, typval_T *rettv, bool getcurpos)
+/// "getcurpos(string)" function
+static void f_getcurpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- pos_T *fp;
- int fnum = -1;
-
- if (getcurpos) {
- fp = &curwin->w_cursor;
- } else {
- fp = var2fpos(&argvars[0], true, &fnum);
- }
-
- list_T *const l = tv_list_alloc_ret(rettv, 4 + (!!getcurpos));
- tv_list_append_number(l, (fnum != -1) ? (varnumber_T)fnum : (varnumber_T)0);
- tv_list_append_number(l, ((fp != NULL)
- ? (varnumber_T)fp->lnum
- : (varnumber_T)0));
- tv_list_append_number(l, ((fp != NULL)
- ? (varnumber_T)(fp->col == MAXCOL ? MAXCOL : fp->col + 1)
- : (varnumber_T)0));
- tv_list_append_number(l, (fp != NULL) ? (varnumber_T)fp->coladd : (varnumber_T)0);
- if (getcurpos) {
- const int save_set_curswant = curwin->w_set_curswant;
- const colnr_T save_curswant = curwin->w_curswant;
- const colnr_T save_virtcol = curwin->w_virtcol;
-
- update_curswant();
- tv_list_append_number(l, (curwin->w_curswant == MAXCOL
- ? (varnumber_T)MAXCOL
- : (varnumber_T)curwin->w_curswant + 1));
-
- // Do not change "curswant", as it is unexpected that a get
- // function has a side effect.
- if (save_set_curswant) {
- curwin->w_set_curswant = save_set_curswant;
- curwin->w_curswant = save_curswant;
- curwin->w_virtcol = save_virtcol;
- curwin->w_valid &= ~VALID_VIRTCOL;
- }
- }
+ getpos_both(argvars, rettv, true, false);
}
-/*
- * "getcurpos(string)" function
- */
-static void f_getcurpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+static void f_getcursorcharpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- getpos_both(argvars, rettv, true);
+ getpos_both(argvars, rettv, true, true);
}
-/*
- * "getpos(string)" function
- */
+/// "getpos(string)" function
static void f_getpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- getpos_both(argvars, rettv, false);
+ getpos_both(argvars, rettv, false, false);
}
/// "getqflist()" functions
@@ -3991,7 +4022,6 @@ static void f_getregtype(typval_T *argvars, typval_T *rettv, FunPtr fptr)
MotionType reg_type = get_reg_type(regname, &reglen);
format_reg_type(reg_type, reglen, buf, ARRAY_SIZE(buf));
- rettv->v_type = VAR_STRING;
rettv->vval.v_string = (char_u *)xstrdup(buf);
}
@@ -4032,8 +4062,6 @@ static void f_gettabinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr)
*/
static void f_gettabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- win_T *oldcurwin;
- tabpage_T *oldtabpage;
bool done = false;
rettv->v_type = VAR_STRING;
@@ -4047,7 +4075,8 @@ static void f_gettabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
win_T *const window = tp == curtab || tp->tp_firstwin == NULL
? firstwin
: tp->tp_firstwin;
- if (switch_win(&oldcurwin, &oldtabpage, window, tp, true) == OK) {
+ switchwin_T switchwin;
+ if (switch_win(&switchwin, window, tp, true) == OK) {
// look up the variable
// Let gettabvar({nr}, "") return the "t:" dictionary.
const dictitem_T *const v = find_var_in_ht(&tp->tp_vars->dv_hashtab, 't',
@@ -4060,7 +4089,7 @@ static void f_gettabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
// restore previous notion of curwin
- restore_win(oldcurwin, oldtabpage, true);
+ restore_win(&switchwin, true);
}
if (!done && argvars[2].v_type != VAR_UNKNOWN) {
@@ -4214,7 +4243,7 @@ static void win_move_into_split(win_T *wp, win_T *targetwin, int size, int flags
int height = wp->w_height;
win_T *oldwin = curwin;
- if (wp == targetwin) {
+ if (wp == targetwin || wp == aucmd_win) {
return;
}
@@ -4434,6 +4463,12 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr)
#if defined(BSD) && !defined(__APPLE__)
"bsd",
#endif
+#ifdef __linux__
+ "linux",
+#endif
+#ifdef SUN_SYSTEM
+ "sun",
+#endif
#ifdef UNIX
"unix",
#endif
@@ -4505,6 +4540,7 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr)
"mouse",
"multi_byte",
"multi_lang",
+ "nanotime",
"num64",
"packages",
"path_extra",
@@ -5882,22 +5918,20 @@ static void f_line(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (argvars[1].v_type != VAR_UNKNOWN) {
tabpage_T *tp;
- win_T *save_curwin;
- tabpage_T *save_curtab;
// use window specified in the second argument
win_T *wp = win_id2wp_tp(&argvars[1], &tp);
if (wp != NULL && tp != NULL) {
- if (switch_win_noblock(&save_curwin, &save_curtab, wp, tp, true)
- == OK) {
+ switchwin_T switchwin;
+ if (switch_win_noblock(&switchwin, wp, tp, true) == OK) {
check_cursor();
- fp = var2fpos(&argvars[0], true, &fnum);
+ fp = var2fpos(&argvars[0], true, &fnum, false);
}
- restore_win_noblock(save_curwin, save_curtab, true);
+ restore_win_noblock(&switchwin, true);
}
} else {
// use current window
- fp = var2fpos(&argvars[0], true, &fnum);
+ fp = var2fpos(&argvars[0], true, &fnum, false);
}
if (fp != NULL) {
@@ -5980,6 +6014,7 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact)
{
char_u *keys_buf = NULL;
char_u *rhs;
+ LuaRef rhs_lua;
int mode;
int abbr = FALSE;
int get_dict = FALSE;
@@ -6016,7 +6051,7 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact)
keys = replace_termcodes(keys, STRLEN(keys), &keys_buf, true, true, true,
CPO_TO_CPO_FLAGS);
- rhs = check_map(keys, mode, exact, false, abbr, &mp, &buffer_local);
+ rhs = check_map(keys, mode, exact, false, abbr, &mp, &buffer_local, &rhs_lua);
xfree(keys_buf);
if (!get_dict) {
@@ -6027,10 +6062,15 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact)
} else {
rettv->vval.v_string = (char_u *)str2special_save((char *)rhs, false, false);
}
+ } else if (rhs_lua != LUA_NOREF) {
+ size_t msglen = 100;
+ char *msg = (char *)xmalloc(msglen);
+ snprintf(msg, msglen, "<Lua function %d>", mp->m_luaref);
+ rettv->vval.v_string = (char_u *)msg;
}
} else {
tv_dict_alloc_ret(rettv);
- if (rhs != NULL) {
+ if (mp != NULL && (rhs != NULL || rhs_lua != LUA_NOREF)) {
// Return a dictionary.
mapblock_fill_dict(rettv->vval.v_dict, mp, buffer_local, true);
}
@@ -6808,12 +6848,23 @@ static void f_or(typval_T *argvars, typval_T *rettv, FunPtr fptr)
*/
static void f_pathshorten(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
+ int trim_len = 1;
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ trim_len = (int)tv_get_number(&argvars[1]);
+ if (trim_len < 1) {
+ trim_len = 1;
+ }
+ }
+
rettv->v_type = VAR_STRING;
- const char *const s = tv_get_string_chk(&argvars[0]);
- if (!s) {
- return;
+ const char_u *p = (char_u *)tv_get_string_chk(&argvars[0]);
+ if (p == NULL) {
+ rettv->vval.v_string = NULL;
+ } else {
+ rettv->vval.v_string = vim_strsave(p);
+ shorten_dir_len(rettv->vval.v_string, trim_len);
}
- rettv->vval.v_string = shorten_dir((char_u *)xstrdup(s));
}
/*
@@ -6922,7 +6973,7 @@ static void f_prompt_setinterrupt(typval_T *argvars, typval_T *rettv, FunPtr fpt
}
/// "prompt_getprompt({buffer})" function
-void f_prompt_getprompt(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+static void f_prompt_getprompt(typval_T *argvars, typval_T *rettv, FunPtr fptr)
FUNC_ATTR_NONNULL_ALL
{
// return an empty string by default, e.g. it's not a prompt buffer
@@ -6977,36 +7028,169 @@ static void f_pumvisible(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
-/*
- * "pyeval()" function
- */
-static void f_pyeval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+/// "py3eval()" and "pyxeval()" functions (always python3)
+static void f_py3eval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- script_host_eval("python", argvars, rettv);
+ script_host_eval("python3", argvars, rettv);
}
-/*
- * "py3eval()" function
- */
-static void f_py3eval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+static void init_srand(uint32_t *const x)
+ FUNC_ATTR_NONNULL_ALL
{
- script_host_eval("python3", argvars, rettv);
+#ifndef MSWIN
+ static int dev_urandom_state = NOTDONE; // FAIL or OK once tried
+
+ if (dev_urandom_state != FAIL) {
+ const int fd = os_open("/dev/urandom", O_RDONLY, 0);
+ struct {
+ union {
+ uint32_t number;
+ char bytes[sizeof(uint32_t)];
+ } contents;
+ } buf;
+
+ // Attempt reading /dev/urandom.
+ if (fd == -1) {
+ dev_urandom_state = FAIL;
+ } else {
+ buf.contents.number = 0;
+ if (read(fd, buf.contents.bytes, sizeof(uint32_t)) != sizeof(uint32_t)) {
+ dev_urandom_state = FAIL;
+ } else {
+ dev_urandom_state = OK;
+ *x = buf.contents.number;
+ }
+ os_close(fd);
+ }
+ }
+ if (dev_urandom_state != OK) {
+ // Reading /dev/urandom doesn't work, fall back to time().
+#endif
+ *x = time(NULL);
+#ifndef MSWIN
+ }
+#endif
+}
+
+static inline uint32_t splitmix32(uint32_t *const x)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE
+{
+ uint32_t z = (*x += 0x9e3779b9);
+ z = (z ^ (z >> 16)) * 0x85ebca6b;
+ z = (z ^ (z >> 13)) * 0xc2b2ae35;
+ return z ^ (z >> 16);
+}
+
+static inline uint32_t shuffle_xoshiro128starstar(uint32_t *const x, uint32_t *const y,
+ uint32_t *const z, uint32_t *const w)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE
+{
+#define ROTL(x, k) ((x << k) | (x >> (32 - k)))
+ const uint32_t result = ROTL(*y * 5, 7) * 9;
+ const uint32_t t = *y << 9;
+ *z ^= *x;
+ *w ^= *y;
+ *y ^= *z;
+ *x ^= *w;
+ *z ^= t;
+ *w = ROTL(*w, 11);
+#undef ROTL
+ return result;
+}
+
+/// "rand()" function
+static void f_rand(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ uint32_t result;
+
+ if (argvars[0].v_type == VAR_UNKNOWN) {
+ static uint32_t gx, gy, gz, gw;
+ static bool initialized = false;
+
+ // When no argument is given use the global seed list.
+ if (!initialized) {
+ // Initialize the global seed list.
+ uint32_t x;
+ init_srand(&x);
+
+ gx = splitmix32(&x);
+ gy = splitmix32(&x);
+ gz = splitmix32(&x);
+ gw = splitmix32(&x);
+ initialized = true;
+ }
+
+ result = shuffle_xoshiro128starstar(&gx, &gy, &gz, &gw);
+ } else if (argvars[0].v_type == VAR_LIST) {
+ list_T *const l = argvars[0].vval.v_list;
+ if (tv_list_len(l) != 4) {
+ goto theend;
+ }
+
+ typval_T *const tvx = TV_LIST_ITEM_TV(tv_list_find(l, 0L));
+ typval_T *const tvy = TV_LIST_ITEM_TV(tv_list_find(l, 1L));
+ typval_T *const tvz = TV_LIST_ITEM_TV(tv_list_find(l, 2L));
+ typval_T *const tvw = TV_LIST_ITEM_TV(tv_list_find(l, 3L));
+ if (tvx->v_type != VAR_NUMBER) {
+ goto theend;
+ }
+ if (tvy->v_type != VAR_NUMBER) {
+ goto theend;
+ }
+ if (tvz->v_type != VAR_NUMBER) {
+ goto theend;
+ }
+ if (tvw->v_type != VAR_NUMBER) {
+ goto theend;
+ }
+ uint32_t x = tvx->vval.v_number;
+ uint32_t y = tvy->vval.v_number;
+ uint32_t z = tvz->vval.v_number;
+ uint32_t w = tvw->vval.v_number;
+
+ result = shuffle_xoshiro128starstar(&x, &y, &z, &w);
+
+ tvx->vval.v_number = (varnumber_T)x;
+ tvy->vval.v_number = (varnumber_T)y;
+ tvz->vval.v_number = (varnumber_T)z;
+ tvw->vval.v_number = (varnumber_T)w;
+ } else {
+ goto theend;
+ }
+
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = (varnumber_T)result;
+ return;
+
+theend:
+ semsg(_(e_invarg2), tv_get_string(&argvars[0]));
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = -1;
}
-// "pyxeval()" function
-static void f_pyxeval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+/// "srand()" function
+static void f_srand(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- init_pyxversion();
- if (p_pyx == 2) {
- f_pyeval(argvars, rettv, NULL);
+ uint32_t x = 0;
+
+ tv_list_alloc_ret(rettv, 4);
+ if (argvars[0].v_type == VAR_UNKNOWN) {
+ init_srand(&x);
} else {
- f_py3eval(argvars, rettv, NULL);
+ bool error = false;
+ x = tv_get_number_chk(&argvars[0], &error);
+ if (error) {
+ return;
+ }
}
+
+ tv_list_append_number(rettv->vval.v_list, (varnumber_T)splitmix32(&x));
+ tv_list_append_number(rettv->vval.v_list, (varnumber_T)splitmix32(&x));
+ tv_list_append_number(rettv->vval.v_list, (varnumber_T)splitmix32(&x));
+ tv_list_append_number(rettv->vval.v_list, (varnumber_T)splitmix32(&x));
}
-///
/// "perleval()" function
-///
static void f_perleval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
script_host_eval("perl", argvars, rettv);
@@ -7873,6 +8057,102 @@ static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
+/// "reduce(list, { accumlator, element -> value } [, initial])" function
+static void f_reduce(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (argvars[0].v_type != VAR_LIST && argvars[0].v_type != VAR_BLOB) {
+ emsg(_(e_listblobreq));
+ return;
+ }
+
+ const char_u *func_name;
+ partial_T *partial = NULL;
+ if (argvars[1].v_type == VAR_FUNC) {
+ func_name = argvars[1].vval.v_string;
+ } else if (argvars[1].v_type == VAR_PARTIAL) {
+ partial = argvars[1].vval.v_partial;
+ func_name = partial_name(partial);
+ } else {
+ func_name = (const char_u *)tv_get_string(&argvars[1]);
+ }
+ if (*func_name == NUL) {
+ return; // type error or empty name
+ }
+
+ funcexe_T funcexe = FUNCEXE_INIT;
+ funcexe.evaluate = true;
+ funcexe.partial = partial;
+
+ typval_T initial;
+ typval_T argv[3];
+ if (argvars[0].v_type == VAR_LIST) {
+ list_T *const l = argvars[0].vval.v_list;
+ const listitem_T *li;
+
+ if (argvars[2].v_type == VAR_UNKNOWN) {
+ if (tv_list_len(l) == 0) {
+ semsg(_(e_reduceempty), "List");
+ return;
+ }
+ const listitem_T *const first = tv_list_first(l);
+ initial = *TV_LIST_ITEM_TV(first);
+ li = TV_LIST_ITEM_NEXT(l, first);
+ } else {
+ initial = argvars[2];
+ li = tv_list_first(l);
+ }
+
+ tv_copy(&initial, rettv);
+
+ if (l != NULL) {
+ const VarLockStatus prev_locked = tv_list_locked(l);
+ const int called_emsg_start = called_emsg;
+
+ tv_list_set_lock(l, VAR_FIXED); // disallow the list changing here
+ for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) {
+ argv[0] = *rettv;
+ argv[1] = *TV_LIST_ITEM_TV(li);
+ rettv->v_type = VAR_UNKNOWN;
+ const int r = call_func(func_name, -1, rettv, 2, argv, &funcexe);
+ tv_clear(&argv[0]);
+ if (r == FAIL || called_emsg != called_emsg_start) {
+ break;
+ }
+ }
+ tv_list_set_lock(l, prev_locked);
+ }
+ } else {
+ const blob_T *const b = argvars[0].vval.v_blob;
+ int i;
+
+ if (argvars[2].v_type == VAR_UNKNOWN) {
+ if (tv_blob_len(b) == 0) {
+ semsg(_(e_reduceempty), "Blob");
+ return;
+ }
+ initial.v_type = VAR_NUMBER;
+ initial.vval.v_number = tv_blob_get(b, 0);
+ i = 1;
+ } else if (argvars[2].v_type != VAR_NUMBER) {
+ emsg(_(e_number_exp));
+ return;
+ } else {
+ initial = argvars[2];
+ i = 0;
+ }
+
+ tv_copy(&initial, rettv);
+ for (; i < tv_blob_len(b); i++) {
+ argv[0] = *rettv;
+ argv[1].v_type = VAR_NUMBER;
+ argv[1].vval.v_number = tv_blob_get(b, i);
+ if (call_func(func_name, -1, rettv, 2, argv, &funcexe) == FAIL) {
+ return;
+ }
+ }
+ }
+}
+
#define SP_NOMOVE 0x01 ///< don't move cursor
#define SP_REPEAT 0x02 ///< repeat to find outer pair
#define SP_RETCOUNT 0x04 ///< return matchcount
@@ -7959,6 +8239,7 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp)
int options = SEARCH_KEEP;
int subpatnum;
searchit_arg_T sia;
+ bool use_skip = false;
const char *const pat = tv_get_string(&argvars[0]);
dir = get_search_arg(&argvars[1], flagsp); // May set p_ws.
@@ -7976,7 +8257,7 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp)
options |= SEARCH_COL;
}
- // Optional arguments: line number to stop searching and timeout.
+ // Optional arguments: line number to stop searching, timeout and skip.
if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN) {
lnum_stop = tv_get_number_chk(&argvars[2], NULL);
if (lnum_stop < 0) {
@@ -7987,6 +8268,7 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp)
if (time_limit < 0) {
goto theend;
}
+ use_skip = eval_expr_valid_arg(&argvars[4]);
}
}
@@ -8006,11 +8288,46 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp)
}
pos = save_cursor = curwin->w_cursor;
+ pos_T firstpos = { 0 };
memset(&sia, 0, sizeof(sia));
sia.sa_stop_lnum = (linenr_T)lnum_stop;
sia.sa_tm = &tm;
- subpatnum = searchit(curwin, curbuf, &pos, NULL, dir, (char_u *)pat, 1,
- options, RE_SEARCH, &sia);
+
+ // Repeat until {skip} returns false.
+ for (;;) {
+ subpatnum
+ = searchit(curwin, curbuf, &pos, NULL, dir, (char_u *)pat, 1, options, RE_SEARCH, &sia);
+ // finding the first match again means there is no match where {skip}
+ // evaluates to zero.
+ if (firstpos.lnum != 0 && equalpos(pos, firstpos)) {
+ subpatnum = FAIL;
+ }
+
+ if (subpatnum == FAIL || !use_skip) {
+ // didn't find it or no skip argument
+ break;
+ }
+ firstpos = pos;
+
+ // If the skip expression matches, ignore this match.
+ {
+ const pos_T save_pos = curwin->w_cursor;
+
+ curwin->w_cursor = pos;
+ bool err = false;
+ const bool do_skip = eval_expr_to_bool(&argvars[4], &err);
+ curwin->w_cursor = save_pos;
+ if (err) {
+ // Evaluating {skip} caused an error, break here.
+ subpatnum = FAIL;
+ break;
+ }
+ if (!do_skip) {
+ break;
+ }
+ }
+ }
+
if (subpatnum != FAIL) {
if (flags & SP_SUBPAT) {
retval = subpatnum;
@@ -8470,13 +8787,9 @@ static int searchpair_cmn(typval_T *argvars, pos_T *match_pos)
|| argvars[4].v_type == VAR_UNKNOWN) {
skip = NULL;
} else {
+ // Type is checked later.
skip = &argvars[4];
- if (skip->v_type != VAR_FUNC
- && skip->v_type != VAR_PARTIAL
- && skip->v_type != VAR_STRING) {
- semsg(_(e_invarg2), tv_get_string(&argvars[4]));
- goto theend; // Type error.
- }
+
if (argvars[5].v_type != VAR_UNKNOWN) {
lnum_stop = tv_get_number_chk(&argvars[5], NULL);
if (lnum_stop < 0) {
@@ -8587,10 +8900,7 @@ long do_searchpair(const char *spat, const char *mpat, const char *epat, int dir
}
if (skip != NULL) {
- // Empty string means to not use the skip expression.
- if (skip->v_type == VAR_STRING || skip->v_type == VAR_FUNC) {
- use_skip = skip->vval.v_string != NULL && *skip->vval.v_string != NUL;
- }
+ use_skip = eval_expr_valid_arg(skip);
}
save_cursor = curwin->w_cursor;
@@ -8863,6 +9173,49 @@ static void f_setbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
+/// Set the cursor or mark position.
+/// If 'charpos' is TRUE, then use the column number as a character offet.
+/// Otherwise use the column number as a byte offset.
+static void set_position(typval_T *argvars, typval_T *rettv, bool charpos)
+{
+ pos_T pos;
+ int fnum;
+ colnr_T curswant = -1;
+
+ rettv->vval.v_number = -1;
+ const char *const name = tv_get_string_chk(argvars);
+ if (name != NULL) {
+ if (list2fpos(&argvars[1], &pos, &fnum, &curswant, charpos) == OK) {
+ if (pos.col != MAXCOL && --pos.col < 0) {
+ pos.col = 0;
+ }
+ if (name[0] == '.' && name[1] == NUL) {
+ // set cursor; "fnum" is ignored
+ curwin->w_cursor = pos;
+ if (curswant >= 0) {
+ curwin->w_curswant = curswant - 1;
+ curwin->w_set_curswant = false;
+ }
+ check_cursor();
+ rettv->vval.v_number = 0;
+ } else if (name[0] == '\'' && name[1] != NUL && name[2] == NUL) {
+ // set mark
+ if (setmark_pos((uint8_t)name[1], &pos, fnum) == OK) {
+ rettv->vval.v_number = 0;
+ }
+ } else {
+ emsg(_(e_invarg));
+ }
+ }
+ }
+}
+
+/// "setcharpos()" function
+static void f_setcharpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ set_position(argvars, rettv, true);
+}
+
static void f_setcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
dict_T *d;
@@ -8905,6 +9258,12 @@ static void f_setcmdpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
+/// "setcursorcharpos" function
+static void f_setcursorcharpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ set_cursorpos(argvars, rettv, true);
+}
+
/// "setenv()" function
static void f_setenv(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
@@ -9161,41 +9520,10 @@ static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
-/*
- * "setpos()" function
- */
+/// "setpos()" function
static void f_setpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- pos_T pos;
- int fnum;
- colnr_T curswant = -1;
-
- rettv->vval.v_number = -1;
- const char *const name = tv_get_string_chk(argvars);
- if (name != NULL) {
- if (list2fpos(&argvars[1], &pos, &fnum, &curswant) == OK) {
- if (pos.col != MAXCOL && --pos.col < 0) {
- pos.col = 0;
- }
- if (name[0] == '.' && name[1] == NUL) {
- // set cursor; "fnum" is ignored
- curwin->w_cursor = pos;
- if (curswant >= 0) {
- curwin->w_curswant = curswant - 1;
- curwin->w_set_curswant = false;
- }
- check_cursor();
- rettv->vval.v_number = 0;
- } else if (name[0] == '\'' && name[1] != NUL && name[2] == NUL) {
- // set mark
- if (setmark_pos((uint8_t)name[1], &pos, fnum) == OK) {
- rettv->vval.v_number = 0;
- }
- } else {
- emsg(_(e_invarg));
- }
- }
- }
+ set_position(argvars, rettv, false);
}
/*
@@ -10210,6 +10538,10 @@ static void f_stdioopen(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (!tv_dict_get_callback(opts, S_LEN("on_stdin"), &on_stdin.cb)) {
return;
}
+ if (!tv_dict_get_callback(opts, S_LEN("on_print"), &on_print)) {
+ return;
+ }
+
on_stdin.buffered = tv_dict_get_number(opts, "stdin_buffered");
if (on_stdin.buffered && on_stdin.cb.type == kCallbackNone) {
on_stdin.self = opts;
@@ -10664,7 +10996,7 @@ static void f_stridx(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/*
* "string()" function
*/
-void f_string(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+static void f_string(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
rettv->v_type = VAR_STRING;
rettv->vval.v_string = (char_u *)encode_tv2string(&argvars[0], NULL);
@@ -11874,7 +12206,7 @@ static void f_virtcol(typval_T *argvars, typval_T *rettv, FunPtr fptr)
pos_T *fp;
int fnum = curbuf->b_fnum;
- fp = var2fpos(&argvars[0], FALSE, &fnum);
+ fp = var2fpos(&argvars[0], false, &fnum, false);
if (fp != NULL && fp->lnum <= curbuf->b_ml.ml_line_count
&& fnum == curbuf->b_fnum) {
// Limit the column to a valid value, getvvcol() doesn't check.
@@ -11980,6 +12312,42 @@ static void f_win_id2win(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->vval.v_number = win_id2win(argvars);
}
+/// "win_move_separator()" function
+static void f_win_move_separator(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ win_T *wp;
+ int offset;
+
+ rettv->vval.v_number = false;
+
+ wp = find_win_by_nr_or_id(&argvars[0]);
+ if (wp == NULL || wp->w_floating) {
+ return;
+ }
+
+ offset = (int)tv_get_number(&argvars[1]);
+ win_drag_vsep_line(wp, offset);
+ rettv->vval.v_number = true;
+}
+
+/// "win_move_statusline()" function
+static void f_win_move_statusline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ win_T *wp;
+ int offset;
+
+ rettv->vval.v_number = false;
+
+ wp = find_win_by_nr_or_id(&argvars[0]);
+ if (wp == NULL || wp->w_floating) {
+ return;
+ }
+
+ offset = (int)tv_get_number(&argvars[1]);
+ win_drag_status_line(wp, offset);
+ rettv->vval.v_number = true;
+}
+
/// "winbufnr(nr)" function
static void f_winbufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c
index 11bbaaed9c..44b003d106 100644
--- a/src/nvim/eval/typval.c
+++ b/src/nvim/eval/typval.c
@@ -8,6 +8,7 @@
#include <stdlib.h>
#include <string.h>
+#include "lauxlib.h"
#include "nvim/ascii.h"
#include "nvim/assert.h"
#include "nvim/charset.h"
@@ -28,11 +29,11 @@
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/message.h"
+#include "nvim/os/fileio.h"
#include "nvim/os/input.h"
#include "nvim/pos.h"
#include "nvim/types.h"
#include "nvim/vim.h"
-#include "nvim/os/fileio.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval/typval.c.generated.h"
@@ -1123,6 +1124,8 @@ bool tv_callback_equal(const Callback *cb1, const Callback *cb2)
// FIXME: this is inconsistent with tv_equal but is needed for precision
// maybe change dictwatcheradd to return a watcher id instead?
return cb1->data.partial == cb2->data.partial;
+ case kCallbackLua:
+ return cb1->data.luaref == cb2->data.luaref;
case kCallbackNone:
return true;
}
@@ -1142,6 +1145,9 @@ void callback_free(Callback *callback)
case kCallbackPartial:
partial_unref(callback->data.partial);
break;
+ case kCallbackLua:
+ NLUA_CLEAR_REF(callback->data.luaref);
+ break;
case kCallbackNone:
break;
}
@@ -1149,6 +1155,27 @@ void callback_free(Callback *callback)
callback->data.funcref = NULL;
}
+/// Check if callback is freed
+bool callback_is_freed(Callback callback)
+{
+ switch (callback.type) {
+ case kCallbackFuncref:
+ return false;
+ break;
+ case kCallbackPartial:
+ return false;
+ break;
+ case kCallbackLua:
+ return callback.data.luaref == LUA_NOREF;
+ break;
+ case kCallbackNone:
+ return true;
+ break;
+ }
+
+ return true;
+}
+
/// Copy a callback into a typval_T.
void callback_put(Callback *cb, typval_T *tv)
FUNC_ATTR_NONNULL_ALL
@@ -1164,6 +1191,11 @@ void callback_put(Callback *cb, typval_T *tv)
tv->vval.v_string = vim_strsave(cb->data.funcref);
func_ref(cb->data.funcref);
break;
+ case kCallbackLua:
+ // TODO(tjdevries): Unified Callback.
+ // At this point this isn't possible, but it'd be nice to put
+ // these handled more neatly in one place.
+ // So instead, we just do the default and put nil
default:
tv->v_type = VAR_SPECIAL;
tv->vval.v_special = kSpecialVarNull;
@@ -1185,6 +1217,9 @@ void callback_copy(Callback *dest, Callback *src)
dest->data.funcref = vim_strsave(src->data.funcref);
func_ref(src->data.funcref);
break;
+ case kCallbackLua:
+ dest->data.luaref = api_new_luaref(src->data.luaref);
+ break;
default:
dest->data.funcref = NULL;
break;
@@ -2455,13 +2490,11 @@ static inline void _nothing_conv_dict_end(typval_T *const tv, dict_T **const dic
#define TYPVAL_ENCODE_NAME nothing
#define TYPVAL_ENCODE_FIRST_ARG_TYPE const void *const
#define TYPVAL_ENCODE_FIRST_ARG_NAME ignored
-#define TYPVAL_ENCODE_TRANSLATE_OBJECT_NAME
#include "nvim/eval/typval_encode.c.h"
#undef TYPVAL_ENCODE_SCOPE
#undef TYPVAL_ENCODE_NAME
#undef TYPVAL_ENCODE_FIRST_ARG_TYPE
#undef TYPVAL_ENCODE_FIRST_ARG_NAME
-#undef TYPVAL_ENCODE_TRANSLATE_OBJECT_NAME
#undef TYPVAL_ENCODE_ALLOW_SPECIALS
#undef TYPVAL_ENCODE_CONV_NIL
@@ -3093,7 +3126,7 @@ linenr_T tv_get_lnum(const typval_T *const tv)
linenr_T lnum = (linenr_T)tv_get_number_chk(tv, NULL);
if (lnum == 0) { // No valid number, try using same function as line() does.
int fnum;
- pos_T *const fp = var2fpos(tv, true, &fnum);
+ pos_T *const fp = var2fpos(tv, true, &fnum, false);
if (fp != NULL) {
lnum = fp->lnum;
}
@@ -3207,8 +3240,9 @@ const char *tv_get_string_buf_chk(const typval_T *const tv, char *const buf)
case VAR_BLOB:
case VAR_UNKNOWN:
emsg(_(str_errors[tv->v_type]));
- return false;
+ return NULL;
}
+ abort();
return NULL;
}
diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h
index d1275d6512..40dc819754 100644
--- a/src/nvim/eval/typval.h
+++ b/src/nvim/eval/typval.h
@@ -72,16 +72,20 @@ typedef enum {
kCallbackNone = 0,
kCallbackFuncref,
kCallbackPartial,
+ kCallbackLua,
} CallbackType;
typedef struct {
union {
char_u *funcref;
partial_T *partial;
+ LuaRef luaref;
} data;
CallbackType type;
} Callback;
-#define CALLBACK_NONE ((Callback){ .type = kCallbackNone })
+
+#define CALLBACK_INIT { .type = kCallbackNone }
+#define CALLBACK_NONE ((Callback)CALLBACK_INIT)
/// Structure holding dictionary watcher
typedef struct dict_watcher {
diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c
index eb241eb8ae..5764f9fbd4 100644
--- a/src/nvim/eval/userfunc.c
+++ b/src/nvim/eval/userfunc.c
@@ -2573,6 +2573,7 @@ void ex_function(exarg_T *eap)
fp->uf_calls = 0;
fp->uf_script_ctx = current_sctx;
fp->uf_script_ctx.sc_lnum += sourcing_lnum_top;
+ nlua_set_sctx(&fp->uf_script_ctx);
goto ret_free;
diff --git a/src/nvim/event/loop.c b/src/nvim/event/loop.c
index 892c46dd04..89fced59c5 100644
--- a/src/nvim/event/loop.c
+++ b/src/nvim/event/loop.c
@@ -75,7 +75,7 @@ bool loop_poll_events(Loop *loop, int ms)
/// @note Event is queued into `fast_events`, which is processed outside of the
/// primary `events` queue by loop_poll_events(). For `main_loop`, that
/// means `fast_events` is NOT processed in an "editor mode"
-/// (VimState.execute), so redraw and other side-effects are likely to be
+/// (VimState.execute), so redraw and other side effects are likely to be
/// skipped.
/// @see loop_schedule_deferred
void loop_schedule_fast(Loop *loop, Event event)
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 4965eb9c20..c7910e148d 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -814,7 +814,11 @@ void ex_retab(exarg_T *eap)
// len is actual number of white characters used
len = num_spaces + num_tabs;
old_len = (long)STRLEN(ptr);
- long new_len = old_len - col + start_col + len + 1;
+ const long new_len = old_len - col + start_col + len + 1;
+ if (new_len <= 0 || new_len >= MAXCOL) {
+ emsg(_(e_resulting_text_too_long));
+ break;
+ }
new_line = xmalloc(new_len);
if (start_col > 0) {
@@ -847,6 +851,10 @@ void ex_retab(exarg_T *eap)
break;
}
vcol += win_chartabsize(curwin, ptr + col, (colnr_T)vcol);
+ if (vcol >= MAXCOL) {
+ emsg(_(e_resulting_text_too_long));
+ break;
+ }
col += utfc_ptr2len(ptr + col);
}
if (new_line == NULL) { // out of memory
@@ -980,8 +988,10 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
foldMoveRange(win, &win->w_folds, line1, line2, dest);
}
}
- curbuf->b_op_start.lnum = dest - num_lines + 1;
- curbuf->b_op_end.lnum = dest;
+ if (!cmdmod.lockmarks) {
+ curbuf->b_op_start.lnum = dest - num_lines + 1;
+ curbuf->b_op_end.lnum = dest;
+ }
line_off = -num_lines;
byte_off = -extent_byte;
} else {
@@ -991,10 +1001,14 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
foldMoveRange(win, &win->w_folds, dest + 1, line1 - 1, line2);
}
}
- curbuf->b_op_start.lnum = dest + 1;
- curbuf->b_op_end.lnum = dest + num_lines;
+ if (!cmdmod.lockmarks) {
+ curbuf->b_op_start.lnum = dest + 1;
+ curbuf->b_op_end.lnum = dest + num_lines;
+ }
+ }
+ if (!cmdmod.lockmarks) {
+ curbuf->b_op_start.col = curbuf->b_op_end.col = 0;
}
- curbuf->b_op_start.col = curbuf->b_op_end.col = 0;
mark_adjust_nofold(last_line - num_lines + 1, last_line,
-(last_line - dest - extra), 0L, kExtmarkNOOP);
@@ -1057,9 +1071,11 @@ void ex_copy(linenr_T line1, linenr_T line2, linenr_T n)
char_u *p;
count = line2 - line1 + 1;
- curbuf->b_op_start.lnum = n + 1;
- curbuf->b_op_end.lnum = n + count;
- curbuf->b_op_start.col = curbuf->b_op_end.col = 0;
+ if (!cmdmod.lockmarks) {
+ curbuf->b_op_start.lnum = n + 1;
+ curbuf->b_op_end.lnum = n + count;
+ curbuf->b_op_start.col = curbuf->b_op_end.col = 0;
+ }
/*
* there are three situations:
@@ -1099,6 +1115,9 @@ void ex_copy(linenr_T line1, linenr_T line2, linenr_T n)
}
appended_lines_mark(n, count);
+ if (VIsual_active) {
+ check_pos(curbuf, &VIsual);
+ }
msgmore((long)count);
}
@@ -1269,12 +1288,18 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char_u *cmd,
char_u *cmd_buf;
buf_T *old_curbuf = curbuf;
int shell_flags = 0;
+ const pos_T orig_start = curbuf->b_op_start;
+ const pos_T orig_end = curbuf->b_op_end;
const int stmp = p_stmp;
if (*cmd == NUL) { // no filter command
return;
}
+ const bool save_lockmarks = cmdmod.lockmarks;
+ // Temporarily disable lockmarks since that's needed to propagate changed
+ // regions of the buffer for foldUpdate(), linecount, etc.
+ cmdmod.lockmarks = false;
cursor_save = curwin->w_cursor;
linecount = line2 - line1 + 1;
@@ -1349,7 +1374,7 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char_u *cmd,
ui_cursor_goto(Rows - 1, 0);
if (do_out) {
- if (u_save((line2), (linenr_T)(line2 + 1)) == FAIL) {
+ if (u_save(line2, (linenr_T)(line2 + 1)) == FAIL) {
xfree(cmd_buf);
goto error;
}
@@ -1455,10 +1480,15 @@ error:
filterend:
+ cmdmod.lockmarks = save_lockmarks;
if (curbuf != old_curbuf) {
no_wait_return--;
emsg(_("E135: *Filter* Autocommands must not change current buffer"));
+ } else if (cmdmod.lockmarks) {
+ curbuf->b_op_start = orig_start;
+ curbuf->b_op_end = orig_end;
}
+
if (itmp != NULL) {
os_remove((char *)itmp);
}
@@ -1913,7 +1943,7 @@ int do_write(exarg_T *eap)
// If 'filetype' was empty try detecting it now.
if (*curbuf->b_p_ft == NUL) {
- if (au_has_group((char_u *)"filetypedetect")) {
+ if (augroup_exists("filetypedetect")) {
(void)do_doautocmd((char_u *)"filetypedetect BufRead", true, NULL);
}
do_modelines(0);
@@ -3006,7 +3036,12 @@ void ex_append(exarg_T *eap)
did_undo = true;
ml_append(lnum, theline, (colnr_T)0, false);
- appended_lines_mark(lnum + (empty ? 1 : 0), 1L);
+ if (empty) {
+ // there are no marks below the inserted lines
+ appended_lines(lnum, 1L);
+ } else {
+ appended_lines_mark(lnum, 1L);
+ }
xfree(theline);
++lnum;
@@ -3025,15 +3060,16 @@ void ex_append(exarg_T *eap)
// "start" is set to eap->line2+1 unless that position is invalid (when
// eap->line2 pointed to the end of the buffer and nothing was appended)
// "end" is set to lnum when something has been appended, otherwise
- // it is the same than "start" -- Acevedo
- curbuf->b_op_start.lnum = (eap->line2 < curbuf->b_ml.ml_line_count) ?
- eap->line2 + 1 : curbuf->b_ml.ml_line_count;
- if (eap->cmdidx != CMD_append) {
- --curbuf->b_op_start.lnum;
+ // it is the same as "start" -- Acevedo
+ if (!cmdmod.lockmarks) {
+ curbuf->b_op_start.lnum
+ = (eap->line2 < curbuf->b_ml.ml_line_count) ? eap->line2 + 1 : curbuf->b_ml.ml_line_count;
+ if (eap->cmdidx != CMD_append) {
+ curbuf->b_op_start.lnum--;
+ }
+ curbuf->b_op_end.lnum = (eap->line2 < lnum) ? lnum : curbuf->b_op_start.lnum;
+ curbuf->b_op_start.col = curbuf->b_op_end.col = 0;
}
- curbuf->b_op_end.lnum = (eap->line2 < lnum)
- ? lnum : curbuf->b_op_start.lnum;
- curbuf->b_op_start.col = curbuf->b_op_end.col = 0;
curwin->w_cursor.lnum = lnum;
check_cursor_lnum();
beginline(BL_SOL | BL_FIX);
@@ -3624,8 +3660,14 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle
// We do it here once to avoid it to be replaced over and over again.
// But don't do it when it starts with "\=", then it's an expression.
assert(sub != NULL);
+
+ bool sub_needs_free = false;
if (!(sub[0] == '\\' && sub[1] == '=')) {
+ char_u *source = sub;
sub = regtilde(sub, p_magic);
+ // When previewing, the new pattern allocated by regtilde() needs to be freed
+ // in this function because it will not be used or freed by regtilde() later.
+ sub_needs_free = preview && sub != source;
}
// Check for a match on each line.
@@ -4346,10 +4388,12 @@ skip:
}
if (sub_nsubs > start_nsubs) {
- // Set the '[ and '] marks.
- curbuf->b_op_start.lnum = eap->line1;
- curbuf->b_op_end.lnum = line2;
- curbuf->b_op_start.col = curbuf->b_op_end.col = 0;
+ if (!cmdmod.lockmarks) {
+ // Set the '[ and '] marks.
+ curbuf->b_op_start.lnum = eap->line1;
+ curbuf->b_op_end.lnum = line2;
+ curbuf->b_op_start.col = curbuf->b_op_end.col = 0;
+ }
if (!global_busy) {
// when interactive leave cursor on the match
@@ -4420,6 +4464,10 @@ skip:
kv_destroy(preview_lines.subresults);
+ if (sub_needs_free) {
+ xfree(sub);
+ }
+
return preview_buf;
#undef ADJUST_SUB_FIRSTLNUM
#undef PUSH_PREVIEW_LINES
@@ -5063,8 +5111,7 @@ int find_help_tags(const char_u *arg, int *num_matches, char_u ***matches, bool
&& ((arg[1] != NUL && arg[2] == NUL)
|| (vim_strchr((char_u *)"%_z@", arg[1]) != NULL
&& arg[2] != NUL))) {
- STRCPY(d, "/\\\\");
- STRCPY(d + 3, arg + 1);
+ vim_snprintf((char *)d, IOSIZE, "/\\\\%s", arg + 1);
// Check for "/\\_$", should be "/\\_\$"
if (d[3] == '_' && d[4] == '$') {
STRCPY(d + 4, "\\$");
@@ -5817,7 +5864,7 @@ void ex_helpclose(exarg_T *eap)
{
FOR_ALL_WINDOWS_IN_TAB(win, curtab) {
if (bt_help(win->w_buffer)) {
- win_close(win, false);
+ win_close(win, false, eap->forceit);
return;
}
}
@@ -6102,12 +6149,14 @@ char_u *skip_vimgrep_pat(char_u *p, char_u **s, int *flags)
p++;
// Find the flags
- while (*p == 'g' || *p == 'j') {
+ while (*p == 'g' || *p == 'j' || *p == 'f') {
if (flags != NULL) {
if (*p == 'g') {
*flags |= VGR_GLOBAL;
- } else {
+ } else if (*p == 'j') {
*flags |= VGR_NOJUMP;
+ } else {
+ *flags |= VGR_FUZZY;
}
}
p++;
diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua
index c388373ac1..c2d40c8bb7 100644
--- a/src/nvim/ex_cmds.lua
+++ b/src/nvim/ex_cmds.lua
@@ -2097,19 +2097,19 @@ module.cmds = {
command='python',
flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN),
addr_type='ADDR_LINES',
- func='ex_python',
+ func='ex_python3',
},
{
command='pydo',
flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN),
addr_type='ADDR_LINES',
- func='ex_pydo',
+ func='ex_pydo3',
},
{
command='pyfile',
flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN),
addr_type='ADDR_LINES',
- func='ex_pyfile',
+ func='ex_py3file',
},
{
command='py3',
@@ -2139,25 +2139,25 @@ module.cmds = {
command='pyx',
flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN),
addr_type='ADDR_LINES',
- func='ex_pyx',
+ func='ex_python3',
},
{
command='pyxdo',
flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN),
addr_type='ADDR_LINES',
- func='ex_pyxdo',
+ func='ex_pydo3',
},
{
command='pythonx',
flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN),
addr_type='ADDR_LINES',
- func='ex_pyx',
+ func='ex_python3',
},
{
command='pyxfile',
flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN),
addr_type='ADDR_LINES',
- func='ex_pyxfile',
+ func='ex_py3file',
},
{
command='quit',
@@ -2413,7 +2413,7 @@ module.cmds = {
},
{
command='set',
- flags=bit.bor(TRLBAR, EXTRA, CMDWIN, SBOXOK),
+ flags=bit.bor(BANG, TRLBAR, EXTRA, CMDWIN, SBOXOK),
addr_type='ADDR_NONE',
func='ex_set',
},
@@ -2425,13 +2425,13 @@ module.cmds = {
},
{
command='setglobal',
- flags=bit.bor(TRLBAR, EXTRA, CMDWIN, SBOXOK),
+ flags=bit.bor(BANG, TRLBAR, EXTRA, CMDWIN, SBOXOK),
addr_type='ADDR_NONE',
func='ex_set',
},
{
command='setlocal',
- flags=bit.bor(TRLBAR, EXTRA, CMDWIN, SBOXOK),
+ flags=bit.bor(BANG, TRLBAR, EXTRA, CMDWIN, SBOXOK),
addr_type='ADDR_NONE',
func='ex_set',
},
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index 33f9477608..8666c7e33a 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -12,6 +12,7 @@
#include <string.h>
#include "nvim/ascii.h"
+#include "nvim/globals.h"
#include "nvim/vim.h"
#ifdef HAVE_LOCALE_H
# include <locale.h>
@@ -135,21 +136,6 @@ void ex_profile(exarg_T *eap)
}
}
-void ex_python(exarg_T *eap)
-{
- script_host_execute("python", eap);
-}
-
-void ex_pyfile(exarg_T *eap)
-{
- script_host_execute_file("python", eap);
-}
-
-void ex_pydo(exarg_T *eap)
-{
- script_host_do_range("python", eap);
-}
-
void ex_ruby(exarg_T *eap)
{
script_host_execute("ruby", eap);
@@ -1613,7 +1599,7 @@ void ex_compiler(exarg_T *eap)
if (old_cur_comp != NULL) {
old_cur_comp = vim_strsave(old_cur_comp);
}
- do_cmdline_cmd("command -nargs=* CompilerSet setlocal <args>");
+ do_cmdline_cmd("command -nargs=* -keepscript CompilerSet setlocal <args>");
}
do_unlet(S_LEN("g:current_compiler"), true);
do_unlet(S_LEN("b:current_compiler"), true);
@@ -1660,126 +1646,6 @@ void ex_options(exarg_T *eap)
cmd_source((char_u *)SYS_OPTWIN_FILE, NULL);
}
-// Detect Python 3 or 2, and initialize 'pyxversion'.
-void init_pyxversion(void)
-{
- if (p_pyx == 0) {
- if (eval_has_provider("python3")) {
- p_pyx = 3;
- } else if (eval_has_provider("python")) {
- p_pyx = 2;
- }
- }
-}
-
-// Does a file contain one of the following strings at the beginning of any
-// line?
-// "#!(any string)python2" => returns 2
-// "#!(any string)python3" => returns 3
-// "# requires python 2.x" => returns 2
-// "# requires python 3.x" => returns 3
-// otherwise return 0.
-static int requires_py_version(char_u *filename)
-{
- FILE *file;
- int requires_py_version = 0;
- int i, lines;
-
- lines = (int)p_mls;
- if (lines < 0) {
- lines = 5;
- }
-
- file = os_fopen((char *)filename, "r");
- if (file != NULL) {
- for (i = 0; i < lines; i++) {
- if (vim_fgets(IObuff, IOSIZE, file)) {
- break;
- }
- if (i == 0 && IObuff[0] == '#' && IObuff[1] == '!') {
- // Check shebang.
- if (strstr((char *)IObuff + 2, "python2") != NULL) {
- requires_py_version = 2;
- break;
- }
- if (strstr((char *)IObuff + 2, "python3") != NULL) {
- requires_py_version = 3;
- break;
- }
- }
- IObuff[21] = '\0';
- if (STRCMP("# requires python 2.x", IObuff) == 0) {
- requires_py_version = 2;
- break;
- }
- if (STRCMP("# requires python 3.x", IObuff) == 0) {
- requires_py_version = 3;
- break;
- }
- }
- fclose(file);
- }
- return requires_py_version;
-}
-
-
-// Source a python file using the requested python version.
-static void source_pyx_file(exarg_T *eap, char_u *fname)
-{
- exarg_T ex;
- long int v = requires_py_version(fname);
-
- init_pyxversion();
- if (v == 0) {
- // user didn't choose a preference, 'pyx' is used
- v = p_pyx;
- }
-
- // now source, if required python version is not supported show
- // unobtrusive message.
- if (eap == NULL) {
- memset(&ex, 0, sizeof(ex));
- } else {
- ex = *eap;
- }
- ex.arg = fname;
- ex.cmd = (char_u *)(v == 2 ? "pyfile" : "pyfile3");
-
- if (v == 2) {
- ex_pyfile(&ex);
- } else {
- ex_py3file(&ex);
- }
-}
-
-// ":pyxfile {fname}"
-void ex_pyxfile(exarg_T *eap)
-{
- source_pyx_file(eap, eap->arg);
-}
-
-// ":pyx"
-void ex_pyx(exarg_T *eap)
-{
- init_pyxversion();
- if (p_pyx == 2) {
- ex_python(eap);
- } else {
- ex_python3(eap);
- }
-}
-
-// ":pyxdo"
-void ex_pyxdo(exarg_T *eap)
-{
- init_pyxversion();
- if (p_pyx == 2) {
- ex_pydo(eap);
- } else {
- ex_pydo3(eap);
- }
-}
-
/// ":source [{fname}]"
void ex_source(exarg_T *eap)
{
@@ -1957,7 +1823,9 @@ static int source_using_linegetter(void *cookie, LineGetter fgetline, const char
sourcing_lnum = 0;
const sctx_T save_current_sctx = current_sctx;
- current_sctx.sc_sid = SID_STR;
+ if (current_sctx.sc_sid != SID_LUA) {
+ current_sctx.sc_sid = SID_STR;
+ }
current_sctx.sc_seq = 0;
current_sctx.sc_lnum = save_sourcing_lnum;
funccal_entry_T entry;
@@ -2038,7 +1906,6 @@ int do_source(char *fname, int check_other, int is_vimrc)
char_u *fname_exp;
char_u *firstline = NULL;
int retval = FAIL;
- static int last_current_SID_seq = 0;
int save_debug_break_level = debug_break_level;
scriptitem_T *si = NULL;
proftime_T wait_start;
@@ -2086,7 +1953,7 @@ int do_source(char *fname, int check_other, int is_vimrc)
}
if (cookie.fp == NULL) {
- if (p_verbose > 0) {
+ if (p_verbose > 1) {
verbose_enter();
if (sourcing_name == NULL) {
smsg(_("could not source \"%s\""), fname);
@@ -2162,37 +2029,8 @@ int do_source(char *fname, int check_other, int is_vimrc)
funccal_entry_T funccalp_entry;
save_funccal(&funccalp_entry);
- // Check if this script was sourced before to finds its SID.
- // If it's new, generate a new SID.
- // Always use a new sequence number.
const sctx_T save_current_sctx = current_sctx;
- current_sctx.sc_seq = ++last_current_SID_seq;
- current_sctx.sc_lnum = 0;
- FileID file_id;
- bool file_id_ok = os_fileid((char *)fname_exp, &file_id);
- assert(script_items.ga_len >= 0);
- for (current_sctx.sc_sid = script_items.ga_len; current_sctx.sc_sid > 0;
- current_sctx.sc_sid--) {
- si = &SCRIPT_ITEM(current_sctx.sc_sid);
- // Compare dev/ino when possible, it catches symbolic links.
- // Also compare file names, the inode may change when the file was edited.
- bool file_id_equal = file_id_ok && si->file_id_valid
- && os_fileid_equal(&(si->file_id), &file_id);
- if (si->sn_name != NULL
- && (file_id_equal || fnamecmp(si->sn_name, fname_exp) == 0)) {
- break;
- }
- }
- if (current_sctx.sc_sid == 0) {
- si = new_script_item(fname_exp, &current_sctx.sc_sid);
- fname_exp = vim_strsave(si->sn_name); // used for autocmd
- if (file_id_ok) {
- si->file_id_valid = true;
- si->file_id = file_id;
- } else {
- si->file_id_valid = false;
- }
- }
+ si = get_current_script_id(fname_exp, &current_sctx);
if (l_do_profiling == PROF_YES) {
bool forceit = false;
@@ -2225,16 +2063,14 @@ int do_source(char *fname, int check_other, int is_vimrc)
firstline = p;
}
- if (path_with_extension((const char *)fname, "lua")) {
- // TODO(shadmansaleh): Properly handle :verbose for lua
- // For now change currennt_sctx before sourcing lua files
- // So verbose doesn't say everything was done in line 1 since we don't know
+ if (path_with_extension((const char *)fname_exp, "lua")) {
const sctx_T current_sctx_backup = current_sctx;
const linenr_T sourcing_lnum_backup = sourcing_lnum;
+ current_sctx.sc_sid = SID_LUA;
current_sctx.sc_lnum = 0;
sourcing_lnum = 0;
// Source the file as lua
- nlua_exec_file((const char *)fname);
+ nlua_exec_file((const char *)fname_exp);
current_sctx = current_sctx_backup;
sourcing_lnum = sourcing_lnum_backup;
} else {
@@ -2307,6 +2143,52 @@ theend:
}
+/// Check if fname was sourced before to finds its SID.
+/// If it's new, generate a new SID.
+/// @param[in] fname file path of script
+/// @param[out] ret_sctx sctx of this script
+scriptitem_T *get_current_script_id(char_u *fname, sctx_T *ret_sctx)
+{
+ static int last_current_SID_seq = 0;
+
+ sctx_T script_sctx = { .sc_seq = ++last_current_SID_seq,
+ .sc_lnum = 0,
+ .sc_sid = 0 };
+ FileID file_id;
+ scriptitem_T *si = NULL;
+
+ bool file_id_ok = os_fileid((char *)fname, &file_id);
+ assert(script_items.ga_len >= 0);
+ for (script_sctx.sc_sid = script_items.ga_len; script_sctx.sc_sid > 0;
+ script_sctx.sc_sid--) {
+ si = &SCRIPT_ITEM(script_sctx.sc_sid);
+ // Compare dev/ino when possible, it catches symbolic links.
+ // Also compare file names, the inode may change when the file was edited.
+ bool file_id_equal = file_id_ok && si->file_id_valid
+ && os_fileid_equal(&(si->file_id), &file_id);
+ if (si->sn_name != NULL
+ && (file_id_equal || fnamecmp(si->sn_name, fname) == 0)) {
+ break;
+ }
+ }
+ if (script_sctx.sc_sid == 0) {
+ si = new_script_item(vim_strsave(fname), &script_sctx.sc_sid);
+ if (file_id_ok) {
+ si->file_id_valid = true;
+ si->file_id = file_id;
+ } else {
+ si->file_id_valid = false;
+ }
+ }
+ if (ret_sctx != NULL) {
+ *ret_sctx = script_sctx;
+ }
+
+ return si;
+}
+
+
+
/// ":scriptnames"
void ex_scriptnames(exarg_T *eap)
{
@@ -2323,9 +2205,13 @@ void ex_scriptnames(exarg_T *eap)
for (int i = 1; i <= script_items.ga_len && !got_int; i++) {
if (SCRIPT_ITEM(i).sn_name != NULL) {
- home_replace(NULL, SCRIPT_ITEM(i).sn_name,
- NameBuff, MAXPATHL, true);
- smsg("%3d: %s", i, NameBuff);
+ home_replace(NULL, SCRIPT_ITEM(i).sn_name, NameBuff, MAXPATHL, true);
+ vim_snprintf((char *)IObuff, IOSIZE, "%3d: %s", i, NameBuff);
+ if (!message_filtered(IObuff)) {
+ msg_putchar('\n');
+ msg_outtrans(IObuff);
+ line_breakcheck();
+ }
}
}
}
diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h
index ea899b660b..92675499be 100644
--- a/src/nvim/ex_cmds_defs.h
+++ b/src/nvim/ex_cmds_defs.h
@@ -4,6 +4,7 @@
#include <stdbool.h>
#include <stdint.h>
+#include "nvim/eval/typval.h"
#include "nvim/normal.h"
#include "nvim/pos.h" // for linenr_T
#include "nvim/regexp_defs.h"
@@ -61,6 +62,7 @@
// current buffer is locked
#define EX_MODIFY 0x100000 // forbidden in non-'modifiable' buffer
#define EX_FLAGS 0x200000 // allow flags after count in argument
+#define EX_KEEPSCRIPT 0x4000000 // keep sctx of where command was invoked
#define EX_FILES (EX_XFILE | EX_EXTRA) // multiple extra files allowed
#define EX_FILE1 (EX_FILES | EX_NOSPC) // 1 file, defaults to current file
#define EX_WORD1 (EX_EXTRA | EX_NOSPC) // one extra word allowed
@@ -90,6 +92,35 @@ typedef struct exarg exarg_T;
typedef void (*ex_func_T)(exarg_T *eap);
+// NOTE: These possible could be removed and changed so that
+// Callback could take a "command" style string, and simply
+// execute that (instead of it being a function).
+//
+// But it's still a bit weird to do that.
+//
+// Another option would be that we just make a callback reference to
+// "execute($INPUT)" or something like that, so whatever the user
+// sends in via autocmds is just executed via this.
+//
+// However, that would probably have some performance cost (probably
+// very marginal, but still some cost either way).
+typedef enum {
+ CALLABLE_NONE,
+ CALLABLE_EX,
+ CALLABLE_CB,
+} AucmdExecutableType;
+
+typedef struct aucmd_executable_t AucmdExecutable;
+struct aucmd_executable_t {
+ AucmdExecutableType type;
+ union {
+ char_u *cmd;
+ Callback cb;
+ } callable;
+};
+
+#define AUCMD_EXECUTABLE_INIT { .type = CALLABLE_NONE }
+
typedef char_u *(*LineGetter)(int, void *, int, bool);
/// Structure for command definition.
@@ -192,6 +223,7 @@ struct expand {
int xp_context; // type of expansion
size_t xp_pattern_len; // bytes in xp_pattern before cursor
char_u *xp_arg; // completion function
+ LuaRef xp_luaref; // Ref to Lua completion function
sctx_T xp_script_ctx; // SCTX for completion function
int xp_backslash; // one of the XP_BS_ values
#ifndef BACKSLASH_IN_FILENAME
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 230cbd4b60..d3203bcee8 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -67,8 +67,8 @@
#include "nvim/sign.h"
#include "nvim/spell.h"
#include "nvim/spellfile.h"
-#include "nvim/strings.h"
#include "nvim/state.h"
+#include "nvim/strings.h"
#include "nvim/syntax.h"
#include "nvim/tag.h"
#include "nvim/terminal.h"
@@ -78,26 +78,14 @@
#include "nvim/vim.h"
#include "nvim/window.h"
+static char *e_no_such_user_defined_command_str = N_("E184: No such user-defined command: %s");
+static char *e_no_such_user_defined_command_in_current_buffer_str
+ = N_("E1237: No such user-defined command in current buffer: %s");
+
static int quitmore = 0;
static bool ex_pressedreturn = false;
-typedef struct ucmd {
- char_u *uc_name; // The command name
- uint32_t uc_argt; // The argument type
- char_u *uc_rep; // The command's replacement string
- long uc_def; // The default value for a range/count
- int uc_compl; // completion type
- cmd_addr_T uc_addr_type; // The command's address type
- sctx_T uc_script_ctx; // SCTX where the command was defined
- char_u *uc_compl_arg; // completion argument if any
-} ucmd_T;
-
-#define UC_BUFFER 1 // -buffer: local to current buffer
-
-static garray_T ucmds = { 0, 0, sizeof(ucmd_T), 4, NULL };
-
-#define USER_CMD(i) (&((ucmd_T *)(ucmds.ga_data))[i])
-#define USER_CMD_GA(gap, i) (&((ucmd_T *)((gap)->ga_data))[i])
+garray_T ucmds = { 0, 0, sizeof(ucmd_T), 4, NULL };
// Whether a command index indicates a user command.
#define IS_USER_CMDIDX(idx) ((int)(idx) < 0)
@@ -1774,7 +1762,9 @@ static char_u *do_one_cmd(char_u **cmdlinep, int flags, cstack_T *cstack, LineGe
ea.regname = *ea.arg++;
// for '=' register: accept the rest of the line as an expression
if (ea.arg[-1] == '=' && ea.arg[0] != NUL) {
- set_expr_line(vim_strsave(ea.arg));
+ if (!ea.skip) {
+ set_expr_line(vim_strsave(ea.arg));
+ }
ea.arg += STRLEN(ea.arg);
}
ea.arg = skipwhite(ea.arg);
@@ -2713,10 +2703,8 @@ static char_u *find_ucmd(exarg_T *eap, char_u *p, int *full, expand_T *xp, int *
bool amb_local = false; // Found ambiguous buffer-local command,
// only full match global is accepted.
- /*
- * Look for buffer-local user commands first, then global ones.
- */
- gap = &curbuf->b_ucmds;
+ // Look for buffer-local user commands first, then global ones.
+ gap = is_in_cmdwin() ? &prevwin->w_buffer->b_ucmds : &curbuf->b_ucmds;
for (;;) {
for (j = 0; j < gap->ga_len; j++) {
uc = USER_CMD_GA(gap, j);
@@ -2761,6 +2749,7 @@ static char_u *find_ucmd(exarg_T *eap, char_u *p, int *full, expand_T *xp, int *
*complp = uc->uc_compl;
}
if (xp != NULL) {
+ xp->xp_luaref = uc->uc_compl_luaref;
xp->xp_arg = uc->uc_compl_arg;
xp->xp_script_ctx = uc->uc_script_ctx;
xp->xp_script_ctx.sc_lnum += sourcing_lnum;
@@ -2906,13 +2895,17 @@ void f_fullcommand(typval_T *argvars, typval_T *rettv, FunPtr fptr)
exarg_T ea;
char_u *name = argvars[0].vval.v_string;
- while (name[0] != NUL && name[0] == ':') {
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+ if (name == NULL) {
+ return;
+ }
+
+ while (*name == ':') {
name++;
}
name = skip_range(name, NULL);
- rettv->v_type = VAR_STRING;
-
ea.cmd = (*name == '2' || *name == '3') ? name + 1 : name;
ea.cmdidx = (cmdidx_T)0;
char_u *p = find_command(&ea, NULL);
@@ -2921,7 +2914,7 @@ void f_fullcommand(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
rettv->vval.v_string = vim_strsave(IS_USER_CMDIDX(ea.cmdidx)
- ? get_user_commands(NULL, ea.useridx)
+ ? get_user_command_name(ea.useridx, ea.cmdidx)
: cmdnames[ea.cmdidx].cmd_name);
}
@@ -4148,7 +4141,11 @@ static linenr_T get_address(exarg_T *eap, char_u **ptr, cmd_addr_T addr_type, in
if (!ascii_isdigit(*cmd)) { // '+' is '+1', but '+0' is not '+1'
n = 1;
} else {
- n = getdigits(&cmd, true, 0);
+ n = getdigits(&cmd, false, MAXLNUM);
+ if (n == MAXLNUM) {
+ emsg(_(e_line_number_out_of_range));
+ goto error;
+ }
}
if (addr_type == ADDR_TABS_RELATIVE) {
@@ -4167,6 +4164,10 @@ static linenr_T get_address(exarg_T *eap, char_u **ptr, cmd_addr_T addr_type, in
if (i == '-') {
lnum -= n;
} else {
+ if (n >= LONG_MAX - lnum) {
+ emsg(_(e_line_number_out_of_range));
+ goto error;
+ }
lnum += n;
}
}
@@ -4385,7 +4386,7 @@ static char_u *replace_makeprg(exarg_T *eap, char_u *p, char_u **cmdlinep)
++i;
}
len = (int)STRLEN(p);
- new_cmdline = xmalloc(STRLEN(program) + i * (len - 2) + 1);
+ new_cmdline = xmalloc(STRLEN(program) + (size_t)i * (len - 2) + 1);
ptr = new_cmdline;
while ((pos = (char_u *)strstr((char *)program, "$*")) != NULL) {
i = (int)(pos - program);
@@ -5166,13 +5167,32 @@ static int check_more(int message, bool forceit)
char_u *get_command_name(expand_T *xp, int idx)
{
if (idx >= CMD_SIZE) {
- return get_user_command_name(idx);
+ return expand_user_command_name(idx);
}
return cmdnames[idx].cmd_name;
}
-static int uc_add_command(char_u *name, size_t name_len, char_u *rep, uint32_t argt, long def,
- int flags, int compl, char_u *compl_arg, cmd_addr_T addr_type, bool force)
+/// Check for a valid user command name
+///
+/// If the given {name} is valid, then a pointer to the end of the valid name is returned.
+/// Otherwise, returns NULL.
+char *uc_validate_name(char *name)
+{
+ if (ASCII_ISALPHA(*name)) {
+ while (ASCII_ISALNUM(*name)) {
+ name++;
+ }
+ }
+ if (!ends_excmd(*name) && !ascii_iswhite(*name)) {
+ return NULL;
+ }
+
+ return name;
+}
+
+int uc_add_command(char_u *name, size_t name_len, char_u *rep, uint32_t argt, long def, int flags,
+ int compl, char_u *compl_arg, LuaRef compl_luaref, cmd_addr_T addr_type,
+ LuaRef luaref, bool force)
FUNC_ATTR_NONNULL_ARG(1, 3)
{
ucmd_T *cmd = NULL;
@@ -5226,6 +5246,8 @@ static int uc_add_command(char_u *name, size_t name_len, char_u *rep, uint32_t a
XFREE_CLEAR(cmd->uc_rep);
XFREE_CLEAR(cmd->uc_compl_arg);
+ NLUA_CLEAR_REF(cmd->uc_luaref);
+ NLUA_CLEAR_REF(cmd->uc_compl_luaref);
break;
}
@@ -5255,14 +5277,19 @@ static int uc_add_command(char_u *name, size_t name_len, char_u *rep, uint32_t a
cmd->uc_compl = compl;
cmd->uc_script_ctx = current_sctx;
cmd->uc_script_ctx.sc_lnum += sourcing_lnum;
+ nlua_set_sctx(&cmd->uc_script_ctx);
cmd->uc_compl_arg = compl_arg;
+ cmd->uc_compl_luaref = compl_luaref;
cmd->uc_addr_type = addr_type;
+ cmd->uc_luaref = luaref;
return OK;
fail:
xfree(rep_buf);
xfree(compl_arg);
+ NLUA_CLEAR_REF(luaref);
+ NLUA_CLEAR_REF(compl_luaref);
return FAIL;
}
@@ -5301,6 +5328,7 @@ static const char *command_complete[] =
[EXPAND_CSCOPE] = "cscope",
[EXPAND_USER_DEFINED] = "custom",
[EXPAND_USER_LIST] = "customlist",
+ [EXPAND_USER_LUA] = "<Lua function>",
[EXPAND_DIFF_BUFFERS] = "diff_buffer",
[EXPAND_DIRECTORIES] = "dir",
[EXPAND_ENV_VARS] = "environment",
@@ -5350,9 +5378,7 @@ static void uc_list(char_u *name, size_t name_len)
uint32_t a;
// In cmdwin, the alternative buffer should be used.
- garray_T *gap = (cmdwin_type != 0 && get_cmdline_type() == NUL)
- ? &prevwin->w_buffer->b_ucmds
- : &curbuf->b_ucmds;
+ garray_T *gap = is_in_cmdwin() ? &prevwin->w_buffer->b_ucmds : &curbuf->b_ucmds;
for (;;) {
for (i = 0; i < gap->ga_len; i++) {
cmd = USER_CMD_GA(gap, i);
@@ -5524,6 +5550,8 @@ static int uc_scan_attr(char_u *attr, size_t len, uint32_t *argt, long *def, int
*flags |= UC_BUFFER;
} else if (STRNICMP(attr, "register", len) == 0) {
*argt |= EX_REGSTR;
+ } else if (STRNICMP(attr, "keepscript", len) == 0) {
+ *argt |= EX_KEEPSCRIPT;
} else if (STRNICMP(attr, "bar", len) == 0) {
*argt |= EX_TRLBAR;
} else {
@@ -5678,23 +5706,18 @@ static void ex_command(exarg_T *eap)
// Get the name (if any) and skip to the following argument.
name = p;
- if (ASCII_ISALPHA(*p)) {
- while (ASCII_ISALNUM(*p)) {
- p++;
- }
- }
- if (!ends_excmd(*p) && !ascii_iswhite(*p)) {
+ end = (char_u *)uc_validate_name((char *)name);
+ if (!end) {
emsg(_("E182: Invalid command name"));
return;
}
- end = p;
- name_len = (int)(end - name);
+ name_len = (size_t)(end - name);
// If there is nothing after the name, and no attributes were specified,
// we are listing commands
p = skipwhite(end);
if (!has_attr && ends_excmd(*p)) {
- uc_list(name, end - name);
+ uc_list(name, name_len);
} else if (!ASCII_ISUPPER(*name)) {
emsg(_("E183: User defined commands must start with an uppercase letter"));
} else if (name_len <= 4 && STRNCMP(name, "Next", name_len) == 0) {
@@ -5702,8 +5725,8 @@ static void ex_command(exarg_T *eap)
} else if (compl > 0 && (argt & EX_EXTRA) == 0) {
emsg(_(e_complete_used_without_nargs));
} else {
- uc_add_command(name, end - name, p, argt, def, flags, compl, compl_arg,
- addr_type_arg, eap->forceit);
+ uc_add_command(name, name_len, p, argt, def, flags, compl, compl_arg, LUA_NOREF,
+ addr_type_arg, LUA_NOREF, eap->forceit);
}
}
@@ -5717,11 +5740,13 @@ void ex_comclear(exarg_T *eap)
uc_clear(&curbuf->b_ucmds);
}
-static void free_ucmd(ucmd_T *cmd)
+void free_ucmd(ucmd_T *cmd)
{
xfree(cmd->uc_name);
xfree(cmd->uc_rep);
xfree(cmd->uc_compl_arg);
+ NLUA_CLEAR_REF(cmd->uc_compl_luaref);
+ NLUA_CLEAR_REF(cmd->uc_luaref);
}
/*
@@ -5736,32 +5761,40 @@ static void ex_delcommand(exarg_T *eap)
{
int i = 0;
ucmd_T *cmd = NULL;
- int cmp = -1;
+ int res = -1;
garray_T *gap;
+ const char_u *arg = eap->arg;
+ bool buffer_only = false;
+
+ if (STRNCMP(arg, "-buffer", 7) == 0 && ascii_iswhite(arg[7])) {
+ buffer_only = true;
+ arg = skipwhite(arg + 7);
+ }
gap = &curbuf->b_ucmds;
for (;;) {
for (i = 0; i < gap->ga_len; i++) {
cmd = USER_CMD_GA(gap, i);
- cmp = STRCMP(eap->arg, cmd->uc_name);
- if (cmp <= 0) {
+ res = STRCMP(arg, cmd->uc_name);
+ if (res <= 0) {
break;
}
}
- if (gap == &ucmds || cmp == 0) {
+ if (gap == &ucmds || res == 0 || buffer_only) {
break;
}
gap = &ucmds;
}
- if (cmp != 0) {
- semsg(_("E184: No such user-defined command: %s"), eap->arg);
+ if (res != 0) {
+ semsg(_(buffer_only
+ ? e_no_such_user_defined_command_in_current_buffer_str
+ : e_no_such_user_defined_command_str),
+ arg);
return;
}
- xfree(cmd->uc_name);
- xfree(cmd->uc_rep);
- xfree(cmd->uc_compl_arg);
+ free_ucmd(cmd);
--gap->ga_len;
@@ -5770,6 +5803,30 @@ static void ex_delcommand(exarg_T *eap)
}
}
+/// Split a string by unescaped whitespace (space & tab), used for f-args on Lua commands callback.
+/// Similar to uc_split_args(), but does not allocate, add quotes, add commas and is an iterator.
+///
+/// @note If no separator is found start = 0 and end = length - 1
+/// @param[in] arg String to split
+/// @param[in] iter Iteration counter
+/// @param[out] start Start of the split
+/// @param[out] end End of the split
+/// @param[in] length Length of the string
+/// @return false if it's the last split (don't call again), true otherwise (call again).
+bool uc_split_args_iter(const char_u *arg, int iter, int *start, int *end, int length)
+{
+ int pos;
+ *start = *end + (iter > 1 ? 2 : 0); // Skip whitespace after the first split
+ for (pos = *start; pos < length - 2; pos++) {
+ if (arg[pos] != '\\' && ascii_iswhite(arg[pos + 1])) {
+ *end = pos;
+ return true;
+ }
+ }
+ *end = length - 1;
+ return false;
+}
+
/*
* split and quote args for <f-args>
*/
@@ -5843,7 +5900,7 @@ static char_u *uc_split_args(char_u *arg, size_t *lenp)
return buf;
}
-static size_t add_cmd_modifier(char_u *buf, char *mod_str, bool *multi_mods)
+static size_t add_cmd_modifier(char *buf, char *mod_str, bool *multi_mods)
{
size_t result = STRLEN(mod_str);
if (*multi_mods) {
@@ -6035,7 +6092,7 @@ static size_t uc_check_code(char_u *code, size_t len, char_u *buf, ucmd_T *cmd,
break;
}
- case ct_MODS: {
+ case ct_MODS:
result = quote ? 2 : 0;
if (buf != NULL) {
if (quote) {
@@ -6044,76 +6101,13 @@ static size_t uc_check_code(char_u *code, size_t len, char_u *buf, ucmd_T *cmd,
*buf = '\0';
}
- bool multi_mods = false;
-
- // :aboveleft and :leftabove
- if (cmdmod.split & WSP_ABOVE) {
- result += add_cmd_modifier(buf, "aboveleft", &multi_mods);
- }
- // :belowright and :rightbelow
- if (cmdmod.split & WSP_BELOW) {
- result += add_cmd_modifier(buf, "belowright", &multi_mods);
- }
- // :botright
- if (cmdmod.split & WSP_BOT) {
- result += add_cmd_modifier(buf, "botright", &multi_mods);
- }
-
- typedef struct {
- bool *set;
- char *name;
- } mod_entry_T;
- static mod_entry_T mod_entries[] = {
- { &cmdmod.browse, "browse" },
- { &cmdmod.confirm, "confirm" },
- { &cmdmod.hide, "hide" },
- { &cmdmod.keepalt, "keepalt" },
- { &cmdmod.keepjumps, "keepjumps" },
- { &cmdmod.keepmarks, "keepmarks" },
- { &cmdmod.keeppatterns, "keeppatterns" },
- { &cmdmod.lockmarks, "lockmarks" },
- { &cmdmod.noswapfile, "noswapfile" }
- };
- // the modifiers that are simple flags
- for (size_t i = 0; i < ARRAY_SIZE(mod_entries); i++) {
- if (*mod_entries[i].set) {
- result += add_cmd_modifier(buf, mod_entries[i].name, &multi_mods);
- }
- }
-
- // TODO(vim): How to support :noautocmd?
- // TODO(vim): How to support :sandbox?
+ result += uc_mods((char *)buf);
- // :silent
- if (msg_silent > 0) {
- result += add_cmd_modifier(buf, emsg_silent > 0 ? "silent!" : "silent",
- &multi_mods);
- }
- // :tab
- if (cmdmod.tab > 0) {
- result += add_cmd_modifier(buf, "tab", &multi_mods);
- }
- // :topleft
- if (cmdmod.split & WSP_TOP) {
- result += add_cmd_modifier(buf, "topleft", &multi_mods);
- }
-
- // TODO(vim): How to support :unsilent?
-
- // :verbose
- if (p_verbose > 0) {
- result += add_cmd_modifier(buf, "verbose", &multi_mods);
- }
- // :vertical
- if (cmdmod.split & WSP_VERT) {
- result += add_cmd_modifier(buf, "vertical", &multi_mods);
- }
if (quote && buf != NULL) {
buf += result - 2;
*buf = '"';
}
break;
- }
case ct_REGISTER:
result = eap->regname ? 1 : 0;
@@ -6152,6 +6146,76 @@ static size_t uc_check_code(char_u *code, size_t len, char_u *buf, ucmd_T *cmd,
return result;
}
+size_t uc_mods(char *buf)
+{
+ size_t result = 0;
+ bool multi_mods = false;
+
+ // :aboveleft and :leftabove
+ if (cmdmod.split & WSP_ABOVE) {
+ result += add_cmd_modifier(buf, "aboveleft", &multi_mods);
+ }
+ // :belowright and :rightbelow
+ if (cmdmod.split & WSP_BELOW) {
+ result += add_cmd_modifier(buf, "belowright", &multi_mods);
+ }
+ // :botright
+ if (cmdmod.split & WSP_BOT) {
+ result += add_cmd_modifier(buf, "botright", &multi_mods);
+ }
+
+ typedef struct {
+ bool *set;
+ char *name;
+ } mod_entry_T;
+ static mod_entry_T mod_entries[] = {
+ { &cmdmod.browse, "browse" },
+ { &cmdmod.confirm, "confirm" },
+ { &cmdmod.hide, "hide" },
+ { &cmdmod.keepalt, "keepalt" },
+ { &cmdmod.keepjumps, "keepjumps" },
+ { &cmdmod.keepmarks, "keepmarks" },
+ { &cmdmod.keeppatterns, "keeppatterns" },
+ { &cmdmod.lockmarks, "lockmarks" },
+ { &cmdmod.noswapfile, "noswapfile" }
+ };
+ // the modifiers that are simple flags
+ for (size_t i = 0; i < ARRAY_SIZE(mod_entries); i++) {
+ if (*mod_entries[i].set) {
+ result += add_cmd_modifier(buf, mod_entries[i].name, &multi_mods);
+ }
+ }
+
+ // TODO(vim): How to support :noautocmd?
+ // TODO(vim): How to support :sandbox?
+
+ // :silent
+ if (msg_silent > 0) {
+ result += add_cmd_modifier(buf, emsg_silent > 0 ? "silent!" : "silent", &multi_mods);
+ }
+ // :tab
+ if (cmdmod.tab > 0) {
+ result += add_cmd_modifier(buf, "tab", &multi_mods);
+ }
+ // :topleft
+ if (cmdmod.split & WSP_TOP) {
+ result += add_cmd_modifier(buf, "topleft", &multi_mods);
+ }
+
+ // TODO(vim): How to support :unsilent?
+
+ // :verbose
+ if (p_verbose > 0) {
+ result += add_cmd_modifier(buf, "verbose", &multi_mods);
+ }
+ // :vertical
+ if (cmdmod.split & WSP_VERT) {
+ result += add_cmd_modifier(buf, "vertical", &multi_mods);
+ }
+
+ return result;
+}
+
static void do_ucmd(exarg_T *eap)
{
char_u *buf;
@@ -6174,6 +6238,11 @@ static void do_ucmd(exarg_T *eap)
cmd = USER_CMD_GA(&curbuf->b_ucmds, eap->useridx);
}
+ if (cmd->uc_luaref > 0) {
+ nlua_do_ucmd(cmd, eap);
+ return;
+ }
+
/*
* Replace <> in the command by the arguments.
* First round: "buf" is NULL, compute length, allocate "buf".
@@ -6199,7 +6268,6 @@ static void do_ucmd(exarg_T *eap)
// K_SPECIAL has been put in the buffer as K_SPECIAL
// KS_SPECIAL KE_FILLER, like for mappings, but
// do_cmdline() doesn't handle that, so convert it back.
- // Also change K_SPECIAL KS_EXTRA KE_CSI into CSI.
len = ksp - p;
if (len > 0) {
memmove(q, p, len);
@@ -6252,15 +6320,19 @@ static void do_ucmd(exarg_T *eap)
buf = xmalloc(totlen + 1);
}
- current_sctx.sc_sid = cmd->uc_script_ctx.sc_sid;
+ if ((cmd->uc_argt & EX_KEEPSCRIPT) == 0) {
+ current_sctx.sc_sid = cmd->uc_script_ctx.sc_sid;
+ }
(void)do_cmdline(buf, eap->getline, eap->cookie,
DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED);
- current_sctx = save_current_sctx;
+ if ((cmd->uc_argt & EX_KEEPSCRIPT) == 0) {
+ current_sctx = save_current_sctx;
+ }
xfree(buf);
xfree(split_buf);
}
-static char_u *get_user_command_name(int idx)
+static char_u *expand_user_command_name(int idx)
{
return get_user_commands(NULL, idx - CMD_SIZE);
}
@@ -6279,9 +6351,7 @@ char_u *get_user_commands(expand_T *xp FUNC_ATTR_UNUSED, int idx)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
// In cmdwin, the alternative buffer should be used.
- const buf_T *const buf = (cmdwin_type != 0 && get_cmdline_type() == NUL)
- ? prevwin->w_buffer
- : curbuf;
+ const buf_T *const buf = is_in_cmdwin() ? prevwin->w_buffer : curbuf;
if (idx < buf->b_ucmds.ga_len) {
return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
@@ -6293,6 +6363,24 @@ char_u *get_user_commands(expand_T *xp FUNC_ATTR_UNUSED, int idx)
return NULL;
}
+// Get the name of user command "idx". "cmdidx" can be CMD_USER or
+// CMD_USER_BUF.
+// Returns NULL if the command is not found.
+static char_u *get_user_command_name(int idx, int cmdidx)
+{
+ if (cmdidx == CMD_USER && idx < ucmds.ga_len) {
+ return USER_CMD(idx)->uc_name;
+ }
+ if (cmdidx == CMD_USER_BUF) {
+ // In cmdwin, the alternative buffer should be used.
+ buf_T *buf = is_in_cmdwin() ? prevwin->w_buffer : curbuf;
+ if (idx < buf->b_ucmds.ga_len) {
+ return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
+ }
+ }
+ return NULL;
+}
+
/*
* Function given to ExpandGeneric() to obtain the list of user command
* attributes.
@@ -6301,7 +6389,7 @@ char_u *get_user_cmd_flags(expand_T *xp, int idx)
{
static char *user_cmd_flags[] = { "addr", "bang", "bar",
"buffer", "complete", "count",
- "nargs", "range", "register" };
+ "nargs", "range", "register", "keepscript" };
if (idx >= (int)ARRAY_SIZE(user_cmd_flags)) {
return NULL;
@@ -6577,7 +6665,7 @@ static void ex_quit(exarg_T *eap)
}
not_exiting();
// close window; may free buffer
- win_close(wp, !buf_hide(wp->w_buffer) || eap->forceit);
+ win_close(wp, !buf_hide(wp->w_buffer) || eap->forceit, eap->forceit);
}
}
@@ -6692,7 +6780,7 @@ void ex_win_close(int forceit, win_T *win, tabpage_T *tp)
// free buffer when not hiding it or when it's a scratch buffer
if (tp == NULL) {
- win_close(win, !need_hide && !buf_hide(buf));
+ win_close(win, !need_hide && !buf_hide(buf), forceit);
} else {
win_close_othertab(win, !need_hide && !buf_hide(buf), tp);
}
@@ -6856,7 +6944,7 @@ static void ex_hide(exarg_T *eap)
// ":hide" or ":hide | cmd": hide current window
if (!eap->skip) {
if (eap->addr_count == 0) {
- win_close(curwin, false); // don't free buffer
+ win_close(curwin, false, eap->forceit); // don't free buffer
} else {
int winnr = 0;
win_T *win = NULL;
@@ -6871,7 +6959,7 @@ static void ex_hide(exarg_T *eap)
if (win == NULL) {
win = lastwin;
}
- win_close(win, false);
+ win_close(win, false, eap->forceit);
}
}
}
@@ -6929,7 +7017,7 @@ static void ex_exit(exarg_T *eap)
}
not_exiting();
// Quit current window, may free the buffer.
- win_close(curwin, !buf_hide(curwin->w_buffer));
+ win_close(curwin, !buf_hide(curwin->w_buffer), eap->forceit);
}
}
@@ -7443,7 +7531,7 @@ static void ex_edit(exarg_T *eap)
do_exedit(eap, NULL);
}
-/// ":edit <file>" command and alikes.
+/// ":edit <file>" command and alike.
///
/// @param old_curwin curwin before doing a split or NULL
void do_exedit(exarg_T *eap, win_T *old_curwin)
@@ -7489,9 +7577,8 @@ void do_exedit(exarg_T *eap, win_T *old_curwin)
if ((eap->cmdidx == CMD_new
|| eap->cmdidx == CMD_tabnew
|| eap->cmdidx == CMD_tabedit
- || eap->cmdidx == CMD_vnew
- ) && *eap->arg == NUL) {
- // ":new" or ":tabnew" without argument: edit an new empty buffer
+ || eap->cmdidx == CMD_vnew) && *eap->arg == NUL) {
+ // ":new" or ":tabnew" without argument: edit a new empty buffer
setpcmark();
(void)do_ecmd(0, NULL, NULL, eap, ECMD_ONE,
ECMD_HIDE + (eap->forceit ? ECMD_FORCEIT : 0),
@@ -7531,7 +7618,7 @@ void do_exedit(exarg_T *eap, win_T *old_curwin)
// Reset the error/interrupt/exception state here so that
// aborting() returns FALSE when closing a window.
enter_cleanup(&cs);
- win_close(curwin, !need_hide && !buf_hide(curbuf));
+ win_close(curwin, !need_hide && !buf_hide(curbuf), false);
// Restore the error/interrupt/exception state if not
// discarded by a new aborting error, interrupt, or
@@ -7743,7 +7830,7 @@ static char_u *get_prevdir(CdScope scope)
/// Deal with the side effects of changing the current directory.
///
/// @param scope Scope of the function call (global, tab or window).
-void post_chdir(CdScope scope, bool trigger_dirchanged)
+static void post_chdir(CdScope scope, bool trigger_dirchanged)
{
// Always overwrite the window-local CWD.
XFREE_CLEAR(curwin->w_localdir);
@@ -7784,7 +7871,7 @@ void post_chdir(CdScope scope, bool trigger_dirchanged)
shorten_fnames(true);
if (trigger_dirchanged) {
- do_autocmd_dirchanged(cwd, scope, kCdCauseManual);
+ do_autocmd_dirchanged(cwd, scope, kCdCauseManual, false);
}
}
@@ -7794,14 +7881,11 @@ void post_chdir(CdScope scope, bool trigger_dirchanged)
/// @return true if the directory is successfully changed.
bool changedir_func(char_u *new_dir, CdScope scope)
{
- char_u *tofree;
- char_u *pdir = NULL;
- bool retval = false;
-
if (new_dir == NULL || allbuf_locked()) {
return false;
}
+ char_u *pdir = NULL;
// ":cd -": Change to previous directory
if (STRCMP(new_dir, "-") == 0) {
pdir = get_prevdir(scope);
@@ -7812,26 +7896,12 @@ bool changedir_func(char_u *new_dir, CdScope scope)
new_dir = pdir;
}
- // Free the previous directory
- tofree = get_prevdir(scope);
-
if (os_dirname(NameBuff, MAXPATHL) == OK) {
pdir = vim_strsave(NameBuff);
} else {
pdir = NULL;
}
- switch (scope) {
- case kCdScopeTabpage:
- curtab->tp_prevdir = pdir;
- break;
- case kCdScopeWindow:
- curwin->w_prevdir = pdir;
- break;
- default:
- prev_dir = pdir;
- }
-
// For UNIX ":cd" means: go to home directory.
// On other systems too if 'cdhome' is set.
#if defined(UNIX)
@@ -7844,17 +7914,33 @@ bool changedir_func(char_u *new_dir, CdScope scope)
new_dir = NameBuff;
}
- bool dir_differs = new_dir == NULL || pdir == NULL
- || pathcmp((char *)pdir, (char *)new_dir, -1) != 0;
- if (new_dir != NULL && (!dir_differs || vim_chdir(new_dir) == 0)) {
- post_chdir(scope, dir_differs);
- retval = true;
- } else {
- emsg(_(e_failed));
+ bool dir_differs = pdir == NULL || pathcmp((char *)pdir, (char *)new_dir, -1) != 0;
+ if (dir_differs) {
+ do_autocmd_dirchanged((char *)new_dir, scope, kCdCauseManual, true);
+ if (vim_chdir(new_dir) != 0) {
+ emsg(_(e_failed));
+ xfree(pdir);
+ return false;
+ }
}
- xfree(tofree);
- return retval;
+ char_u **pp;
+ switch (scope) {
+ case kCdScopeTabpage:
+ pp = &curtab->tp_prevdir;
+ break;
+ case kCdScopeWindow:
+ pp = &curwin->w_prevdir;
+ break;
+ default:
+ pp = &prev_dir;
+ }
+ xfree(*pp);
+ *pp = pdir;
+
+ post_chdir(scope, dir_differs);
+
+ return true;
}
/// ":cd", ":tcd", ":lcd", ":chdir", "tchdir" and ":lchdir".
@@ -8102,6 +8188,7 @@ static void ex_put(exarg_T *eap)
eap->forceit = TRUE;
}
curwin->w_cursor.lnum = eap->line2;
+ check_cursor_col();
do_put(eap->regname, NULL, eap->forceit ? BACKWARD : FORWARD, 1,
PUT_LINE|PUT_CURSLINE);
}
@@ -8623,7 +8710,7 @@ static void ex_normal(exarg_T *eap)
return;
}
- // vgetc() expects a CSI and K_SPECIAL to have been escaped. Don't do
+ // vgetc() expects K_SPECIAL to have been escaped. Don't do
// this for the K_SPECIAL leading byte, otherwise special keys will not
// work.
{
@@ -8632,8 +8719,7 @@ static void ex_normal(exarg_T *eap)
// Count the number of characters to be escaped.
for (p = eap->arg; *p != NUL; p++) {
for (l = utfc_ptr2len(p) - 1; l > 0; l--) {
- if (*++p == K_SPECIAL // trailbyte K_SPECIAL or CSI
- ) {
+ if (*++p == K_SPECIAL) { // trailbyte K_SPECIAL
len += 2;
}
}
@@ -9542,23 +9628,34 @@ static void ex_filetype(exarg_T *eap)
}
}
-/// Set all :filetype options ON if user did not explicitly set any to OFF.
-void filetype_maybe_enable(void)
+/// Source ftplugin.vim and indent.vim to create the necessary FileType
+/// autocommands. We do this separately from filetype.vim so that these
+/// autocommands will always fire first (and thus can be overriden) while still
+/// allowing general filetype detection to be disabled in the user's init file.
+void filetype_plugin_enable(void)
{
- if (filetype_detect == kNone) {
- source_runtime(FILETYPE_FILE, true);
- filetype_detect = kTrue;
- }
if (filetype_plugin == kNone) {
- source_runtime(FTPLUGIN_FILE, true);
+ source_runtime(FTPLUGIN_FILE, DIP_ALL);
filetype_plugin = kTrue;
}
if (filetype_indent == kNone) {
- source_runtime(INDENT_FILE, true);
+ source_runtime(INDENT_FILE, DIP_ALL);
filetype_indent = kTrue;
}
}
+/// Enable filetype detection if the user did not explicitly disable it.
+void filetype_maybe_enable(void)
+{
+ if (filetype_detect == kNone) {
+ // Normally .vim files are sourced before .lua files when both are
+ // supported, but we reverse the order here because we want the Lua
+ // autocommand to be defined first so that it runs first
+ source_runtime(FILETYPE_FILE, DIP_ALL);
+ filetype_detect = kTrue;
+ }
+}
+
/// ":setfiletype [FALLBACK] {name}"
static void ex_setfiletype(exarg_T *eap)
{
@@ -9585,18 +9682,6 @@ static void ex_digraphs(exarg_T *eap)
}
}
-static void ex_set(exarg_T *eap)
-{
- int flags = 0;
-
- if (eap->cmdidx == CMD_setlocal) {
- flags = OPT_LOCAL;
- } else if (eap->cmdidx == CMD_setglobal) {
- flags = OPT_GLOBAL;
- }
- (void)do_set(eap->arg, flags);
-}
-
void set_no_hlsearch(bool flag)
{
no_hlsearch = flag;
@@ -9831,6 +9916,7 @@ Dictionary commands_array(buf_T *buf)
PUT(d, "bang", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_BANG)));
PUT(d, "bar", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_TRLBAR)));
PUT(d, "register", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_REGSTR)));
+ PUT(d, "keepscript", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_KEEPSCRIPT)));
switch (cmd->uc_argt & (EX_EXTRA | EX_NOSPC | EX_NEEDARG)) {
case 0:
diff --git a/src/nvim/ex_docmd.h b/src/nvim/ex_docmd.h
index a302d4a3c5..abf6ec347b 100644
--- a/src/nvim/ex_docmd.h
+++ b/src/nvim/ex_docmd.h
@@ -32,6 +32,26 @@ typedef struct {
tasave_T tabuf;
} save_state_T;
+typedef struct ucmd {
+ char_u *uc_name; // The command name
+ uint32_t uc_argt; // The argument type
+ char_u *uc_rep; // The command's replacement string
+ long uc_def; // The default value for a range/count
+ int uc_compl; // completion type
+ cmd_addr_T uc_addr_type; // The command's address type
+ sctx_T uc_script_ctx; // SCTX where the command was defined
+ char_u *uc_compl_arg; // completion argument if any
+ LuaRef uc_compl_luaref; // Reference to Lua completion function
+ LuaRef uc_luaref; // Reference to Lua function
+} ucmd_T;
+
+#define UC_BUFFER 1 // -buffer: local to current buffer
+
+extern garray_T ucmds;
+
+#define USER_CMD(i) (&((ucmd_T *)(ucmds.ga_data))[i])
+#define USER_CMD_GA(gap, i) (&((ucmd_T *)((gap)->ga_data))[i])
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_docmd.h.generated.h"
#endif
diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c
index b1c59a607c..851828afcf 100644
--- a/src/nvim/ex_eval.c
+++ b/src/nvim/ex_eval.c
@@ -599,7 +599,7 @@ static void catch_exception(except_T *excp)
{
excp->caught = caught_stack;
caught_stack = excp;
- set_vim_var_string(VV_EXCEPTION, (char *)excp->value, -1);
+ set_vim_var_string(VV_EXCEPTION, excp->value, -1);
if (*excp->throw_name != NUL) {
if (excp->throw_lnum != 0) {
vim_snprintf((char *)IObuff, IOSIZE, _("%s, line %" PRId64),
@@ -650,7 +650,7 @@ static void finish_exception(except_T *excp)
}
caught_stack = caught_stack->caught;
if (caught_stack != NULL) {
- set_vim_var_string(VV_EXCEPTION, (char *)caught_stack->value, -1);
+ set_vim_var_string(VV_EXCEPTION, caught_stack->value, -1);
if (*caught_stack->throw_name != NUL) {
if (caught_stack->throw_lnum != 0) {
vim_snprintf((char *)IObuff, IOSIZE,
@@ -733,7 +733,7 @@ static void report_pending(int action, int pending, void *value)
vim_snprintf((char *)IObuff, IOSIZE,
mesg, _("Exception"));
mesg = (char *)concat_str(IObuff, (char_u *)": %s");
- s = (char *)((except_T *)value)->value;
+ s = ((except_T *)value)->value;
} else if ((pending & CSTP_ERROR) && (pending & CSTP_INTERRUPT)) {
s = _("Error and interrupt");
} else if (pending & CSTP_ERROR) {
@@ -1620,7 +1620,7 @@ void ex_endtry(exarg_T *eap)
// the finally clause. The latter case need not be tested since then
// anything pending has already been discarded.
bool skip = did_emsg || got_int || current_exception
- || !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE);
+ || !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE);
if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY)) {
eap->errmsg = get_end_emsg(cstack);
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index ba2238ace2..30287cd6f2 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -311,7 +311,7 @@ static char_u *get_healthcheck_names(expand_T *xp, int idx)
healthchecks.last_gen = last_prompt_id;
}
return idx <
- (int)healthchecks.names.ga_len ? ((char_u **)(healthchecks.names.ga_data))[idx] : NULL;
+ healthchecks.names.ga_len ? ((char_u **)(healthchecks.names.ga_data))[idx] : NULL;
}
/// Transform healthcheck file path into it's name.
@@ -772,7 +772,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
ccline.cmdindent = (s->firstc > 0 ? s->indent : 0);
// alloc initial ccline.cmdbuff
- alloc_cmdbuff(exmode_active ? 250 : s->indent + 1);
+ alloc_cmdbuff(indent + 50);
ccline.cmdlen = ccline.cmdpos = 0;
ccline.cmdbuff[0] = NUL;
@@ -1024,11 +1024,13 @@ static int command_line_execute(VimState *state, int key)
CommandLineState *s = (CommandLineState *)state;
s->c = key;
- if (s->c == K_EVENT || s->c == K_COMMAND) {
+ if (s->c == K_EVENT || s->c == K_COMMAND || s->c == K_LUA) {
if (s->c == K_EVENT) {
state_handle_k_event();
- } else {
+ } else if (s->c == K_COMMAND) {
do_cmdline(NULL, getcmdkeycmd, NULL, DOCMD_NOWAIT);
+ } else {
+ map_execute_lua();
}
if (!cmdline_was_last_drawn) {
@@ -4983,6 +4985,9 @@ static int ExpandFromContext(expand_T *xp, char_u *pat, int *num_file, char_u **
if (xp->xp_context == EXPAND_USER_LIST) {
return ExpandUserList(xp, num_file, file);
}
+ if (xp->xp_context == EXPAND_USER_LUA) {
+ return ExpandUserLua(xp, num_file, file);
+ }
if (xp->xp_context == EXPAND_PACKADD) {
return ExpandPackAddDir(pat, num_file, file);
}
@@ -5044,8 +5049,8 @@ static int ExpandFromContext(expand_T *xp, char_u *pat, int *num_file, char_u **
{ EXPAND_SYNTAX, get_syntax_name, true, true },
{ EXPAND_SYNTIME, get_syntime_arg, true, true },
{ EXPAND_HIGHLIGHT, (ExpandFunc)get_highlight_name, true, true },
- { EXPAND_EVENTS, get_event_name, true, true },
- { EXPAND_AUGROUP, get_augroup_name, true, true },
+ { EXPAND_EVENTS, expand_get_event_name, true, true },
+ { EXPAND_AUGROUP, expand_get_augroup_name, true, true },
{ EXPAND_CSCOPE, get_cscope_name, true, true },
{ EXPAND_SIGN, get_sign_name, true, true },
{ EXPAND_PROFILE, get_profile_name, true, true },
@@ -5411,6 +5416,35 @@ static int ExpandUserList(expand_T *xp, int *num_file, char_u ***file)
return OK;
}
+static int ExpandUserLua(expand_T *xp, int *num_file, char_u ***file)
+{
+ typval_T rettv;
+ nlua_call_user_expand_func(xp, &rettv);
+ if (rettv.v_type != VAR_LIST) {
+ tv_clear(&rettv);
+ return FAIL;
+ }
+
+ list_T *const retlist = rettv.vval.v_list;
+
+ garray_T ga;
+ ga_init(&ga, (int)sizeof(char *), 3);
+ // Loop over the items in the list.
+ TV_LIST_ITER_CONST(retlist, li, {
+ if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING
+ || TV_LIST_ITEM_TV(li)->vval.v_string == NULL) {
+ continue; // Skip non-string items and empty strings.
+ }
+
+ GA_APPEND(char *, &ga, xstrdup((const char *)TV_LIST_ITEM_TV(li)->vval.v_string));
+ });
+ tv_list_unref(retlist);
+
+ *file = ga.ga_data;
+ *num_file = ga.ga_len;
+ return OK;
+}
+
/// Expand color scheme, compiler or filetype names.
/// Search from 'runtimepath':
/// 'runtimepath'/{dirnames}/{pat}.vim
@@ -6529,7 +6563,7 @@ static int open_cmdwin(void)
set_bufref(&bufref, curbuf);
win_goto(old_curwin);
if (win_valid(wp) && wp != curwin) {
- win_close(wp, true);
+ win_close(wp, true, false);
}
// win_close() may have already wiped the buffer when 'bh' is
@@ -6553,6 +6587,13 @@ static int open_cmdwin(void)
return cmdwin_result;
}
+/// @return true if in the cmdwin, not editing the command line.
+bool is_in_cmdwin(void)
+ FUNC_ATTR_PURE
+{
+ return cmdwin_type != 0 && get_cmdline_type() == NUL;
+}
+
/// Get script string
///
/// Used for commands which accept either `:command script` or
diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c
index a37cad9f2d..ca07174543 100644
--- a/src/nvim/ex_session.c
+++ b/src/nvim/ex_session.c
@@ -338,14 +338,26 @@ static int put_view(FILE *fd, win_T *wp, int add_edit, unsigned *flagp, int curr
// Edit the file. Skip this when ":next" already did it.
if (add_edit && (!did_next || wp->w_arg_idx_invalid)) {
- char *fname_esc =
- ses_escape_fname(ses_get_fname(wp->w_buffer, flagp), flagp);
- //
- // Load the file.
- //
- if (wp->w_buffer->b_ffname != NULL
- && (!bt_nofile(wp->w_buffer)
- || wp->w_buffer->terminal)) {
+ char *fname_esc = ses_escape_fname(ses_get_fname(wp->w_buffer, flagp), flagp);
+ if (bt_help(wp->w_buffer)) {
+ char *curtag = "";
+
+ // A help buffer needs some options to be set.
+ // First, create a new empty buffer with "buftype=help".
+ // Then ":help" will re-use both the buffer and the window and set
+ // the options, even when "options" is not in 'sessionoptions'.
+ if (0 < wp->w_tagstackidx && wp->w_tagstackidx <= wp->w_tagstacklen) {
+ curtag = (char *)wp->w_tagstack[wp->w_tagstackidx - 1].tagname;
+ }
+
+ if (put_line(fd, "enew | setl bt=help") == FAIL
+ || fprintf(fd, "help %s", curtag) < 0 || put_eol(fd) == FAIL) {
+ return FAIL;
+ }
+ } else if (wp->w_buffer->b_ffname != NULL
+ && (!bt_nofile(wp->w_buffer) || wp->w_buffer->terminal)) {
+ // Load the file.
+
// Editing a file in this buffer: use ":edit file".
// This may have side effects! (e.g., compressed or network file).
//
@@ -579,6 +591,24 @@ static int makeopens(FILE *fd, char_u *dirnow)
return FAIL;
}
+ // Put all buffers into the buffer list.
+ // Do it very early to preserve buffer order after loading session (which
+ // can be disrupted by prior `edit` or `tabedit` calls).
+ FOR_ALL_BUFFERS(buf) {
+ if (!(only_save_windows && buf->b_nwindows == 0)
+ && !(buf->b_help && !(ssop_flags & SSOP_HELP))
+ && buf->b_fname != NULL
+ && buf->b_p_bl) {
+ if (fprintf(fd, "badd +%" PRId64 " ",
+ buf->b_wininfo == NULL
+ ? (int64_t)1L
+ : (int64_t)buf->b_wininfo->wi_fpos.lnum) < 0
+ || ses_fname(fd, buf, &ssop_flags, true) == FAIL) {
+ return FAIL;
+ }
+ }
+ }
+
// the global argument list
if (ses_arglist(fd, "argglobal", &global_alist.al_ga,
!(ssop_flags & SSOP_CURDIR), &ssop_flags) == FAIL) {
@@ -614,7 +644,10 @@ static int makeopens(FILE *fd, char_u *dirnow)
// Similar to ses_win_rec() below, populate the tab pages first so
// later local options won't be copied to the new tabs.
FOR_ALL_TABS(tp) {
- if (tp->tp_next != NULL && put_line(fd, "tabnew") == FAIL) {
+ // Use `bufhidden=wipe` to remove empty "placeholder" buffers once
+ // they are not needed. This prevents creating extra buffers (see
+ // cause of Vim patch 8.1.0829)
+ if (tp->tp_next != NULL && put_line(fd, "tabnew +setlocal\\ bufhidden=wipe") == FAIL) {
return FAIL;
}
}
@@ -792,25 +825,6 @@ static int makeopens(FILE *fd, char_u *dirnow)
return FAIL;
}
- // Now put the remaining buffers into the buffer list.
- // This is near the end, so that when 'hidden' is set we don't create extra
- // buffers. If the buffer was already created with another command the
- // ":badd" will have no effect.
- FOR_ALL_BUFFERS(buf) {
- if (!(only_save_windows && buf->b_nwindows == 0)
- && !(buf->b_help && !(ssop_flags & SSOP_HELP))
- && buf->b_fname != NULL
- && buf->b_p_bl) {
- if (fprintf(fd, "badd +%" PRId64 " ",
- buf->b_wininfo == NULL
- ? (int64_t)1L
- : (int64_t)buf->b_wininfo->wi_fpos.lnum) < 0
- || ses_fname(fd, buf, &ssop_flags, true) == FAIL) {
- return FAIL;
- }
- }
- }
-
//
// Wipe out an empty unnamed buffer we started in.
//
diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c
index c4d8f75a21..48e57e20e1 100644
--- a/src/nvim/extmark.c
+++ b/src/nvim/extmark.c
@@ -48,28 +48,29 @@
# include "extmark.c.generated.h"
#endif
-static ExtmarkNs *buf_ns_ref(buf_T *buf, uint64_t ns_id, bool put)
+static uint32_t *buf_ns_ref(buf_T *buf, uint32_t ns_id, bool put)
{
- return map_ref(uint64_t, ExtmarkNs)(buf->b_extmark_ns, ns_id, put);
+ return map_ref(uint32_t, uint32_t)(buf->b_extmark_ns, ns_id, put);
}
/// Create or update an extmark
///
/// must not be used during iteration!
-/// @returns the internal mark id
-uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t *idp, int row, colnr_T col, int end_row,
- colnr_T end_col, Decoration *decor, bool right_gravity, bool end_right_gravity,
- ExtmarkOp op)
+void extmark_set(buf_T *buf, uint32_t ns_id, uint32_t *idp, int row, colnr_T col, int end_row,
+ colnr_T end_col, Decoration *decor, bool right_gravity, bool end_right_gravity,
+ ExtmarkOp op)
{
- ExtmarkNs *ns = buf_ns_ref(buf, ns_id, true);
- assert(ns != NULL);
- mtpos_t old_pos;
- uint64_t mark = 0;
- uint64_t id = idp ? *idp : 0;
+ uint32_t *ns = buf_ns_ref(buf, ns_id, true);
+ uint32_t id = idp ? *idp : 0;
+ bool decor_full = false;
uint8_t decor_level = kDecorLevelNone; // no decor
if (decor) {
+ if (kv_size(decor->virt_text) || kv_size(decor->virt_lines)) {
+ decor_full = true;
+ decor = xmemdup(decor, sizeof *decor);
+ }
decor_level = kDecorLevelVisible; // decor affects redraw
if (kv_size(decor->virt_lines)) {
decor_level = kDecorLevelVirtLine; // decor affects horizontal size
@@ -77,50 +78,64 @@ uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t *idp, int row, colnr_T
}
if (id == 0) {
- id = ns->free_id++;
+ id = ++*ns;
} else {
- uint64_t old_mark = map_get(uint64_t, uint64_t)(ns->map, id);
- if (old_mark) {
- if (old_mark & MARKTREE_PAIRED_FLAG || end_row > -1) {
+ MarkTreeIter itr[1] = { 0 };
+ mtkey_t old_mark = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, itr);
+ if (old_mark.id) {
+ if (mt_paired(old_mark) || end_row > -1) {
extmark_del(buf, ns_id, id);
} else {
- MarkTreeIter itr[1] = { 0 };
- old_pos = marktree_lookup(buf->b_marktree, old_mark, itr);
+ // TODO(bfredl): we need to do more if "revising" a decoration mark.
assert(itr->node);
- if (old_pos.row == row && old_pos.col == col) {
- ExtmarkItem it = map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index,
- old_mark);
- if (it.decor) {
- decor_remove(buf, row, row, it.decor);
+ if (old_mark.pos.row == row && old_mark.pos.col == col) {
+ if (marktree_decor_level(old_mark) > kDecorLevelNone) {
+ decor_remove(buf, row, row, old_mark.decor_full);
+ old_mark.decor_full = NULL;
+ }
+ old_mark.flags = 0;
+ if (decor_full) {
+ old_mark.decor_full = decor;
+ } else if (decor) {
+ old_mark.hl_id = decor->hl_id;
+ // Workaround: the gcc compiler of functionaltest-lua build
+ // apparently incapable of handling basic integer constants.
+ // This can be underanged as soon as we bump minimal gcc version.
+ old_mark.flags = (uint16_t)(old_mark.flags
+ | (decor->hl_eol ? (uint16_t)MT_FLAG_HL_EOL : (uint16_t)0));
+ old_mark.priority = decor->priority;
}
- mark = marktree_revise(buf->b_marktree, itr, decor_level);
+ marktree_revise(buf->b_marktree, itr, decor_level, old_mark);
goto revised;
}
marktree_del_itr(buf->b_marktree, itr, false);
}
} else {
- ns->free_id = MAX(ns->free_id, id+1);
+ *ns = MAX(*ns, id);
}
}
- if (end_row > -1) {
- mark = marktree_put_pair(buf->b_marktree,
- row, col, right_gravity,
- end_row, end_col, end_right_gravity, decor_level);
- } else {
- mark = marktree_put(buf->b_marktree, row, col, right_gravity, decor_level);
+ mtkey_t mark = { { row, col }, ns_id, id, 0,
+ mt_flags(right_gravity, decor_level), 0, NULL };
+ if (decor_full) {
+ mark.decor_full = decor;
+ } else if (decor) {
+ mark.hl_id = decor->hl_id;
+ // workaround: see above
+ mark.flags = (uint16_t)(mark.flags | (decor->hl_eol ? (uint16_t)MT_FLAG_HL_EOL : (uint16_t)0));
+ mark.priority = decor->priority;
}
-revised:
- map_put(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark,
- (ExtmarkItem){ ns_id, id, decor });
- map_put(uint64_t, uint64_t)(ns->map, id, mark);
+ marktree_put(buf->b_marktree, mark, end_row, end_col, end_right_gravity);
+revised:
if (op != kExtmarkNoUndo) {
// TODO(bfredl): this doesn't cover all the cases and probably shouldn't
// be done "prematurely". Any movement in undo history might necessitate
- // adding new marks to old undo headers.
- u_extmark_set(buf, mark, row, col);
+ // adding new marks to old undo headers. add a test case for this (doesn't
+ // fail extmark_spec.lua, and it should)
+ uint64_t mark_id = mt_lookup_id(ns_id, id, false);
+ u_extmark_set(buf, mark_id, row, col);
}
if (decor) {
@@ -133,18 +148,17 @@ revised:
if (idp) {
*idp = id;
}
- return mark;
}
static bool extmark_setraw(buf_T *buf, uint64_t mark, int row, colnr_T col)
{
MarkTreeIter itr[1] = { 0 };
- mtpos_t pos = marktree_lookup(buf->b_marktree, mark, itr);
- if (pos.row == -1) {
+ mtkey_t key = marktree_lookup(buf->b_marktree, mark, itr);
+ if (key.pos.row == -1) {
return false;
}
- if (pos.row == row && pos.col == col) {
+ if (key.pos.row == row && key.pos.col == col) {
return true;
}
@@ -154,45 +168,35 @@ static bool extmark_setraw(buf_T *buf, uint64_t mark, int row, colnr_T col)
// Remove an extmark
// Returns 0 on missing id
-bool extmark_del(buf_T *buf, uint64_t ns_id, uint64_t id)
+bool extmark_del(buf_T *buf, uint32_t ns_id, uint32_t id)
{
- ExtmarkNs *ns = buf_ns_ref(buf, ns_id, false);
- if (!ns) {
- return false;
- }
-
- uint64_t mark = map_get(uint64_t, uint64_t)(ns->map, id);
- if (!mark) {
+ MarkTreeIter itr[1] = { 0 };
+ mtkey_t key = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, itr);
+ if (!key.id) {
return false;
}
-
- MarkTreeIter itr[1] = { 0 };
- mtpos_t pos = marktree_lookup(buf->b_marktree, mark, itr);
- assert(pos.row >= 0);
+ assert(key.pos.row >= 0);
marktree_del_itr(buf->b_marktree, itr, false);
- ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark);
- mtpos_t pos2 = pos;
- if (mark & MARKTREE_PAIRED_FLAG) {
- pos2 = marktree_lookup(buf->b_marktree, mark|MARKTREE_END_FLAG, itr);
- assert(pos2.row >= 0);
+ mtkey_t key2 = key;
+
+ if (mt_paired(key)) {
+ key2 = marktree_lookup_ns(buf->b_marktree, ns_id, id, true, itr);
+ assert(key2.pos.row >= 0);
marktree_del_itr(buf->b_marktree, itr, false);
}
- if (item.decor) {
- decor_remove(buf, pos.row, pos2.row, item.decor);
+ if (marktree_decor_level(key) > kDecorLevelNone) {
+ decor_remove(buf, key.pos.row, key2.pos.row, key.decor_full);
}
- map_del(uint64_t, uint64_t)(ns->map, id);
- map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark);
-
// TODO(bfredl): delete it from current undo header, opportunistically?
return true;
}
// Free extmarks in a ns between lines
// if ns = 0, it means clear all namespaces
-bool extmark_clear(buf_T *buf, uint64_t ns_id, int l_row, colnr_T l_col, int u_row, colnr_T u_col)
+bool extmark_clear(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_col, int u_row, colnr_T u_col)
{
if (!map_size(buf->b_extmark_ns)) {
return false;
@@ -201,68 +205,58 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id, int l_row, colnr_T l_col, int u_r
bool marks_cleared = false;
bool all_ns = (ns_id == 0);
- ExtmarkNs *ns = NULL;
+ uint32_t *ns = NULL;
if (!all_ns) {
ns = buf_ns_ref(buf, ns_id, false);
if (!ns) {
// nothing to do
return false;
}
-
- // TODO(bfredl): if map_size(ns->map) << buf->b_marktree.n_nodes
- // it could be faster to iterate over the map instead
}
// the value is either zero or the lnum (row+1) if highlight was present.
static Map(uint64_t, ssize_t) delete_set = MAP_INIT;
- typedef struct { Decoration *decor; int row1; } DecorItem;
+ typedef struct { int row1; } DecorItem;
static kvec_t(DecorItem) decors;
MarkTreeIter itr[1] = { 0 };
marktree_itr_get(buf->b_marktree, l_row, l_col, itr);
while (true) {
- mtmark_t mark = marktree_itr_current(itr);
- if (mark.row < 0
- || mark.row > u_row
- || (mark.row == u_row && mark.col > u_col)) {
+ mtkey_t mark = marktree_itr_current(itr);
+ if (mark.pos.row < 0
+ || mark.pos.row > u_row
+ || (mark.pos.row == u_row && mark.pos.col > u_col)) {
break;
}
- ssize_t *del_status = map_ref(uint64_t, ssize_t)(&delete_set, mark.id,
+ ssize_t *del_status = map_ref(uint64_t, ssize_t)(&delete_set, mt_lookup_key(mark),
false);
if (del_status) {
marktree_del_itr(buf->b_marktree, itr, false);
if (*del_status >= 0) { // we had a decor_id
DecorItem it = kv_A(decors, *del_status);
- decor_remove(buf, it.row1, mark.row, it.decor);
+ decor_remove(buf, it.row1, mark.pos.row, mark.decor_full);
}
- map_del(uint64_t, ssize_t)(&delete_set, mark.id);
+ map_del(uint64_t, ssize_t)(&delete_set, mt_lookup_key(mark));
continue;
}
- uint64_t start_id = mark.id & ~MARKTREE_END_FLAG;
- ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index,
- start_id);
-
- assert(item.ns_id > 0 && item.mark_id > 0);
- if (item.mark_id > 0 && (item.ns_id == ns_id || all_ns)) {
+ assert(mark.ns > 0 && mark.id > 0);
+ if (mark.ns == ns_id || all_ns) {
marks_cleared = true;
- if (mark.id & MARKTREE_PAIRED_FLAG) {
- uint64_t other = mark.id ^ MARKTREE_END_FLAG;
+ if (mt_paired(mark)) {
+ uint64_t other = mt_lookup_id(mark.ns, mark.id, !mt_end(mark));
ssize_t decor_id = -1;
- if (item.decor) {
+ if (marktree_decor_level(mark) > kDecorLevelNone) {
// Save the decoration and the first pos. Clear the decoration
// later when we know the full range.
decor_id = (ssize_t)kv_size(decors);
kv_push(decors,
- ((DecorItem) { .decor = item.decor, .row1 = mark.row }));
+ ((DecorItem) { .row1 = mark.pos.row }));
}
map_put(uint64_t, ssize_t)(&delete_set, other, decor_id);
- } else if (item.decor) {
- decor_remove(buf, mark.row, mark.row, item.decor);
+ } else if (mark.decor_full) {
+ decor_remove(buf, mark.pos.row, mark.pos.row, mark.decor_full);
}
- ExtmarkNs *my_ns = all_ns ? buf_ns_ref(buf, item.ns_id, false) : ns;
- map_del(uint64_t, uint64_t)(my_ns->map, item.mark_id);
- map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, start_id);
marktree_del_itr(buf->b_marktree, itr, false);
} else {
marktree_itr_next(buf->b_marktree, itr);
@@ -271,12 +265,12 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id, int l_row, colnr_T l_col, int u_r
uint64_t id;
ssize_t decor_id;
map_foreach(&delete_set, id, decor_id, {
- mtpos_t pos = marktree_lookup(buf->b_marktree, id, itr);
+ mtkey_t mark = marktree_lookup(buf->b_marktree, id, itr);
assert(itr->node);
marktree_del_itr(buf->b_marktree, itr, false);
if (decor_id >= 0) {
DecorItem it = kv_A(decors, decor_id);
- decor_remove(buf, it.row1, pos.row, it.decor);
+ decor_remove(buf, it.row1, mark.pos.row, mark.decor_full);
}
});
map_clear(uint64_t, ssize_t)(&delete_set);
@@ -290,7 +284,7 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id, int l_row, colnr_T l_col, int u_r
// will be searched to the start, or end
// dir can be set to control the order of the array
// amount = amount of marks to find or -1 for all
-ExtmarkInfoArray extmark_get(buf_T *buf, uint64_t ns_id, int l_row, colnr_T l_col, int u_row,
+ExtmarkInfoArray extmark_get(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_col, int u_row,
colnr_T u_col, int64_t amount, bool reverse)
{
ExtmarkInfoArray array = KV_INITIAL_VALUE;
@@ -300,30 +294,26 @@ ExtmarkInfoArray extmark_get(buf_T *buf, uint64_t ns_id, int l_row, colnr_T l_co
itr, reverse, false, NULL);
int order = reverse ? -1 : 1;
while ((int64_t)kv_size(array) < amount) {
- mtmark_t mark = marktree_itr_current(itr);
- mtpos_t endpos = { -1, -1 };
- if (mark.row < 0
- || (mark.row - u_row) * order > 0
- || (mark.row == u_row && (mark.col - u_col) * order > 0)) {
+ mtkey_t mark = marktree_itr_current(itr);
+ if (mark.pos.row < 0
+ || (mark.pos.row - u_row) * order > 0
+ || (mark.pos.row == u_row && (mark.pos.col - u_col) * order > 0)) {
break;
}
- if (mark.id & MARKTREE_END_FLAG) {
+ if (mt_end(mark)) {
goto next_mark;
- } else if (mark.id & MARKTREE_PAIRED_FLAG) {
- endpos = marktree_lookup(buf->b_marktree, mark.id | MARKTREE_END_FLAG,
- NULL);
}
-
- ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index,
- mark.id);
- if (item.ns_id == ns_id) {
- kv_push(array, ((ExtmarkInfo) { .ns_id = item.ns_id,
- .mark_id = item.mark_id,
- .row = mark.row, .col = mark.col,
- .end_row = endpos.row,
- .end_col = endpos.col,
- .decor = item.decor }));
+ if (mark.ns == ns_id) {
+ mtkey_t end = marktree_get_alt(buf->b_marktree, mark, NULL);
+ kv_push(array, ((ExtmarkInfo) { .ns_id = mark.ns,
+ .mark_id = mark.id,
+ .row = mark.pos.row, .col = mark.pos.col,
+ .end_row = end.pos.row,
+ .end_col = end.pos.col,
+ .right_gravity = mt_right(mark),
+ .end_right_gravity = mt_right(end),
+ .decor = get_decor(mark) }));
}
next_mark:
if (reverse) {
@@ -336,36 +326,25 @@ next_mark:
}
// Lookup an extmark by id
-ExtmarkInfo extmark_from_id(buf_T *buf, uint64_t ns_id, uint64_t id)
+ExtmarkInfo extmark_from_id(buf_T *buf, uint32_t ns_id, uint32_t id)
{
- ExtmarkNs *ns = buf_ns_ref(buf, ns_id, false);
- ExtmarkInfo ret = { 0, 0, -1, -1, -1, -1, NULL };
- if (!ns) {
+ ExtmarkInfo ret = { 0, 0, -1, -1, -1, -1, false, false, DECORATION_INIT };
+ mtkey_t mark = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, NULL);
+ if (!mark.id) {
return ret;
}
-
- uint64_t mark = map_get(uint64_t, uint64_t)(ns->map, id);
- if (!mark) {
- return ret;
- }
-
- mtpos_t pos = marktree_lookup(buf->b_marktree, mark, NULL);
- mtpos_t endpos = { -1, -1 };
- if (mark & MARKTREE_PAIRED_FLAG) {
- endpos = marktree_lookup(buf->b_marktree, mark | MARKTREE_END_FLAG, NULL);
- }
- assert(pos.row >= 0);
-
- ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index,
- mark);
+ assert(mark.pos.row >= 0);
+ mtkey_t end = marktree_get_alt(buf->b_marktree, mark, NULL);
ret.ns_id = ns_id;
ret.mark_id = id;
- ret.row = pos.row;
- ret.col = pos.col;
- ret.end_row = endpos.row;
- ret.end_col = endpos.col;
- ret.decor = item.decor;
+ ret.row = mark.pos.row;
+ ret.col = mark.pos.col;
+ ret.end_row = end.pos.row;
+ ret.end_col = end.pos.col;
+ ret.right_gravity = mt_right(mark);
+ ret.end_right_gravity = mt_right(end);
+ ret.decor = get_decor(mark);
return ret;
}
@@ -378,25 +357,26 @@ void extmark_free_all(buf_T *buf)
return;
}
- uint64_t id;
- ExtmarkNs ns;
- ExtmarkItem item;
+ MarkTreeIter itr[1] = { 0 };
+ marktree_itr_get(buf->b_marktree, 0, 0, itr);
+ while (true) {
+ mtkey_t mark = marktree_itr_current(itr);
+ if (mark.pos.row < 0) {
+ break;
+ }
- marktree_clear(buf->b_marktree);
+ // don't free mark.decor_full twice for a paired mark.
+ if (!(mt_paired(mark) && mt_end(mark))) {
+ decor_free(mark.decor_full);
+ }
- map_foreach(buf->b_extmark_ns, id, ns, {
- (void)id;
- map_destroy(uint64_t, uint64_t)(ns.map);
- });
- map_destroy(uint64_t, ExtmarkNs)(buf->b_extmark_ns);
- map_init(uint64_t, ExtmarkNs, buf->b_extmark_ns);
+ marktree_itr_next(buf->b_marktree, itr);
+ }
- map_foreach(buf->b_extmark_index, id, item, {
- (void)id;
- decor_free(item.decor);
- });
- map_destroy(uint64_t, ExtmarkItem)(buf->b_extmark_index);
- map_init(uint64_t, ExtmarkItem, buf->b_extmark_index);
+ marktree_clear(buf->b_marktree);
+
+ map_destroy(uint32_t, uint32_t)(buf->b_extmark_ns);
+ map_init(uint32_t, uint32_t, buf->b_extmark_ns);
}
@@ -437,16 +417,16 @@ void u_extmark_copy(buf_T *buf, int l_row, colnr_T l_col, int u_row, colnr_T u_c
MarkTreeIter itr[1] = { 0 };
marktree_itr_get(buf->b_marktree, l_row, l_col, itr);
while (true) {
- mtmark_t mark = marktree_itr_current(itr);
- if (mark.row < 0
- || mark.row > u_row
- || (mark.row == u_row && mark.col > u_col)) {
+ mtkey_t mark = marktree_itr_current(itr);
+ if (mark.pos.row < 0
+ || mark.pos.row > u_row
+ || (mark.pos.row == u_row && mark.pos.col > u_col)) {
break;
}
ExtmarkSavePos pos;
- pos.mark = mark.id;
- pos.old_row = mark.row;
- pos.old_col = mark.col;
+ pos.mark = mt_lookup_key(mark);
+ pos.old_row = mark.pos.row;
+ pos.old_col = mark.pos.col;
pos.row = -1;
pos.col = -1;
diff --git a/src/nvim/extmark.h b/src/nvim/extmark.h
index c70db9f7aa..af9526cd43 100644
--- a/src/nvim/extmark.h
+++ b/src/nvim/extmark.h
@@ -3,6 +3,7 @@
#include "nvim/buffer_defs.h"
#include "nvim/extmark_defs.h"
+#include "nvim/decoration.h"
#include "nvim/marktree.h"
#include "nvim/pos.h"
@@ -15,7 +16,9 @@ typedef struct {
colnr_T col;
int end_row;
colnr_T end_col;
- Decoration *decor;
+ bool right_gravity;
+ bool end_right_gravity;
+ Decoration decor; // TODO(bfredl): CHONKY
} ExtmarkInfo;
typedef kvec_t(ExtmarkInfo) ExtmarkInfoArray;
diff --git a/src/nvim/extmark_defs.h b/src/nvim/extmark_defs.h
index bbe8504ebf..5570b5c71e 100644
--- a/src/nvim/extmark_defs.h
+++ b/src/nvim/extmark_defs.h
@@ -4,23 +4,11 @@
#include "nvim/lib/kvec.h"
#include "nvim/types.h"
-typedef struct Decoration Decoration;
-
typedef struct {
char *text;
int hl_id;
} VirtTextChunk;
-
-typedef struct {
- uint64_t ns_id;
- uint64_t mark_id;
- // TODO(bfredl): a lot of small allocations. Should probably use
- // kvec_t(Decoration) as an arena. Alternatively, store ns_id/mark_id
- // _inline_ in MarkTree and use the map only for decorations.
- Decoration *decor;
-} ExtmarkItem;
-
typedef struct undo_object ExtmarkUndoObject;
typedef kvec_t(ExtmarkUndoObject) extmark_undo_vec_t;
diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c
index 1884dd49c5..d0f7a91d6c 100644
--- a/src/nvim/file_search.c
+++ b/src/nvim/file_search.c
@@ -483,8 +483,14 @@ void *vim_findfile_init(char_u *path, char_u *filename, char_u *stopdirs, int le
int len = 0;
if (p > search_ctx->ffsc_fix_path) {
+ // do not add '..' to the path and start upwards searching
len = (int)(p - search_ctx->ffsc_fix_path) - 1;
- STRNCAT(ff_expand_buffer, search_ctx->ffsc_fix_path, len);
+ if ((len >= 2 && STRNCMP(search_ctx->ffsc_fix_path, "..", 2) == 0)
+ && (len == 2 || search_ctx->ffsc_fix_path[2] == PATHSEP)) {
+ xfree(buf);
+ goto error_return;
+ }
+ STRLCAT(ff_expand_buffer, search_ctx->ffsc_fix_path, eb_len + (size_t)len + 1);
add_pathsep((char *)ff_expand_buffer);
} else {
len = (int)STRLEN(search_ctx->ffsc_fix_path);
@@ -607,7 +613,7 @@ char_u *vim_findfile(void *search_ctx_arg)
for (;;) {
// downward search loop
for (;;) {
- // check if user user wants to stop the search
+ // check if user wants to stop the search
os_breakcheck();
if (got_int) {
break;
@@ -1139,7 +1145,7 @@ static int ff_check_visited(ff_visited_T **visited_list, char_u *fname, char_u *
bool url = false;
FileID file_id;
- // For an URL we only compare the name, otherwise we compare the
+ // For a URL we only compare the name, otherwise we compare the
// device/inode.
if (path_with_url((char *)fname)) {
STRLCPY(ff_expand_buffer, fname, MAXPATHL);
@@ -1433,7 +1439,11 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first
rel_fname = NULL;
}
- if (first == TRUE) {
+ if (first == true) {
+ if (len == 0) {
+ return NULL;
+ }
+
// copy file name into NameBuff, expanding environment variables
save_char = ptr[len];
ptr[len] = NUL;
@@ -1590,11 +1600,13 @@ theend:
return file_name;
}
-void do_autocmd_dirchanged(char *new_dir, CdScope scope, CdCause cause)
+void do_autocmd_dirchanged(char *new_dir, CdScope scope, CdCause cause, bool pre)
{
static bool recursive = false;
- if (recursive || !has_event(EVENT_DIRCHANGED)) {
+ event_T event = pre ? EVENT_DIRCHANGEDPRE : EVENT_DIRCHANGED;
+
+ if (recursive || !has_event(event)) {
// No autocommand was defined or we changed
// the directory from this autocommand.
return;
@@ -1628,8 +1640,12 @@ void do_autocmd_dirchanged(char *new_dir, CdScope scope, CdCause cause)
new_dir = new_dir_buf;
#endif
+ if (pre) {
+ tv_dict_add_str(dict, S_LEN("directory"), new_dir);
+ } else {
+ tv_dict_add_str(dict, S_LEN("cwd"), new_dir);
+ }
tv_dict_add_str(dict, S_LEN("scope"), buf); // -V614
- tv_dict_add_str(dict, S_LEN("cwd"), new_dir);
tv_dict_add_bool(dict, S_LEN("changed_window"), cause == kCdCauseWindow);
tv_dict_set_keys_readonly(dict);
@@ -1645,8 +1661,7 @@ void do_autocmd_dirchanged(char *new_dir, CdScope scope, CdCause cause)
abort();
}
- apply_autocmds(EVENT_DIRCHANGED, (char_u *)buf, (char_u *)new_dir, false,
- curbuf);
+ apply_autocmds(event, (char_u *)buf, (char_u *)new_dir, false, curbuf);
restore_v_event(dict, &save_v_event);
@@ -1672,12 +1687,16 @@ int vim_chdirfile(char_u *fname, CdCause cause)
return OK;
}
+ if (cause != kCdCauseOther) {
+ do_autocmd_dirchanged(dir, kCdScopeWindow, cause, true);
+ }
+
if (os_chdir(dir) != 0) {
return FAIL;
}
if (cause != kCdCauseOther) {
- do_autocmd_dirchanged(dir, kCdScopeWindow, cause);
+ do_autocmd_dirchanged(dir, kCdScopeWindow, cause, false);
}
return OK;
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index f8cf341836..965aa8749d 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -242,6 +242,7 @@ int readfile(char_u *fname, char_u *sfname, linenr_T from, linenr_T lines_to_ski
bool notconverted = false; // true if conversion wanted but it wasn't possible
char_u conv_rest[CONV_RESTLEN];
int conv_restlen = 0; // nr of bytes in conv_rest[]
+ pos_T orig_start;
buf_T *old_curbuf;
char_u *old_b_ffname;
char_u *old_b_fname;
@@ -298,14 +299,10 @@ int readfile(char_u *fname, char_u *sfname, linenr_T from, linenr_T lines_to_ski
fname = sfname;
#endif
- /*
- * The BufReadCmd and FileReadCmd events intercept the reading process by
- * executing the associated commands instead.
- */
+ // The BufReadCmd and FileReadCmd events intercept the reading process by
+ // executing the associated commands instead.
if (!filtering && !read_stdin && !read_buffer) {
- pos_T pos;
-
- pos = curbuf->b_op_start;
+ orig_start = curbuf->b_op_start;
// Set '[ mark to the line above where the lines go (line 1 if zero).
curbuf->b_op_start.lnum = ((from == 0) ? 1 : from);
@@ -335,7 +332,7 @@ int readfile(char_u *fname, char_u *sfname, linenr_T from, linenr_T lines_to_ski
return aborting() ? FAIL : OK;
}
- curbuf->b_op_start = pos;
+ curbuf->b_op_start = orig_start;
}
if ((shortmess(SHM_OVER) || curbuf->b_help) && p_verbose == 0) {
@@ -408,6 +405,7 @@ int readfile(char_u *fname, char_u *sfname, linenr_T from, linenr_T lines_to_ski
if (os_fileinfo((char *)fname, &file_info)) {
buf_store_file_info(curbuf, &file_info);
curbuf->b_mtime_read = curbuf->b_mtime;
+ curbuf->b_mtime_read_ns = curbuf->b_mtime_ns;
#ifdef UNIX
/*
* Use the protection bits of the original file for the swap file.
@@ -424,7 +422,9 @@ int readfile(char_u *fname, char_u *sfname, linenr_T from, linenr_T lines_to_ski
#endif
} else {
curbuf->b_mtime = 0;
+ curbuf->b_mtime_ns = 0;
curbuf->b_mtime_read = 0;
+ curbuf->b_mtime_read_ns = 0;
curbuf->b_orig_size = 0;
curbuf->b_orig_mode = 0;
}
@@ -576,9 +576,8 @@ int readfile(char_u *fname, char_u *sfname, linenr_T from, linenr_T lines_to_ski
++no_wait_return; // don't wait for return yet
- /*
- * Set '[ mark to the line above where the lines go (line 1 if zero).
- */
+ // Set '[ mark to the line above where the lines go (line 1 if zero).
+ orig_start = curbuf->b_op_start;
curbuf->b_op_start.lnum = ((from == 0) ? 1 : from);
curbuf->b_op_start.col = 0;
@@ -618,6 +617,7 @@ int readfile(char_u *fname, char_u *sfname, linenr_T from, linenr_T lines_to_ski
try_mac = (vim_strchr(p_ffs, 'm') != NULL);
try_dos = (vim_strchr(p_ffs, 'd') != NULL);
try_unix = (vim_strchr(p_ffs, 'x') != NULL);
+ curbuf->b_op_start = orig_start;
if (msg_scrolled == n) {
msg_scroll = m;
@@ -1888,13 +1888,13 @@ failed:
check_cursor_lnum();
beginline(BL_WHITE | BL_FIX); // on first non-blank
- /*
- * Set '[ and '] marks to the newly read lines.
- */
- curbuf->b_op_start.lnum = from + 1;
- curbuf->b_op_start.col = 0;
- curbuf->b_op_end.lnum = from + linecnt;
- curbuf->b_op_end.col = 0;
+ if (!cmdmod.lockmarks) {
+ // Set '[ and '] marks to the newly read lines.
+ curbuf->b_op_start.lnum = from + 1;
+ curbuf->b_op_start.col = 0;
+ curbuf->b_op_end.lnum = from + linecnt;
+ curbuf->b_op_end.col = 0;
+ }
}
msg_scroll = msg_save;
@@ -2013,10 +2013,8 @@ static linenr_T readfile_linenr(linenr_T linecnt, char_u *p, char_u *endp)
return lnum;
}
-/*
- * Fill "*eap" to force the 'fileencoding', 'fileformat' and 'binary to be
- * equal to the buffer "buf". Used for calling readfile().
- */
+/// Fill "*eap" to force the 'fileencoding', 'fileformat' and 'binary' to be
+/// equal to the buffer "buf". Used for calling readfile().
void prep_exarg(exarg_T *eap, const buf_T *buf)
FUNC_ATTR_NONNULL_ALL
{
@@ -2252,6 +2250,8 @@ int buf_write(buf_T *buf, char_u *fname, char_u *sfname, linenr_T start, linenr_
int write_undo_file = FALSE;
context_sha256_T sha_ctx;
unsigned int bkc = get_bkc_value(buf);
+ const pos_T orig_start = buf->b_op_start;
+ const pos_T orig_end = buf->b_op_end;
if (fname == NULL || *fname == NUL) { // safety check
return FAIL;
@@ -2432,7 +2432,13 @@ int buf_write(buf_T *buf, char_u *fname, char_u *sfname, linenr_T start, linenr_
if (buf == NULL || (buf->b_ml.ml_mfp == NULL && !empty_memline)
|| did_cmd || nofile_err
|| aborting()) {
- --no_wait_return;
+ if (buf != NULL && cmdmod.lockmarks) {
+ // restore the original '[ and '] positions
+ buf->b_op_start = orig_start;
+ buf->b_op_end = orig_end;
+ }
+
+ no_wait_return--;
msg_scroll = msg_save;
if (nofile_err) {
emsg(_("E676: No matching autocommands for acwrite buffer"));
@@ -2513,6 +2519,11 @@ int buf_write(buf_T *buf, char_u *fname, char_u *sfname, linenr_T start, linenr_
}
}
+ if (cmdmod.lockmarks) {
+ // restore the original '[ and '] positions
+ buf->b_op_start = orig_start;
+ buf->b_op_end = orig_end;
+ }
if (shortmess(SHM_OVER) && !exiting) {
msg_scroll = FALSE; // overwrite previous file message
@@ -3308,7 +3319,7 @@ restore_backup:
if (end == 0
|| (lnum == end
&& (write_bin || !buf->b_p_fixeol)
- && (lnum == buf->b_no_eol_lnum
+ && ((write_bin && lnum == buf->b_no_eol_lnum)
|| (lnum == buf->b_ml.ml_line_count && !buf->b_p_eol)))) {
lnum++; // written the line, count it
no_eol = true;
@@ -3685,11 +3696,12 @@ nofail:
msg_puts_attr(_("don't quit the editor until the file is successfully written!"),
attr | MSG_HIST);
- /* Update the timestamp to avoid an "overwrite changed file"
- * prompt when writing again. */
+ // Update the timestamp to avoid an "overwrite changed file"
+ // prompt when writing again.
if (os_fileinfo((char *)fname, &file_info_old)) {
buf_store_file_info(buf, &file_info_old);
buf->b_mtime_read = buf->b_mtime;
+ buf->b_mtime_read_ns = buf->b_mtime_ns;
}
}
}
@@ -3784,7 +3796,7 @@ static int set_rw_fname(char_u *fname, char_u *sfname)
// Do filetype detection now if 'filetype' is empty.
if (*curbuf->b_p_ft == NUL) {
- if (au_has_group((char_u *)"filetypedetect")) {
+ if (augroup_exists("filetypedetect")) {
(void)do_doautocmd((char_u *)"filetypedetect BufRead", false, NULL);
}
do_modelines(0);
@@ -3853,7 +3865,7 @@ void msg_add_lines(int insert_space, long lnum, off_T nchars)
*p++ = ' ';
}
if (shortmess(SHM_LINES)) {
- vim_snprintf((char *)p, IOSIZE - (p - IObuff), "%" PRId64 "L, %" PRId64 "C",
+ vim_snprintf((char *)p, IOSIZE - (p - IObuff), "%" PRId64 "L, %" PRId64 "B",
(int64_t)lnum, (int64_t)nchars);
} else {
vim_snprintf((char *)p, IOSIZE - (p - IObuff),
@@ -3861,7 +3873,7 @@ void msg_add_lines(int insert_space, long lnum, off_T nchars)
(int64_t)lnum);
p += STRLEN(p);
vim_snprintf((char *)p, IOSIZE - (p - IObuff),
- NGETTEXT("%" PRId64 " character", "%" PRId64 " characters", nchars),
+ NGETTEXT("%" PRId64 " byte", "%" PRId64 " bytes", nchars),
(int64_t)nchars);
}
}
@@ -3883,8 +3895,7 @@ static void msg_add_eol(void)
static int check_mtime(buf_T *buf, FileInfo *file_info)
{
if (buf->b_mtime_read != 0
- && time_differs(file_info->stat.st_mtim.tv_sec,
- buf->b_mtime_read)) {
+ && time_differs(file_info, buf->b_mtime_read, buf->b_mtime_read_ns)) {
msg_scroll = true; // Don't overwrite messages here.
msg_silent = 0; // Must give this prompt.
// Don't use emsg() here, don't want to flush the buffers.
@@ -3898,19 +3909,17 @@ static int check_mtime(buf_T *buf, FileInfo *file_info)
return OK;
}
-/// Return true if the times differ
-///
-/// @param t1 first time
-/// @param t2 second time
-static bool time_differs(long t1, long t2) FUNC_ATTR_CONST
+static bool time_differs(const FileInfo *file_info, long mtime, long mtime_ns) FUNC_ATTR_CONST
{
+ return (long)file_info->stat.st_mtim.tv_nsec != mtime_ns
#if defined(__linux__) || defined(MSWIN)
- // On a FAT filesystem, esp. under Linux, there are only 5 bits to store
- // the seconds. Since the roundoff is done when flushing the inode, the
- // time may change unexpectedly by one second!!!
- return t1 - t2 > 1 || t2 - t1 > 1;
+ // On a FAT filesystem, esp. under Linux, there are only 5 bits to store
+ // the seconds. Since the roundoff is done when flushing the inode, the
+ // time may change unexpectedly by one second!!!
+ || (long)file_info->stat.st_mtim.tv_sec - mtime > 1
+ || mtime - (long)file_info->stat.st_mtim.tv_sec > 1;
#else
- return t1 != t2;
+ || (long)file_info->stat.st_mtim.tv_sec != mtime;
#endif
}
@@ -4890,13 +4899,11 @@ static int move_lines(buf_T *frombuf, buf_T *tobuf)
return retval;
}
-/*
- * Check if buffer "buf" has been changed.
- * Also check if the file for a new buffer unexpectedly appeared.
- * return 1 if a changed buffer was found.
- * return 2 if a message has been displayed.
- * return 0 otherwise.
- */
+/// Check if buffer "buf" has been changed.
+/// Also check if the file for a new buffer unexpectedly appeared.
+/// return 1 if a changed buffer was found.
+/// return 2 if a message has been displayed.
+/// return 0 otherwise.
int buf_check_timestamp(buf_T *buf)
FUNC_ATTR_NONNULL_ALL
{
@@ -4905,7 +4912,11 @@ int buf_check_timestamp(buf_T *buf)
char *mesg = NULL;
char *mesg2 = "";
bool helpmesg = false;
- bool reload = false;
+ enum {
+ RELOAD_NONE,
+ RELOAD_NORMAL,
+ RELOAD_DETECT
+ } reload = RELOAD_NONE;
bool can_reload = false;
uint64_t orig_size = buf->b_orig_size;
int orig_mode = buf->b_orig_mode;
@@ -4933,7 +4944,7 @@ int buf_check_timestamp(buf_T *buf)
if (!(buf->b_flags & BF_NOTEDITED)
&& buf->b_mtime != 0
&& (!(file_info_ok = os_fileinfo((char *)buf->b_ffname, &file_info))
- || time_differs(file_info.stat.st_mtim.tv_sec, buf->b_mtime)
+ || time_differs(&file_info, buf->b_mtime, buf->b_mtime_ns)
|| (int)file_info.stat.st_mode != buf->b_orig_mode)) {
const long prev_b_mtime = buf->b_mtime;
@@ -4958,7 +4969,7 @@ int buf_check_timestamp(buf_T *buf)
// If 'autoread' is set, the buffer has no changes and the file still
// exists, reload the buffer. Use the buffer-local option value if it
// was set, the global option value otherwise.
- reload = true;
+ reload = RELOAD_NORMAL;
} else {
if (!file_info_ok) {
reason = "deleted";
@@ -4989,7 +5000,9 @@ int buf_check_timestamp(buf_T *buf)
}
s = get_vim_var_str(VV_FCS_CHOICE);
if (STRCMP(s, "reload") == 0 && *reason != 'd') {
- reload = true;
+ reload = RELOAD_NORMAL;
+ } else if (STRCMP(s, "edit") == 0) {
+ reload = RELOAD_DETECT;
} else if (STRCMP(s, "ask") == 0) {
n = false;
} else {
@@ -5024,6 +5037,7 @@ int buf_check_timestamp(buf_T *buf)
// Only timestamp changed, store it to avoid a warning
// in check_mtime() later.
buf->b_mtime_read = buf->b_mtime;
+ buf->b_mtime_read_ns = buf->b_mtime_ns;
}
}
}
@@ -5052,9 +5066,15 @@ int buf_check_timestamp(buf_T *buf)
xstrlcat(tbuf, "\n", tbuf_len - 1);
xstrlcat(tbuf, mesg2, tbuf_len - 1);
}
- if (do_dialog(VIM_WARNING, (char_u *)_("Warning"), (char_u *)tbuf,
- (char_u *)_("&OK\n&Load File"), 1, NULL, true) == 2) {
- reload = true;
+ switch (do_dialog(VIM_WARNING, (char_u *)_("Warning"), (char_u *)tbuf,
+ (char_u *)_("&OK\n&Load File\nLoad File &and Options"),
+ 1, NULL, true)) {
+ case 2:
+ reload = RELOAD_NORMAL;
+ break;
+ case 3:
+ reload = RELOAD_DETECT;
+ break;
}
} else if (State > NORMAL_BUSY || (State & CMDLINE) || already_warned) {
if (*mesg2 != NUL) {
@@ -5088,9 +5108,9 @@ int buf_check_timestamp(buf_T *buf)
xfree(tbuf);
}
- if (reload) {
+ if (reload != RELOAD_NONE) {
// Reload the buffer.
- buf_reload(buf, orig_mode);
+ buf_reload(buf, orig_mode, reload == RELOAD_DETECT);
if (buf->b_p_udf && buf->b_ffname != NULL) {
char_u hash[UNDO_HASH_SIZE];
@@ -5108,13 +5128,11 @@ int buf_check_timestamp(buf_T *buf)
return retval;
}
-/*
- * Reload a buffer that is already loaded.
- * Used when the file was changed outside of Vim.
- * "orig_mode" is buf->b_orig_mode before the need for reloading was detected.
- * buf->b_orig_mode may have been reset already.
- */
-void buf_reload(buf_T *buf, int orig_mode)
+/// Reload a buffer that is already loaded.
+/// Used when the file was changed outside of Vim.
+/// "orig_mode" is buf->b_orig_mode before the need for reloading was detected.
+/// buf->b_orig_mode may have been reset already.
+void buf_reload(buf_T *buf, int orig_mode, bool reload_options)
{
exarg_T ea;
pos_T old_cursor;
@@ -5129,11 +5147,15 @@ void buf_reload(buf_T *buf, int orig_mode)
// set curwin/curbuf for "buf" and save some things
aucmd_prepbuf(&aco, buf);
- // We only want to read the text from the file, not reset the syntax
- // highlighting, clear marks, diff status, etc. Force the fileformat and
- // encoding to be the same.
+ // Unless reload_options is set, we only want to read the text from the
+ // file, not reset the syntax highlighting, clear marks, diff status, etc.
+ // Force the fileformat and encoding to be the same.
+ if (reload_options) {
+ memset(&ea, 0, sizeof(ea));
+ } else {
+ prep_exarg(&ea, buf);
+ }
- prep_exarg(&ea, buf);
old_cursor = curwin->w_cursor;
old_topline = curwin->w_topline;
@@ -5252,6 +5274,7 @@ void buf_store_file_info(buf_T *buf, FileInfo *file_info)
FUNC_ATTR_NONNULL_ALL
{
buf->b_mtime = file_info->stat.st_mtim.tv_sec;
+ buf->b_mtime_ns = file_info->stat.st_mtim.tv_nsec;
buf->b_orig_size = os_fileinfo_size(file_info);
buf->b_orig_mode = (int)file_info->stat.st_mode;
}
diff --git a/src/nvim/fold.c b/src/nvim/fold.c
index b1d4321d4c..546345eeac 100644
--- a/src/nvim/fold.c
+++ b/src/nvim/fold.c
@@ -2332,7 +2332,7 @@ static linenr_T foldUpdateIEMSRecurse(garray_T *const gap, const int level,
* firstlnum.
*/
while (!got_int) {
- // set concat to 1 if it's allowed to concatenated this fold
+ // set concat to 1 if it's allowed to concatenate this fold
// with a previous one that touches it.
if (flp->start != 0 || flp->had_end <= MAX_LEVEL) {
concat = 0;
diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua
index 21f8c3855e..c6dd25154b 100644
--- a/src/nvim/generators/gen_api_dispatch.lua
+++ b/src/nvim/generators/gen_api_dispatch.lua
@@ -441,8 +441,8 @@ local function process_function(fn)
local cparam = string.format('arg%u', j)
local param_type = real_type(param[1])
local lc_param_type = real_type(param[1]):lower()
- local extra = ((param_type == "Object" or param_type == "Dictionary") and "false, ") or ""
- if param[1] == "DictionaryOf(LuaRef)" then
+ local extra = param_type == "Dictionary" and "false, " or ""
+ if param[1] == "Object" or param[1] == "DictionaryOf(LuaRef)" then
extra = "true, "
end
local errshift = 0
diff --git a/src/nvim/generators/gen_keysets.lua b/src/nvim/generators/gen_keysets.lua
index 63ef202fe1..633c5da184 100644
--- a/src/nvim/generators/gen_keysets.lua
+++ b/src/nvim/generators/gen_keysets.lua
@@ -26,6 +26,18 @@ local defspipe = io.open(defs_file, 'wb')
local keysets = require'api.keysets'
+local keywords = {
+ register = true;
+ default = true;
+}
+
+local function sanitize(key)
+ if keywords[key] then
+ return key .. "_"
+ end
+ return key
+end
+
for name, keys in pairs(keysets) do
local neworder, hashfun = hashy.hashy_hash(name, keys, function (idx)
return name.."_table["..idx.."].str"
@@ -33,7 +45,7 @@ for name, keys in pairs(keysets) do
defspipe:write("typedef struct {\n")
for _, key in ipairs(neworder) do
- defspipe:write(" Object "..key..";\n")
+ defspipe:write(" Object "..sanitize(key)..";\n")
end
defspipe:write("} KeyDict_"..name..";\n\n")
@@ -41,7 +53,7 @@ for name, keys in pairs(keysets) do
funcspipe:write("KeySetLink "..name.."_table[] = {\n")
for _, key in ipairs(neworder) do
- funcspipe:write(' {"'..key..'", offsetof(KeyDict_'..name..", "..key..")},\n")
+ funcspipe:write(' {"'..key..'", offsetof(KeyDict_'..name..", "..sanitize(key)..")},\n")
end
funcspipe:write(' {NULL, 0},\n')
funcspipe:write("};\n\n")
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index 6b1150cefa..85a5c176bb 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -15,6 +15,7 @@
#include <stdbool.h>
#include <string.h>
+#include "nvim/api/private/helpers.h"
#include "nvim/ascii.h"
#include "nvim/assert.h"
#include "nvim/buffer_defs.h"
@@ -58,25 +59,18 @@
static int curscript = 0;
FileDescriptor *scriptin[NSCRIPT] = { NULL };
-/*
- * These buffers are used for storing:
- * - stuffed characters: A command that is translated into another command.
- * - redo characters: will redo the last change.
- * - recorded characters: for the "q" command.
- *
- * The bytes are stored like in the typeahead buffer:
- * - K_SPECIAL introduces a special key (two more bytes follow). A literal
- * K_SPECIAL is stored as K_SPECIAL KS_SPECIAL KE_FILLER.
- * - CSI introduces a GUI termcap code (also when gui.in_use is FALSE,
- * otherwise switching the GUI on would make mappings invalid).
- * A literal CSI is stored as CSI KS_EXTRA KE_CSI.
- * These translations are also done on multi-byte characters!
- *
- * Escaping CSI bytes is done by the system-specific input functions, called
- * by ui_inchar().
- * Escaping K_SPECIAL is done by inchar().
- * Un-escaping is done by vgetc().
- */
+// These buffers are used for storing:
+// - stuffed characters: A command that is translated into another command.
+// - redo characters: will redo the last change.
+// - recorded characters: for the "q" command.
+//
+// The bytes are stored like in the typeahead buffer:
+// - K_SPECIAL introduces a special key (two more bytes follow). A literal
+// K_SPECIAL is stored as K_SPECIAL KS_SPECIAL KE_FILLER.
+// These translations are also done on multi-byte characters!
+//
+// Escaping K_SPECIAL is done by inchar().
+// Un-escaping is done by vgetc().
#define MINIMAL_SIZE 20 // minimal size for b_str
@@ -146,7 +140,7 @@ static int KeyNoremap = 0; // remapping flags
// typebuf.tb_buf has three parts: room in front (for result of mappings), the
// middle for typeahead and room for new characters (which needs to be 3 *
-// MAXMAPLEN) for the Amiga).
+// MAXMAPLEN for the Amiga).
#define TYPELEN_INIT (5 * (MAXMAPLEN + 3))
static char_u typebuf_init[TYPELEN_INIT]; // initial typebuf.tb_buf
static char_u noremapbuf_init[TYPELEN_INIT]; // initial typebuf.tb_noremap
@@ -169,10 +163,11 @@ void free_buff(buffheader_T *buf)
xfree(p);
}
buf->bh_first.b_next = NULL;
+ buf->bh_curr = NULL;
}
/// Return the contents of a buffer as a single string.
-/// K_SPECIAL and CSI in the returned string are escaped.
+/// K_SPECIAL in the returned string is escaped.
///
/// @param dozero count == zero is not an error
static char_u *get_buffcont(buffheader_T *buffer, int dozero)
@@ -201,11 +196,9 @@ static char_u *get_buffcont(buffheader_T *buffer, int dozero)
return p;
}
-/*
- * Return the contents of the record buffer as a single string
- * and clear the record buffer.
- * K_SPECIAL and CSI in the returned string are escaped.
- */
+/// Return the contents of the record buffer as a single string
+/// and clear the record buffer.
+/// K_SPECIAL in the returned string is escaped.
char_u *get_recorded(void)
{
char_u *p;
@@ -235,10 +228,8 @@ char_u *get_recorded(void)
return p;
}
-/*
- * Return the contents of the redo buffer as a single string.
- * K_SPECIAL and CSI in the returned string are escaped.
- */
+/// Return the contents of the redo buffer as a single string.
+/// K_SPECIAL in the returned string is escaped.
char_u *get_inserted(void)
{
return get_buffcont(&redobuff, FALSE);
@@ -246,7 +237,7 @@ char_u *get_inserted(void)
/// Add string after the current block of the given buffer
///
-/// K_SPECIAL and CSI should have been escaped already.
+/// K_SPECIAL should have been escaped already.
///
/// @param[out] buf Buffer to add to.
/// @param[in] s String to add.
@@ -294,9 +285,23 @@ static void add_buff(buffheader_T *const buf, const char *const s, ptrdiff_t sle
}
}
-/*
- * Add number "n" to buffer "buf".
- */
+/// Delete "slen" bytes from the end of "buf".
+/// Only works when it was just added.
+static void delete_buff_tail(buffheader_T *buf, int slen)
+{
+ int len;
+
+ if (buf->bh_curr == NULL) {
+ return; // nothing to delete
+ }
+ len = (int)STRLEN(buf->bh_curr->b_str);
+ if (len >= slen) {
+ buf->bh_curr->b_str[len - slen] = NUL;
+ buf->bh_space += (size_t)slen;
+ }
+}
+
+/// Add number "n" to buffer "buf".
static void add_num_buff(buffheader_T *buf, long n)
{
char number[32];
@@ -304,10 +309,8 @@ static void add_num_buff(buffheader_T *buf, long n)
add_buff(buf, number, -1L);
}
-/*
- * Add character 'c' to buffer "buf".
- * Translates special keys, NUL, CSI, K_SPECIAL and multibyte characters.
- */
+/// Add character 'c' to buffer "buf".
+/// Translates special keys, NUL, K_SPECIAL and multibyte characters.
static void add_char_buff(buffheader_T *buf, int c)
{
uint8_t bytes[MB_MAXBYTES + 1];
@@ -339,12 +342,10 @@ static void add_char_buff(buffheader_T *buf, int c)
}
}
-/*
- * Get one byte from the read buffers. Use readbuf1 one first, use readbuf2
- * if that one is empty.
- * If advance == TRUE go to the next char.
- * No translation is done K_SPECIAL and CSI are escaped.
- */
+/// Get one byte from the read buffers. Use readbuf1 one first, use readbuf2
+/// if that one is empty.
+/// If advance == TRUE go to the next char.
+/// No translation is done K_SPECIAL is escaped.
static int read_readbuffers(int advance)
{
int c;
@@ -523,10 +524,8 @@ void restoreRedobuff(save_redo_T *save_redo)
old_redobuff = save_redo->sr_old_redobuff;
}
-/*
- * Append "s" to the redo buffer.
- * K_SPECIAL and CSI should already have been escaped.
- */
+/// Append "s" to the redo buffer.
+/// K_SPECIAL should already have been escaped.
void AppendToRedobuff(const char *s)
{
if (!block_redo) {
@@ -535,7 +534,7 @@ void AppendToRedobuff(const char *s)
}
/// Append to Redo buffer literally, escaping special characters with CTRL-V.
-/// K_SPECIAL and CSI are escaped as well.
+/// K_SPECIAL is escaped as well.
///
/// @param str String to append
/// @param len Length of `str` or -1 for up to the NUL.
@@ -583,10 +582,8 @@ void AppendToRedobuffLit(const char_u *str, int len)
}
}
-/*
- * Append a character to the redo buffer.
- * Translates special keys, NUL, CSI, K_SPECIAL and multibyte characters.
- */
+/// Append a character to the redo buffer.
+/// Translates special keys, NUL, K_SPECIAL and multibyte characters.
void AppendCharToRedobuff(int c)
{
if (!block_redo) {
@@ -604,17 +601,15 @@ void AppendNumberToRedobuff(long n)
}
}
-/*
- * Append string "s" to the stuff buffer.
- * CSI and K_SPECIAL must already have been escaped.
- */
+/// Append string "s" to the stuff buffer.
+/// K_SPECIAL must already have been escaped.
void stuffReadbuff(const char *s)
{
add_buff(&readbuf1, s, -1L);
}
/// Append string "s" to the redo stuff buffer.
-/// @remark CSI and K_SPECIAL must already have been escaped.
+/// @remark K_SPECIAL must already have been escaped.
void stuffRedoReadbuff(const char *s)
{
add_buff(&readbuf2, s, -1L);
@@ -625,11 +620,9 @@ void stuffReadbuffLen(const char *s, long len)
add_buff(&readbuf1, s, len);
}
-/*
- * Stuff "s" into the stuff buffer, leaving special key codes unmodified and
- * escaping other K_SPECIAL and CSI bytes.
- * Change CR, LF and ESC into a space.
- */
+/// Stuff "s" into the stuff buffer, leaving special key codes unmodified and
+/// escaping other K_SPECIAL bytes.
+/// Change CR, LF and ESC into a space.
void stuffReadbuffSpec(const char *s)
{
while (*s != NUL) {
@@ -647,10 +640,8 @@ void stuffReadbuffSpec(const char *s)
}
}
-/*
- * Append a character to the stuff buffer.
- * Translates special keys, NUL, CSI, K_SPECIAL and multibyte characters.
- */
+/// Append a character to the stuff buffer.
+/// Translates special keys, NUL, K_SPECIAL and multibyte characters.
void stuffcharReadbuff(int c)
{
add_char_buff(&readbuf1, c);
@@ -664,12 +655,12 @@ void stuffnumReadbuff(long n)
add_num_buff(&readbuf1, n);
}
-// Read a character from the redo buffer. Translates K_SPECIAL, CSI and
-// multibyte characters.
-// The redo buffer is left as it is.
-// If init is true, prepare for redo, return FAIL if nothing to redo, OK
-// otherwise.
-// If old_redo is true, use old_redobuff instead of redobuff.
+/// Read a character from the redo buffer. Translates K_SPECIAL and
+/// multibyte characters.
+/// The redo buffer is left as it is.
+/// If init is true, prepare for redo, return FAIL if nothing to redo, OK
+/// otherwise.
+/// If old_redo is true, use old_redobuff instead of redobuff.
static int read_redo(bool init, bool old_redo)
{
static buffblock_T *bp;
@@ -723,9 +714,9 @@ static int read_redo(bool init, bool old_redo)
return c;
}
-// Copy the rest of the redo buffer into the stuff buffer (in a slow way).
-// If old_redo is true, use old_redobuff instead of redobuff.
-// The escaped K_SPECIAL and CSI are copied without translation.
+/// Copy the rest of the redo buffer into the stuff buffer (in a slow way).
+/// If old_redo is true, use old_redobuff instead of redobuff.
+/// The escaped K_SPECIAL is copied without translation.
static void copy_redo(bool old_redo)
{
int c;
@@ -861,7 +852,7 @@ void init_default_mappings(void)
//
// If noremap is REMAP_YES, new string can be mapped again.
// If noremap is REMAP_NONE, new string cannot be mapped again.
-// If noremap is REMAP_SKIP, fist char of new string cannot be mapped again,
+// If noremap is REMAP_SKIP, first char of new string cannot be mapped again,
// but abbreviations are allowed.
// If noremap is REMAP_SCRIPT, new string cannot be mapped again, except for
// script-local mappings.
@@ -991,36 +982,34 @@ int ins_typebuf(char_u *str, int noremap, int offset, bool nottyped, bool silent
return OK;
}
-/*
- * Put character "c" back into the typeahead buffer.
- * Can be used for a character obtained by vgetc() that needs to be put back.
- * Uses cmd_silent, KeyTyped and KeyNoremap to restore the flags belonging to
- * the char.
- */
-void ins_char_typebuf(int c)
+/// Put character "c" back into the typeahead buffer.
+/// Can be used for a character obtained by vgetc() that needs to be put back.
+/// Uses cmd_silent, KeyTyped and KeyNoremap to restore the flags belonging to
+/// the char.
+/// @return the length of what was inserted
+int ins_char_typebuf(int c, int modifier)
{
- char_u buf[MB_MAXBYTES + 1];
- if (IS_SPECIAL(c)) {
+ char_u buf[MB_MAXBYTES * 3 + 4];
+ int len = 0;
+ if (modifier != 0) {
buf[0] = K_SPECIAL;
- buf[1] = (char_u)K_SECOND(c);
- buf[2] = (char_u)K_THIRD(c);
+ buf[1] = KS_MODIFIER;
+ buf[2] = (char_u)modifier;
buf[3] = NUL;
+ len = 3;
+ }
+ if (IS_SPECIAL(c)) {
+ buf[len] = K_SPECIAL;
+ buf[len + 1] = (char_u)K_SECOND(c);
+ buf[len + 2] = (char_u)K_THIRD(c);
+ buf[len + 3] = NUL;
} else {
- buf[utf_char2bytes(c, buf)] = NUL;
- char_u *p = buf;
- while (*p) {
- if ((uint8_t)(*p) == CSI || (uint8_t)(*p) == K_SPECIAL) {
- bool is_csi = (uint8_t)(*p) == CSI;
- memmove(p + 3, p + 1, STRLEN(p + 1) + 1);
- *p++ = K_SPECIAL;
- *p++ = is_csi ? KS_EXTRA : KS_SPECIAL;
- *p++ = is_csi ? KE_CSI : KE_FILLER;
- } else {
- p++;
- }
- }
+ char_u *end = add_char2buf(c, buf + len);
+ *end = NUL;
+ len = (int)(end - buf);
}
(void)ins_typebuf(buf, KeyNoremap, 0, !KeyTyped, cmd_silent);
+ return len;
}
/// Return TRUE if the typeahead buffer was changed (while waiting for a
@@ -1180,6 +1169,18 @@ static void gotchars(const char_u *chars, size_t len)
maptick++;
}
+/// Undo the last gotchars() for "len" bytes. To be used when putting a typed
+/// character back into the typeahead buffer, thus gotchars() will be called
+/// again.
+/// Only affects recorded characters.
+void ungetchars(int len)
+{
+ if (reg_recording != 0) {
+ delete_buff_tail(&recordbuff, len);
+ last_recorded_len -= (size_t)len;
+ }
+}
+
/*
* Sync undo. Called when typed characters are obtained from the typeahead
* buffer, or when a menu is used.
@@ -1426,15 +1427,13 @@ static void updatescript(int c)
}
}
-/*
- * Get the next input character.
- * Can return a special key or a multi-byte character.
- * Can return NUL when called recursively, use safe_vgetc() if that's not
- * wanted.
- * This translates escaped K_SPECIAL and CSI bytes to a K_SPECIAL or CSI byte.
- * Collects the bytes of a multibyte character into the whole character.
- * Returns the modifiers in the global "mod_mask".
- */
+/// Get the next input character.
+/// Can return a special key or a multi-byte character.
+/// Can return NUL when called recursively, use safe_vgetc() if that's not
+/// wanted.
+/// This translates escaped K_SPECIAL bytes to a K_SPECIAL byte.
+/// Collects the bytes of a multibyte character into the whole character.
+/// Returns the modifiers in the global "mod_mask".
int vgetc(void)
{
int c, c2;
@@ -1460,8 +1459,9 @@ int vgetc(void)
mouse_row = old_mouse_row;
mouse_col = old_mouse_col;
} else {
- mod_mask = 0x0;
+ mod_mask = 0;
last_recorded_len = 0;
+
for (;;) { // this is done twice if there are modifiers
bool did_inc = false;
if (mod_mask) { // no mapping after modifier has been read
@@ -1571,14 +1571,9 @@ int vgetc(void)
buf[i] = (char_u)vgetorpeek(true);
if (buf[i] == K_SPECIAL) {
// Must be a K_SPECIAL - KS_SPECIAL - KE_FILLER sequence,
- // which represents a K_SPECIAL (0x80),
- // or a CSI - KS_EXTRA - KE_CSI sequence, which represents
- // a CSI (0x9B),
- // of a K_SPECIAL - KS_EXTRA - KE_CSI, which is CSI too.
- c = vgetorpeek(true);
- if (vgetorpeek(true) == KE_CSI && c == KS_EXTRA) {
- buf[i] = CSI;
- }
+ // which represents a K_SPECIAL (0x80).
+ (void)vgetorpeek(true); // skip KS_SPECIAL
+ (void)vgetorpeek(true); // skip KE_FILLER
}
}
no_mapping--;
@@ -1592,8 +1587,9 @@ int vgetc(void)
if (!no_mapping && KeyTyped && !(State & TERM_FOCUS)
&& (mod_mask == MOD_MASK_ALT || mod_mask == MOD_MASK_META)) {
mod_mask = 0;
- ins_char_typebuf(c);
- ins_char_typebuf(ESC);
+ int len = ins_char_typebuf(c, 0);
+ (void)ins_char_typebuf(ESC, 0);
+ ungetchars(len + 3); // The ALT/META modifier takes three more bytes
continue;
}
@@ -1693,7 +1689,7 @@ typedef enum {
map_result_fail, // failed, break loop
map_result_get, // get a character from typeahead
map_result_retry, // try to map again
- map_result_nomatch // no matching mapping, get char
+ map_result_nomatch, // no matching mapping, get char
} map_result_T;
/// Handle mappings in the typeahead buffer.
@@ -1714,6 +1710,15 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth)
int keylen = *keylenp;
int i;
int local_State = get_real_state();
+ bool is_plug_map = false;
+
+ // Check if typehead starts with a <Plug> mapping.
+ // In that case we will ignore nore flag on it.
+ if (typebuf.tb_buf[typebuf.tb_off] == K_SPECIAL
+ && typebuf.tb_buf[typebuf.tb_off+1] == KS_EXTRA
+ && typebuf.tb_buf[typebuf.tb_off+2] == KE_PLUG) {
+ is_plug_map = true;
+ }
// Check for a mappable key sequence.
// Walk through one maphash[] list until we find an entry that matches.
@@ -1729,7 +1734,7 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth)
tb_c1 = typebuf.tb_buf[typebuf.tb_off];
if (no_mapping == 0 && maphash_valid
&& (no_zero_mapping == 0 || tb_c1 != '0')
- && (typebuf.tb_maplen == 0
+ && (typebuf.tb_maplen == 0 || is_plug_map
|| (p_remap
&& !(typebuf.tb_noremap[typebuf.tb_off] & (RM_NONE|RM_ABBR))))
&& !(p_paste && (State & (INSERT + CMDLINE)))
@@ -1817,7 +1822,7 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth)
break;
}
}
- if (n >= 0) {
+ if (!is_plug_map && n >= 0) {
continue;
}
@@ -1902,7 +1907,7 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth)
// complete match
if (keylen >= 0 && keylen <= typebuf.tb_len) {
- char_u *map_str;
+ char_u *map_str = NULL;
int save_m_expr;
int save_m_noremap;
int save_m_silent;
@@ -1947,20 +1952,52 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth)
save_m_silent = mp->m_silent;
char_u *save_m_keys = NULL; // only saved when needed
char_u *save_m_str = NULL; // only saved when needed
+ LuaRef save_m_luaref = mp->m_luaref;
// Handle ":map <expr>": evaluate the {rhs} as an
// expression. Also save and restore the command line
// for "normal :".
if (mp->m_expr) {
- int save_vgetc_busy = vgetc_busy;
+ const int save_vgetc_busy = vgetc_busy;
const bool save_may_garbage_collect = may_garbage_collect;
+ const int save_cursor_row = ui_current_row();
+ const int save_cursor_col = ui_current_col();
+ const int prev_did_emsg = did_emsg;
vgetc_busy = 0;
may_garbage_collect = false;
save_m_keys = vim_strsave(mp->m_keys);
- save_m_str = vim_strsave(mp->m_str);
- map_str = eval_map_expr(save_m_str, NUL);
+ if (save_m_luaref == LUA_NOREF) {
+ save_m_str = vim_strsave(mp->m_str);
+ }
+ map_str = eval_map_expr(mp, NUL);
+
+ // The mapping may do anything, but we expect it to take care of
+ // redrawing. Do put the cursor back where it was.
+ ui_cursor_goto(save_cursor_row, save_cursor_col);
+ ui_flush();
+
+ // If an error was displayed and the expression returns an empty
+ // string, generate a <Nop> to allow for a redraw.
+ if (prev_did_emsg != did_emsg && (map_str == NULL || *map_str == NUL)) {
+ char_u buf[4];
+ xfree(map_str);
+ buf[0] = K_SPECIAL;
+ buf[1] = KS_EXTRA;
+ buf[2] = KE_IGNORE;
+ buf[3] = NUL;
+ map_str = vim_strsave(buf);
+ if (State & CMDLINE) {
+ // redraw the command below the error
+ msg_didout = true;
+ if (msg_row < cmdline_row) {
+ msg_row = cmdline_row;
+ }
+ redrawcmd();
+ }
+ }
+
vgetc_busy = save_vgetc_busy;
may_garbage_collect = save_may_garbage_collect;
} else {
@@ -2039,7 +2076,7 @@ void vungetc(int c)
///
/// When `no_mapping` (global) is zero, checks for mappings in the current mode.
/// Only returns one byte (of a multi-byte character).
-/// K_SPECIAL and CSI may be escaped, need to get two more bytes then.
+/// K_SPECIAL may be escaped, need to get two more bytes then.
static int vgetorpeek(bool advance)
{
int c, c1;
@@ -2294,6 +2331,10 @@ static int vgetorpeek(bool advance)
c = ESC;
}
tc = c;
+
+ // no chars to block abbreviations for
+ typebuf.tb_no_abbr_cnt = 0;
+
break;
}
@@ -2452,7 +2493,7 @@ static int vgetorpeek(bool advance)
/// 1. a scriptfile
/// 2. the keyboard
///
-/// As much characters as we can get (up to 'maxlen') are put in "buf" and
+/// As many characters as we can get (up to 'maxlen') are put in "buf" and
/// NUL terminated (buffer length must be 'maxlen' + 1).
/// Minimum for "maxlen" is 3!!!!
///
@@ -2470,7 +2511,7 @@ static int vgetorpeek(bool advance)
/// Return the number of obtained characters.
/// Return -1 when end of input script reached.
///
-/// @param wait_time milli seconds
+/// @param wait_time milliseconds
int inchar(char_u *buf, int maxlen, long wait_time)
{
int len = 0; // Init for GCC.
@@ -2569,7 +2610,7 @@ int fix_input_buffer(char_u *buf, int len)
FUNC_ATTR_NONNULL_ALL
{
if (!using_script()) {
- // Should not escape K_SPECIAL/CSI reading input from the user because vim
+ // Should not escape K_SPECIAL reading input from the user because vim
// key codes keys are processed in input.c/input_enqueue.
buf[len] = NUL;
return len;
@@ -2580,9 +2621,8 @@ int fix_input_buffer(char_u *buf, int len)
char_u *p = buf;
// Two characters are special: NUL and K_SPECIAL.
- // Replace NUL by K_SPECIAL KS_ZERO KE_FILLER
+ // Replace NUL by K_SPECIAL KS_ZERO KE_FILLER
// Replace K_SPECIAL by K_SPECIAL KS_SPECIAL KE_FILLER
- // Replace CSI by K_SPECIAL KS_EXTRA KE_CSI
for (i = len; --i >= 0; ++p) {
if (p[0] == NUL
|| (p[0] == K_SPECIAL
@@ -2618,11 +2658,13 @@ int fix_input_buffer(char_u *buf, int len)
/// @param[in] orig_lhs Original mapping LHS, with characters to replace.
/// @param[in] orig_lhs_len `strlen` of orig_lhs.
/// @param[in] orig_rhs Original mapping RHS, with characters to replace.
+/// @param[in] rhs_lua Lua reference for Lua maps.
/// @param[in] orig_rhs_len `strlen` of orig_rhs.
/// @param[in] cpo_flags See param docs for @ref replace_termcodes.
/// @param[out] mapargs MapArguments struct holding the replaced strings.
-void set_maparg_lhs_rhs(const char_u *orig_lhs, const size_t orig_lhs_len, const char_u *orig_rhs,
- const size_t orig_rhs_len, int cpo_flags, MapArguments *mapargs)
+void set_maparg_lhs_rhs(const char_u *orig_lhs, const size_t orig_lhs_len,
+ const char_u *orig_rhs, const size_t orig_rhs_len,
+ LuaRef rhs_lua, int cpo_flags, MapArguments *mapargs)
{
char_u *lhs_buf = NULL;
char_u *rhs_buf = NULL;
@@ -2638,22 +2680,34 @@ void set_maparg_lhs_rhs(const char_u *orig_lhs, const size_t orig_lhs_len, const
true, true, true, cpo_flags);
mapargs->lhs_len = STRLEN(replaced);
STRLCPY(mapargs->lhs, replaced, sizeof(mapargs->lhs));
+ mapargs->rhs_lua = rhs_lua;
- mapargs->orig_rhs_len = orig_rhs_len;
- mapargs->orig_rhs = xcalloc(mapargs->orig_rhs_len + 1, sizeof(char_u));
- STRLCPY(mapargs->orig_rhs, orig_rhs, mapargs->orig_rhs_len + 1);
+ if (rhs_lua == LUA_NOREF) {
+ mapargs->orig_rhs_len = orig_rhs_len;
+ mapargs->orig_rhs = xcalloc(mapargs->orig_rhs_len + 1, sizeof(char_u));
+ STRLCPY(mapargs->orig_rhs, orig_rhs, mapargs->orig_rhs_len + 1);
- if (STRICMP(orig_rhs, "<nop>") == 0) { // "<Nop>" means nothing
- mapargs->rhs = xcalloc(1, sizeof(char_u)); // single null-char
- mapargs->rhs_len = 0;
- mapargs->rhs_is_noop = true;
+ if (STRICMP(orig_rhs, "<nop>") == 0) { // "<Nop>" means nothing
+ mapargs->rhs = xcalloc(1, sizeof(char_u)); // single null-char
+ mapargs->rhs_len = 0;
+ mapargs->rhs_is_noop = true;
+ } else {
+ replaced = replace_termcodes(orig_rhs, orig_rhs_len, &rhs_buf,
+ false, true, true, cpo_flags);
+ mapargs->rhs_len = STRLEN(replaced);
+ mapargs->rhs_is_noop = false;
+ mapargs->rhs = xcalloc(mapargs->rhs_len + 1, sizeof(char_u));
+ STRLCPY(mapargs->rhs, replaced, mapargs->rhs_len + 1);
+ }
} else {
- replaced = replace_termcodes(orig_rhs, orig_rhs_len, &rhs_buf,
- false, true, true, cpo_flags);
- mapargs->rhs_len = STRLEN(replaced);
- mapargs->rhs_is_noop = false;
- mapargs->rhs = xcalloc(mapargs->rhs_len + 1, sizeof(char_u));
- STRLCPY(mapargs->rhs, replaced, mapargs->rhs_len + 1);
+ char tmp_buf[64];
+ // orig_rhs is not used for Lua mappings, but still needs to be a string.
+ mapargs->orig_rhs = xcalloc(1, sizeof(char_u));
+ mapargs->orig_rhs_len = 0;
+ // stores <lua>ref_no<cr> in map_str
+ mapargs->rhs_len = (size_t)vim_snprintf(S_LEN(tmp_buf), "%c%c%c%d\r", K_SPECIAL,
+ (char_u)KS_EXTRA, KE_LUA, rhs_lua);
+ mapargs->rhs = vim_strsave((char_u *)tmp_buf);
}
xfree(lhs_buf);
@@ -2765,7 +2819,7 @@ int str_to_mapargs(const char_u *strargs, bool is_unmap, MapArguments *mapargs)
size_t orig_rhs_len = STRLEN(rhs_start);
set_maparg_lhs_rhs(lhs_to_replace, orig_lhs_len,
- rhs_start, orig_rhs_len,
+ rhs_start, orig_rhs_len, LUA_NOREF,
CPO_TO_CPO_FLAGS, &parsed_args);
xfree(lhs_to_replace);
@@ -2827,7 +2881,7 @@ int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, buf_T
validate_maphash();
bool has_lhs = (args->lhs[0] != NUL);
- bool has_rhs = (args->rhs[0] != NUL) || args->rhs_is_noop;
+ bool has_rhs = args->rhs_lua != LUA_NOREF || (args->rhs[0] != NUL) || args->rhs_is_noop;
// check for :unmap without argument
if (maptype == 1 && !has_lhs) {
@@ -3017,10 +3071,14 @@ int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, buf_T
} else { // new rhs for existing entry
mp->m_mode &= ~mode; // remove mode bits
if (mp->m_mode == 0 && !did_it) { // reuse entry
- xfree(mp->m_str);
+ XFREE_CLEAR(mp->m_str);
+ XFREE_CLEAR(mp->m_orig_str);
+ XFREE_CLEAR(mp->m_desc);
+ NLUA_CLEAR_REF(mp->m_luaref);
+
mp->m_str = vim_strsave(rhs);
- xfree(mp->m_orig_str);
mp->m_orig_str = vim_strsave(orig_rhs);
+ mp->m_luaref = args->rhs_lua;
mp->m_noremap = noremap;
mp->m_nowait = args->nowait;
mp->m_silent = args->silent;
@@ -3028,6 +3086,10 @@ int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, buf_T
mp->m_expr = args->expr;
mp->m_script_ctx = current_sctx;
mp->m_script_ctx.sc_lnum += sourcing_lnum;
+ nlua_set_sctx(&mp->m_script_ctx);
+ if (args->desc != NULL) {
+ mp->m_desc = xstrdup(args->desc);
+ }
did_it = true;
}
}
@@ -3096,6 +3158,7 @@ int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, buf_T
mp->m_keys = vim_strsave(lhs);
mp->m_str = vim_strsave(rhs);
mp->m_orig_str = vim_strsave(orig_rhs);
+ mp->m_luaref = args->rhs_lua;
mp->m_keylen = (int)STRLEN(mp->m_keys);
mp->m_noremap = noremap;
mp->m_nowait = args->nowait;
@@ -3104,6 +3167,11 @@ int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, buf_T
mp->m_expr = args->expr;
mp->m_script_ctx = current_sctx;
mp->m_script_ctx.sc_lnum += sourcing_lnum;
+ nlua_set_sctx(&mp->m_script_ctx);
+ mp->m_desc = NULL;
+ if (args->desc != NULL) {
+ mp->m_desc = xstrdup(args->desc);
+ }
// add the new entry in front of the abbrlist or maphash[] list
if (is_abbrev) {
@@ -3200,8 +3268,10 @@ static void mapblock_free(mapblock_T **mpp)
mp = *mpp;
xfree(mp->m_keys);
- xfree(mp->m_str);
- xfree(mp->m_orig_str);
+ NLUA_CLEAR_REF(mp->m_luaref);
+ XFREE_CLEAR(mp->m_str);
+ XFREE_CLEAR(mp->m_orig_str);
+ XFREE_CLEAR(mp->m_desc);
*mpp = mp->m_next;
xfree(mp);
}
@@ -3392,7 +3462,8 @@ static void showmap(mapblock_T *mp, bool local)
{
size_t len = 1;
- if (message_filtered(mp->m_keys) && message_filtered(mp->m_str)) {
+ if (message_filtered(mp->m_keys) && message_filtered(mp->m_str)
+ && (mp->m_desc == NULL || message_filtered((char_u *)mp->m_desc))) {
return;
}
@@ -3437,19 +3508,29 @@ static void showmap(mapblock_T *mp, bool local)
/* Use FALSE below if we only want things like <Up> to show up as such on
* the rhs, and not M-x etc, TRUE gets both -- webb */
- if (*mp->m_str == NUL) {
+ if (mp->m_luaref != LUA_NOREF) {
+ char msg[100];
+ snprintf(msg, sizeof(msg), "<Lua function %d>", mp->m_luaref);
+ msg_puts_attr(msg, HL_ATTR(HLF_8));
+ } else if (mp->m_str[0] == NUL) {
msg_puts_attr("<Nop>", HL_ATTR(HLF_8));
} else {
- // Remove escaping of CSI, because "m_str" is in a format to be used
+ // Remove escaping of K_SPECIAL, because "m_str" is in a format to be used
// as typeahead.
char_u *s = vim_strsave(mp->m_str);
- vim_unescape_csi(s);
+ vim_unescape_ks(s);
msg_outtrans_special(s, false, 0);
xfree(s);
}
+
+ if (mp->m_desc != NULL) {
+ msg_puts("\n "); // Shift line to same level as rhs.
+ msg_puts(mp->m_desc);
+ }
if (p_verbose > 0) {
last_set_msg(mp->m_script_ctx);
}
+ msg_clr_eos();
ui_flush(); // show one line at a time
}
@@ -3532,8 +3613,7 @@ int map_to_exists_mode(const char *const rhs, const int mode, const bool abbr)
mp = maphash[hash];
}
for (; mp; mp = mp->m_next) {
- if ((mp->m_mode & mode)
- && strstr((char *)mp->m_str, rhs) != NULL) {
+ if ((mp->m_mode & mode) && strstr((char *)mp->m_str, rhs) != NULL) {
return true;
}
}
@@ -3818,9 +3898,9 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol)
int match;
if (strchr((const char *)mp->m_keys, K_SPECIAL) != NULL) {
- // Might have CSI escaped mp->m_keys.
+ // Might have K_SPECIAL escaped mp->m_keys.
q = vim_strsave(mp->m_keys);
- vim_unescape_csi(q);
+ vim_unescape_ks(q);
qlen = (int)STRLEN(q);
}
// find entries with right mode and keys
@@ -3866,7 +3946,7 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol)
int newlen = utf_char2bytes(c, tb + j);
tb[j + newlen] = NUL;
// Need to escape K_SPECIAL.
- char_u *escaped = vim_strsave_escape_csi(tb + j);
+ char_u *escaped = vim_strsave_escape_ks(tb + j);
if (escaped != NULL) {
newlen = (int)STRLEN(escaped);
memmove(tb + j, escaped, (size_t)newlen);
@@ -3879,7 +3959,7 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol)
(void)ins_typebuf(tb, 1, 0, true, mp->m_silent);
}
if (mp->m_expr) {
- s = eval_map_expr(mp->m_str, c);
+ s = eval_map_expr(mp, c);
} else {
s = mp->m_str;
}
@@ -3909,20 +3989,22 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol)
/// special characters.
///
/// @param c NUL or typed character for abbreviation
-static char_u *eval_map_expr(char_u *str, int c)
+static char_u *eval_map_expr(mapblock_T *mp, int c)
{
char_u *res;
- char_u *p;
- char_u *expr;
+ char_u *p = NULL;
+ char_u *expr = NULL;
char_u *save_cmd;
pos_T save_cursor;
int save_msg_col;
int save_msg_row;
- /* Remove escaping of CSI, because "str" is in a format to be used as
- * typeahead. */
- expr = vim_strsave(str);
- vim_unescape_csi(expr);
+ // Remove escaping of K_SPECIAL, because "str" is in a format to be used as
+ // typeahead.
+ if (mp->m_luaref == LUA_NOREF) {
+ expr = vim_strsave(mp->m_str);
+ vim_unescape_ks(expr);
+ }
save_cmd = save_cmdline_alloc();
@@ -3934,7 +4016,22 @@ static char_u *eval_map_expr(char_u *str, int c)
save_cursor = curwin->w_cursor;
save_msg_col = msg_col;
save_msg_row = msg_row;
- p = eval_to_string(expr, NULL, false);
+ if (mp->m_luaref != LUA_NOREF) {
+ Error err = ERROR_INIT;
+ Array args = ARRAY_DICT_INIT;
+ Object ret = nlua_call_ref(mp->m_luaref, NULL, args, true, &err);
+ if (ret.type == kObjectTypeString) {
+ p = (char_u *)xstrndup(ret.data.string.data, ret.data.string.size);
+ }
+ api_free_object(ret);
+ if (err.type != kErrorTypeNone) {
+ semsg_multiline("E5108: %s", err.msg);
+ api_clear_error(&err);
+ }
+ } else {
+ p = eval_to_string(expr, NULL, false);
+ xfree(expr);
+ }
textlock--;
ex_normal_lock--;
curwin->w_cursor = save_cursor;
@@ -3942,23 +4039,20 @@ static char_u *eval_map_expr(char_u *str, int c)
msg_row = save_msg_row;
restore_cmdline_alloc(save_cmd);
- xfree(expr);
if (p == NULL) {
return NULL;
}
- // Escape CSI in the result to be able to use the string as typeahead.
- res = vim_strsave_escape_csi(p);
+ // Escape K_SPECIAL in the result to be able to use the string as typeahead.
+ res = vim_strsave_escape_ks(p);
xfree(p);
return res;
}
-/*
- * Copy "p" to allocated memory, escaping K_SPECIAL and CSI so that the result
- * can be put in the typeahead buffer.
- */
-char_u *vim_strsave_escape_csi(char_u *p)
+/// Copy "p" to allocated memory, escaping K_SPECIAL so that the result
+/// can be put in the typeahead buffer.
+char_u *vim_strsave_escape_ks(char_u *p)
{
// Need a buffer to hold up to three times as much. Four in case of an
// illegal utf-8 byte:
@@ -3973,7 +4067,7 @@ char_u *vim_strsave_escape_csi(char_u *p)
*d++ = *s++;
} else {
// Add character, possibly multi-byte to destination, escaping
- // CSI and K_SPECIAL. Be careful, it can be an illegal byte!
+ // K_SPECIAL. Be careful, it can be an illegal byte!
d = add_char2buf(utf_ptr2char(s), d);
s += utf_ptr2len(s);
}
@@ -3983,11 +4077,9 @@ char_u *vim_strsave_escape_csi(char_u *p)
return res;
}
-/*
- * Remove escaping from CSI and K_SPECIAL characters. Reverse of
- * vim_strsave_escape_csi(). Works in-place.
- */
-void vim_unescape_csi(char_u *p)
+/// Remove escaping from K_SPECIAL characters. Reverse of
+/// vim_strsave_escape_ks(). Works in-place.
+void vim_unescape_ks(char_u *p)
{
char_u *s = p, *d = p;
@@ -3995,10 +4087,6 @@ void vim_unescape_csi(char_u *p)
if (s[0] == K_SPECIAL && s[1] == KS_SPECIAL && s[2] == KE_FILLER) {
*d++ = K_SPECIAL;
s += 3;
- } else if ((s[0] == K_SPECIAL || s[0] == CSI)
- && s[1] == KS_EXTRA && s[2] == (int)KE_CSI) {
- *d++ = CSI;
- s += 3;
} else {
*d++ = *s++;
}
@@ -4049,8 +4137,11 @@ int makemap(FILE *fd, buf_T *buf)
continue;
}
- // skip mappings that contain a <SNR> (script-local thing),
+ // skip lua mappings and mappings that contain a <SNR> (script-local thing),
// they probably don't work when loaded again
+ if (mp->m_luaref != LUA_NOREF) {
+ continue;
+ }
for (p = mp->m_str; *p != NUL; p++) {
if (p[0] == K_SPECIAL && p[1] == KS_EXTRA
&& p[2] == (int)KE_SNR) {
@@ -4238,7 +4329,7 @@ int put_escstr(FILE *fd, char_u *strstart, int what)
for (; *str != NUL; str++) {
// Check for a multi-byte character, which may contain escaped
- // K_SPECIAL and CSI bytes.
+ // K_SPECIAL bytes.
const char *p = mb_unescape((const char **)&str);
if (p != NULL) {
while (*p != NUL) {
@@ -4331,10 +4422,11 @@ int put_escstr(FILE *fd, char_u *strstart, int what)
/// @param mp_ptr return: pointer to mapblock or NULL
/// @param local_ptr return: buffer-local mapping or NULL
char_u *check_map(char_u *keys, int mode, int exact, int ign_mod, int abbr, mapblock_T **mp_ptr,
- int *local_ptr)
+ int *local_ptr, int *rhs_lua)
{
int len, minlen;
mapblock_T *mp;
+ *rhs_lua = LUA_NOREF;
validate_maphash();
@@ -4375,7 +4467,8 @@ char_u *check_map(char_u *keys, int mode, int exact, int ign_mod, int abbr, mapb
if (local_ptr != NULL) {
*local_ptr = local;
}
- return mp->m_str;
+ *rhs_lua = mp->m_luaref;
+ return mp->m_luaref == LUA_NOREF ? mp->m_str : NULL;
}
}
}
@@ -4560,3 +4653,47 @@ char_u *getcmdkeycmd(int promptc, void *cookie, int indent, bool do_concat)
return (char_u *)line_ga.ga_data;
}
+
+bool map_execute_lua(void)
+{
+ garray_T line_ga;
+ int c1 = -1;
+ bool aborted = false;
+
+ ga_init(&line_ga, 1, 32);
+
+ no_mapping++;
+
+ got_int = false;
+ while (c1 != NUL && !aborted) {
+ ga_grow(&line_ga, 32);
+ // Get one character at a time.
+ c1 = vgetorpeek(true);
+ if (got_int) {
+ aborted = true;
+ } else if (c1 == '\r' || c1 == '\n') {
+ c1 = NUL; // end the line
+ } else {
+ ga_append(&line_ga, (char)c1);
+ }
+ }
+
+ no_mapping--;
+
+ if (aborted) {
+ ga_clear(&line_ga);
+ return false;
+ }
+
+ LuaRef ref = (LuaRef)atoi(line_ga.ga_data);
+ Error err = ERROR_INIT;
+ Array args = ARRAY_DICT_INIT;
+ nlua_call_ref(ref, NULL, args, false, &err);
+ if (err.type != kErrorTypeNone) {
+ semsg_multiline("E5108: %s", err.msg);
+ api_clear_error(&err);
+ }
+
+ ga_clear(&line_ga);
+ return true;
+}
diff --git a/src/nvim/getchar.h b/src/nvim/getchar.h
index 5950611d3f..f24a4e7c7c 100644
--- a/src/nvim/getchar.h
+++ b/src/nvim/getchar.h
@@ -50,14 +50,16 @@ struct map_arguments {
char_u *rhs; /// The {rhs} of the mapping.
size_t rhs_len;
+ LuaRef rhs_lua; /// lua function as rhs
bool rhs_is_noop; /// True when the {orig_rhs} is <nop>.
char_u *orig_rhs; /// The original text of the {rhs}.
size_t orig_rhs_len;
+ char *desc; /// map description
};
typedef struct map_arguments MapArguments;
#define MAP_ARGUMENTS_INIT { false, false, false, false, false, false, false, \
- { 0 }, 0, NULL, 0, false, NULL, 0 }
+ { 0 }, 0, NULL, 0, LUA_NOREF, false, NULL, 0, NULL }
#define KEYLEN_PART_KEY -1 // keylen value for incomplete key-code
#define KEYLEN_PART_MAP -2 // keylen value for incomplete mapping
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index 697d4b11a7..35ad57906b 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -28,7 +28,7 @@
#endif
#ifndef FILETYPE_FILE
-# define FILETYPE_FILE "filetype.vim"
+# define FILETYPE_FILE "filetype.lua filetype.vim"
#endif
#ifndef FTPLUGIN_FILE
@@ -127,7 +127,7 @@ typedef off_t off_T;
// When vgetc() is called, it sets mod_mask to the set of modifiers that are
// held down based on the MOD_MASK_* symbols that are read first.
-EXTERN int mod_mask INIT(= 0x0); // current key modifiers
+EXTERN int mod_mask INIT(= 0); // current key modifiers
// Cmdline_row is the row where the command line starts, just below the
@@ -326,16 +326,16 @@ EXTERN int want_garbage_collect INIT(= false);
EXTERN int garbage_collect_at_exit INIT(= false);
// Special values for current_SID.
-#define SID_MODELINE -1 // when using a modeline
-#define SID_CMDARG -2 // for "--cmd" argument
-#define SID_CARG -3 // for "-c" argument
-#define SID_ENV -4 // for sourcing environment variable
-#define SID_ERROR -5 // option was reset because of an error
-#define SID_NONE -6 // don't set scriptID
-#define SID_WINLAYOUT -7 // changing window size
-#define SID_LUA -8 // for Lua scripts/chunks
-#define SID_API_CLIENT -9 // for API clients
-#define SID_STR -10 // for sourcing a string with no script item
+#define SID_MODELINE (-1) // when using a modeline
+#define SID_CMDARG (-2) // for "--cmd" argument
+#define SID_CARG (-3) // for "-c" argument
+#define SID_ENV (-4) // for sourcing environment variable
+#define SID_ERROR (-5) // option was reset because of an error
+#define SID_NONE (-6) // don't set scriptID
+#define SID_WINLAYOUT (-7) // changing window size
+#define SID_LUA (-8) // for Lua scripts/chunks
+#define SID_API_CLIENT (-9) // for API clients
+#define SID_STR (-10) // for sourcing a string with no script item
// Script CTX being sourced or was sourced to define the current function.
EXTERN sctx_T current_sctx INIT(= { 0 COMMA 0 COMMA 0 });
@@ -524,6 +524,8 @@ EXTERN pos_T VIsual;
EXTERN int VIsual_active INIT(= false);
/// Whether Select mode is active.
EXTERN int VIsual_select INIT(= false);
+/// Register name for Select mode
+EXTERN int VIsual_select_reg INIT(= 0);
/// Restart Select mode when next cmd finished
EXTERN int restart_VIsual_select INIT(= 0);
/// Whether to restart the selection after a Select-mode mapping or menu.
@@ -977,6 +979,7 @@ EXTERN char e_invalidreg[] INIT(= N_("E850: Invalid register name"));
EXTERN char e_dirnotf[] INIT(= N_("E919: Directory not found in '%s': \"%s\""));
EXTERN char e_au_recursive[] INIT(= N_("E952: Autocommand caused recursive behavior"));
EXTERN char e_autocmd_close[] INIT(= N_("E813: Cannot close autocmd window"));
+EXTERN char e_listarg[] INIT(= N_("E686: Argument of %s must be a List"));
EXTERN char e_unsupportedoption[] INIT(= N_("E519: Option not supported"));
EXTERN char e_fnametoolong[] INIT(= N_("E856: Filename too long"));
EXTERN char e_float_as_string[] INIT(= N_("E806: using Float as a String"));
@@ -997,6 +1000,10 @@ EXTERN char e_non_empty_string_required[] INIT(= N_("E1142: Non-empty string req
EXTERN char e_cannot_define_autocommands_for_all_events[] INIT(= N_("E1155: Cannot define autocommands for ALL events"));
+EXTERN char e_resulting_text_too_long[] INIT(= N_("E1240: Resulting text too long"));
+
+EXTERN char e_line_number_out_of_range[] INIT(= N_("E1247: Line number out of range"));
+
EXTERN char e_highlight_group_name_too_long[] INIT(= N_("E1249: Highlight group name too long"));
EXTERN char top_bot_msg[] INIT(= N_("search hit TOP, continuing at BOTTOM"));
diff --git a/src/nvim/hardcopy.c b/src/nvim/hardcopy.c
index 6fc70144ac..eb10c65be9 100644
--- a/src/nvim/hardcopy.c
+++ b/src/nvim/hardcopy.c
@@ -386,30 +386,43 @@ static uint32_t prt_get_term_color(int colorindex)
return cterm_color_8[colorindex % 8];
}
-static void prt_get_attr(int hl_id, prt_text_attr_T *pattr, int modec)
+static uint32_t prt_get_color(int hl_id, int modec)
{
int colorindex;
uint32_t fg_color;
+ const char *color = highlight_color(hl_id, "fg#", 'g');
+ if (color != NULL) {
+ RgbValue rgb = name_to_color(color);
+ if (rgb != -1) {
+ return (uint32_t)rgb;
+ }
+ }
+
+ color = highlight_color(hl_id, "fg", modec);
+ if (color == NULL) {
+ colorindex = 0;
+ } else {
+ colorindex = atoi(color);
+ }
+
+ if (colorindex >= 0 && colorindex < t_colors) {
+ fg_color = prt_get_term_color(colorindex);
+ } else {
+ fg_color = PRCOLOR_BLACK;
+ }
+
+ return fg_color;
+}
+
+static void prt_get_attr(int hl_id, prt_text_attr_T *pattr, int modec)
+{
pattr->bold = (highlight_has_attr(hl_id, HL_BOLD, modec) != NULL);
pattr->italic = (highlight_has_attr(hl_id, HL_ITALIC, modec) != NULL);
pattr->underline = (highlight_has_attr(hl_id, HL_UNDERLINE, modec) != NULL);
pattr->undercurl = (highlight_has_attr(hl_id, HL_UNDERCURL, modec) != NULL);
- {
- const char *color = highlight_color(hl_id, "fg", modec);
- if (color == NULL) {
- colorindex = 0;
- } else {
- colorindex = atoi(color);
- }
-
- if (colorindex >= 0 && colorindex < t_colors) {
- fg_color = prt_get_term_color(colorindex);
- } else {
- fg_color = PRCOLOR_BLACK;
- }
- }
+ uint32_t fg_color = prt_get_color(hl_id, modec);
if (fg_color == PRCOLOR_WHITE) {
fg_color = PRCOLOR_BLACK;
diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c
index 3050ca02de..e43a56086f 100644
--- a/src/nvim/highlight.c
+++ b/src/nvim/highlight.c
@@ -144,13 +144,19 @@ int hl_get_syn_attr(int ns_id, int idx, HlAttrs at_en)
}
}
-void ns_hl_def(NS ns_id, int hl_id, HlAttrs attrs, int link_id)
+void ns_hl_def(NS ns_id, int hl_id, HlAttrs attrs, int link_id, Dict(highlight) *dict)
{
- DecorProvider *p = get_decor_provider(ns_id, true);
if ((attrs.rgb_ae_attr & HL_DEFAULT)
&& map_has(ColorKey, ColorItem)(&ns_hl, ColorKey(ns_id, hl_id))) {
return;
}
+ if (ns_id == 0) {
+ assert(dict);
+ // set in global (':highlight') namespace
+ set_hl_group(hl_id, attrs, dict, link_id);
+ return;
+ }
+ DecorProvider *p = get_decor_provider(ns_id, true);
int attr_id = link_id > 0 ? -1 : hl_get_syn_attr(ns_id, hl_id, attrs);
ColorItem it = { .attr_id = attr_id,
.link_id = link_id,
@@ -192,23 +198,17 @@ int ns_get_hl(NS ns_id, int hl_id, bool link, bool nodefault)
int tmp = false;
HlAttrs attrs = HLATTRS_INIT;
if (ret.type == kObjectTypeDictionary) {
- Dictionary dict = ret.data.dictionary;
fallback = false;
- attrs = dict2hlattrs(dict, true, &it.link_id, &err);
- for (size_t i = 0; i < dict.size; i++) {
- char *key = dict.items[i].key.data;
- Object val = dict.items[i].value;
- bool truthy = api_object_to_bool(val, key, false, &err);
-
- if (strequal(key, "fallback")) {
- fallback = truthy;
- } else if (strequal(key, "temp")) {
- tmp = truthy;
+ Dict(highlight) dict = { 0 };
+ if (api_dict_to_keydict(&dict, KeyDict_highlight_get_field,
+ ret.data.dictionary, &err)) {
+ attrs = dict2hlattrs(&dict, true, &it.link_id, &err);
+ fallback = api_object_to_bool(dict.fallback, "fallback", true, &err);
+ tmp = api_object_to_bool(dict.fallback, "tmp", false, &err);
+ if (it.link_id >= 0) {
+ fallback = true;
}
}
- if (it.link_id >= 0) {
- fallback = true;
- }
}
it.attr_id = fallback ? -1 : hl_get_syn_attr((int)ns_id, hl_id, attrs);
@@ -796,112 +796,121 @@ Dictionary hlattrs2dict(HlAttrs ae, bool use_rgb)
return hl;
}
-HlAttrs dict2hlattrs(Dictionary dict, bool use_rgb, int *link_id, Error *err)
+HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *err)
{
HlAttrs hlattrs = HLATTRS_INIT;
-
int32_t fg = -1, bg = -1, ctermfg = -1, ctermbg = -1, sp = -1;
+ int blend = -1;
int16_t mask = 0;
int16_t cterm_mask = 0;
bool cterm_mask_provided = false;
- for (size_t i = 0; i < dict.size; i++) {
- char *key = dict.items[i].key.data;
- Object val = dict.items[i].value;
-
- struct {
- const char *name;
- int16_t flag;
- } flags[] = {
- { "bold", HL_BOLD },
- { "standout", HL_STANDOUT },
- { "underline", HL_UNDERLINE },
- { "undercurl", HL_UNDERCURL },
- { "italic", HL_ITALIC },
- { "reverse", HL_INVERSE },
- { "default", HL_DEFAULT },
- { "global", HL_GLOBAL },
- { NULL, 0 },
- };
-
- int j;
- for (j = 0; flags[j].name; j++) {
- if (strequal(flags[j].name, key)) {
- if (api_object_to_bool(val, key, false, err)) {
- mask = mask | flags[j].flag;
- }
- break;
- }
+#define CHECK_FLAG(d, m, name, extra, flag) \
+ if (api_object_to_bool(d->name ## extra, #name, false, err)) { \
+ m = m | flag; \
}
- // Handle cterm attrs
- if (strequal(key, "cterm") && val.type == kObjectTypeDictionary) {
- cterm_mask_provided = true;
- Dictionary cterm_dict = val.data.dictionary;
- for (size_t l = 0; l < cterm_dict.size; l++) {
- char *cterm_dict_key = cterm_dict.items[l].key.data;
- Object cterm_dict_val = cterm_dict.items[l].value;
- for (int m = 0; flags[m].name; m++) {
- if (strequal(flags[m].name, cterm_dict_key)) {
- if (api_object_to_bool(cterm_dict_val, cterm_dict_key, false,
- err)) {
- cterm_mask |= flags[m].flag;
- }
- break;
- }
- }
- }
+ CHECK_FLAG(dict, mask, bold, , HL_BOLD);
+ CHECK_FLAG(dict, mask, standout, , HL_STANDOUT);
+ CHECK_FLAG(dict, mask, underline, , HL_UNDERLINE);
+ CHECK_FLAG(dict, mask, undercurl, , HL_UNDERCURL);
+ CHECK_FLAG(dict, mask, italic, , HL_ITALIC);
+ CHECK_FLAG(dict, mask, reverse, , HL_INVERSE);
+ CHECK_FLAG(dict, mask, strikethrough, , HL_STRIKETHROUGH);
+ CHECK_FLAG(dict, mask, nocombine, , HL_NOCOMBINE);
+ CHECK_FLAG(dict, mask, default, _, HL_DEFAULT);
+ CHECK_FLAG(dict, mask, global, , HL_GLOBAL);
+
+ if (HAS_KEY(dict->fg)) {
+ fg = object_to_color(dict->fg, "fg", true, err);
+ } else if (HAS_KEY(dict->foreground)) {
+ fg = object_to_color(dict->foreground, "foreground", true, err);
+ }
+ if (ERROR_SET(err)) {
+ return hlattrs;
+ }
+
+ if (HAS_KEY(dict->bg)) {
+ bg = object_to_color(dict->bg, "bg", true, err);
+ } else if (HAS_KEY(dict->background)) {
+ bg = object_to_color(dict->background, "background", true, err);
+ }
+ if (ERROR_SET(err)) {
+ return hlattrs;
+ }
+
+ if (HAS_KEY(dict->sp)) {
+ sp = object_to_color(dict->sp, "sp", true, err);
+ } else if (HAS_KEY(dict->special)) {
+ sp = object_to_color(dict->special, "special", true, err);
+ }
+ if (ERROR_SET(err)) {
+ return hlattrs;
+ }
+
+ if (dict->blend.type == kObjectTypeInteger) {
+ Integer blend0 = dict->blend.data.integer;
+ if (blend0 < 0 || blend0 > 100) {
+ api_set_error(err, kErrorTypeValidation, "'blend' is not between 0 to 100");
+ } else {
+ blend = (int)blend0;
}
+ } else if (HAS_KEY(dict->blend)) {
+ api_set_error(err, kErrorTypeValidation, "'blend' must be an integer");
+ }
+ if (ERROR_SET(err)) {
+ return hlattrs;
+ }
- struct {
- const char *name;
- const char *shortname;
- int *dest;
- } colors[] = {
- { "foreground", "fg", &fg },
- { "background", "bg", &bg },
- { "ctermfg", NULL, &ctermfg },
- { "ctermbg", NULL, &ctermbg },
- { "special", "sp", &sp },
- { NULL, NULL, NULL },
- };
-
- int k;
- for (k = 0; (!flags[j].name) && colors[k].name; k++) {
- if (strequal(colors[k].name, key) || strequal(colors[k].shortname, key)) {
- if (val.type == kObjectTypeInteger) {
- *colors[k].dest = (int)val.data.integer;
- } else if (val.type == kObjectTypeString) {
- String str = val.data.string;
- // TODO(bfredl): be more fancy with "bg", "fg" etc
- if (str.size) {
- *colors[k].dest = name_to_color(str.data);
- }
- } else {
- api_set_error(err, kErrorTypeValidation,
- "'%s' must be string or integer", key);
- }
- break;
+ if (HAS_KEY(dict->link)) {
+ if (link_id) {
+ *link_id = object_to_hl_id(dict->link, "link", err);
+ if (ERROR_SET(err)) {
+ return hlattrs;
}
+ } else {
+ api_set_error(err, kErrorTypeValidation, "Invalid Key: 'link'");
}
+ }
- if (flags[j].name || colors[k].name) {
- // handled above
- } else if (link_id && strequal(key, "link")) {
- if (val.type == kObjectTypeString) {
- String str = val.data.string;
- *link_id = syn_check_group(str.data, (int)str.size);
- } else if (val.type == kObjectTypeInteger) {
- // TODO(bfredl): validate range?
- *link_id = (int)val.data.integer;
- } else {
- api_set_error(err, kErrorTypeValidation,
- "'link' must be string or integer");
- }
+ // Handle cterm attrs
+ if (dict->cterm.type == kObjectTypeDictionary) {
+ Dict(highlight_cterm) cterm[1] = { 0 };
+ if (!api_dict_to_keydict(cterm, KeyDict_highlight_cterm_get_field,
+ dict->cterm.data.dictionary, err)) {
+ return hlattrs;
}
+ cterm_mask_provided = true;
+ CHECK_FLAG(cterm, cterm_mask, bold, , HL_BOLD);
+ CHECK_FLAG(cterm, cterm_mask, standout, , HL_STANDOUT);
+ CHECK_FLAG(cterm, cterm_mask, underline, , HL_UNDERLINE);
+ CHECK_FLAG(cterm, cterm_mask, undercurl, , HL_UNDERCURL);
+ CHECK_FLAG(cterm, cterm_mask, italic, , HL_ITALIC);
+ CHECK_FLAG(cterm, cterm_mask, reverse, , HL_INVERSE);
+ CHECK_FLAG(cterm, cterm_mask, strikethrough, , HL_STRIKETHROUGH);
+ CHECK_FLAG(cterm, cterm_mask, nocombine, , HL_NOCOMBINE);
+
+ } else if (dict->cterm.type == kObjectTypeArray && dict->cterm.data.array.size == 0) {
+ // empty list from Lua API should clear all cterm attributes
+ // TODO(clason): handle via gen_api_dispatch
+ cterm_mask_provided = true;
+ } else if (HAS_KEY(dict->cterm)) {
+ api_set_error(err, kErrorTypeValidation, "'cterm' must be a Dictionary.");
+ }
+#undef CHECK_FLAG
+
+ if (HAS_KEY(dict->ctermfg)) {
+ ctermfg = object_to_color(dict->ctermfg, "ctermfg", false, err);
if (ERROR_SET(err)) {
- return hlattrs; // error set, caller should not use retval
+ return hlattrs;
+ }
+ }
+
+ if (HAS_KEY(dict->ctermbg)) {
+ ctermbg = object_to_color(dict->ctermbg, "ctermbg", false, err);
+ if (ERROR_SET(err)) {
+ return hlattrs;
}
}
@@ -914,19 +923,45 @@ HlAttrs dict2hlattrs(Dictionary dict, bool use_rgb, int *link_id, Error *err)
hlattrs.rgb_bg_color = bg;
hlattrs.rgb_fg_color = fg;
hlattrs.rgb_sp_color = sp;
- hlattrs.cterm_bg_color =
- ctermbg == -1 ? cterm_normal_bg_color : ctermbg + 1;
- hlattrs.cterm_fg_color =
- ctermfg == -1 ? cterm_normal_fg_color : ctermfg + 1;
+ hlattrs.hl_blend = blend;
+ hlattrs.cterm_bg_color = ctermbg == -1 ? 0 : ctermbg + 1;
+ hlattrs.cterm_fg_color = ctermfg == -1 ? 0 : ctermfg + 1;
hlattrs.cterm_ae_attr = cterm_mask;
} else {
hlattrs.cterm_ae_attr = cterm_mask;
- hlattrs.cterm_bg_color = bg == -1 ? cterm_normal_bg_color : bg + 1;
- hlattrs.cterm_fg_color = fg == -1 ? cterm_normal_fg_color : fg + 1;
+ hlattrs.cterm_bg_color = bg == -1 ? 0 : bg + 1;
+ hlattrs.cterm_fg_color = fg == -1 ? 0 : fg + 1;
}
return hlattrs;
}
+
+int object_to_color(Object val, char *key, bool rgb, Error *err)
+{
+ if (val.type == kObjectTypeInteger) {
+ return (int)val.data.integer;
+ } else if (val.type == kObjectTypeString) {
+ String str = val.data.string;
+ // TODO(bfredl): be more fancy with "bg", "fg" etc
+ if (!str.size || STRICMP(str.data, "NONE") == 0) {
+ return -1;
+ }
+ int color;
+ if (rgb) {
+ color = name_to_color(str.data);
+ } else {
+ color = name_to_ctermcolor(str.data);
+ }
+ if (color < 0) {
+ api_set_error(err, kErrorTypeValidation, "'%s' is not a valid color", str.data);
+ }
+ return color;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "'%s' must be string or integer", key);
+ return 0;
+ }
+}
+
Array hl_inspect(int attr)
{
Array ret = ARRAY_DICT_INIT;
diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c
index daef8db267..9ca01137cf 100644
--- a/src/nvim/if_cscope.c
+++ b/src/nvim/if_cscope.c
@@ -919,8 +919,8 @@ static int cs_find(exarg_T *eap)
/// Common code for cscope find, shared by cs_find() and ex_cstag().
-static bool cs_find_common(char *opt, char *pat, int forceit, int verbose,
- bool use_ll, char_u *cmdline)
+static bool cs_find_common(char *opt, char *pat, int forceit, int verbose, bool use_ll,
+ char_u *cmdline)
{
char *cmd;
int *nummatches;
@@ -1594,7 +1594,6 @@ static char *cs_pathcomponents(char *path)
char *s = path + strlen(path) - 1;
for (int i = 0; i < p_cspc; i++) {
while (s > path && *--s != '/') {
- continue;
}
}
if ((s > path && *s == '/')) {
@@ -1813,7 +1812,6 @@ static int cs_read_prompt(size_t i)
static void sig_handler(int s)
{
// do nothing
- return;
}
#endif
diff --git a/src/nvim/indent_c.c b/src/nvim/indent_c.c
index faa9b38cf7..7f483d02ab 100644
--- a/src/nvim/indent_c.c
+++ b/src/nvim/indent_c.c
@@ -41,9 +41,7 @@ static pos_T *ind_find_start_comment(void) // XXX
pos_T *find_start_comment(int ind_maxcomment) // XXX
{
- pos_T *pos;
- char_u *line;
- char_u *p;
+ pos_T *pos;
int64_t cur_maxcomment = ind_maxcomment;
for (;; ) {
@@ -55,11 +53,9 @@ pos_T *find_start_comment(int ind_maxcomment) // XXX
* Check if the comment start we found is inside a string.
* If it is then restrict the search to below this line and try again.
*/
- line = ml_get(pos->lnum);
- for (p = line; *p && (colnr_T)(p - line) < pos->col; ++p)
- p = skip_string(p);
- if ((colnr_T)(p - line) <= pos->col)
+ if (!is_pos_in_string(ml_get(pos->lnum), pos->col)) {
break;
+ }
cur_maxcomment = curwin->w_cursor.lnum - pos->lnum - 1;
if (cur_maxcomment <= 0) {
pos = NULL;
@@ -110,8 +106,6 @@ static pos_T *ind_find_start_CORS(linenr_T *is_raw)
static pos_T *find_start_rawstring(int ind_maxcomment) // XXX
{
pos_T *pos;
- char_u *line;
- char_u *p;
long cur_maxcomment = ind_maxcomment;
for (;;)
@@ -124,11 +118,9 @@ static pos_T *find_start_rawstring(int ind_maxcomment) // XXX
* Check if the raw string start we found is inside a string.
* If it is then restrict the search to below this line and try again.
*/
- line = ml_get(pos->lnum);
- for (p = line; *p && (colnr_T)(p - line) < pos->col; ++p)
- p = skip_string(p);
- if ((colnr_T)(p - line) <= pos->col)
- break;
+ if (!is_pos_in_string(ml_get(pos->lnum), pos->col)) {
+ break;
+ }
cur_maxcomment = curwin->w_cursor.lnum - pos->lnum - 1;
if (cur_maxcomment <= 0)
{
@@ -143,7 +135,7 @@ static pos_T *find_start_rawstring(int ind_maxcomment) // XXX
* Skip to the end of a "string" and a 'c' character.
* If there is no string or character, return argument unmodified.
*/
-static char_u *skip_string(char_u *p)
+static const char_u *skip_string(const char_u *p)
{
int i;
@@ -152,11 +144,11 @@ static char_u *skip_string(char_u *p)
*/
for (;; p++) {
if (p[0] == '\'') { // 'c' or '\n' or '\000'
- if (!p[1]) { // ' at end of line
+ if (p[1] == NUL) { // ' at end of line
break;
}
i = 2;
- if (p[1] == '\\') { // '\n' or '\000'
+ if (p[1] == '\\' && p[2] != NUL) { // '\n' or '\000'
i++;
while (ascii_isdigit(p[i - 1])) { // '\000'
i++;
@@ -178,24 +170,24 @@ static char_u *skip_string(char_u *p)
continue; // continue for another string
}
} else if (p[0] == 'R' && p[1] == '"') {
- // Raw string: R"[delim](...)[delim]"
- char_u *delim = p + 2;
- char_u *paren = vim_strchr(delim, '(');
-
- if (paren != NULL) {
- const ptrdiff_t delim_len = paren - delim;
-
- for (p += 3; *p; ++p)
- if (p[0] == ')' && STRNCMP(p + 1, delim, delim_len) == 0
- && p[delim_len + 1] == '"')
- {
- p += delim_len + 1;
- break;
- }
- if (p[0] == '"') {
- continue; // continue for another string
- }
+ // Raw string: R"[delim](...)[delim]"
+ const char_u *delim = p + 2;
+ const char_u *paren = vim_strchr(delim, '(');
+
+ if (paren != NULL) {
+ const ptrdiff_t delim_len = paren - delim;
+
+ for (p += 3; *p; p++) {
+ if (p[0] == ')' && STRNCMP(p + 1, delim, delim_len) == 0
+ && p[delim_len + 1] == '"') {
+ p += delim_len + 1;
+ break;
+ }
+ }
+ if (p[0] == '"') {
+ continue; // continue for another string
}
+ }
}
break; // no string found
}
@@ -205,6 +197,16 @@ static char_u *skip_string(char_u *p)
return p;
}
+/// @returns true if "line[col]" is inside a C string.
+int is_pos_in_string(const char_u *line, colnr_T col)
+{
+ const char_u *p;
+
+ for (p = line; *p && (colnr_T)(p - line) < col; p++) {
+ p = skip_string(p);
+ }
+ return !((colnr_T)(p - line) <= col);
+}
/*
* Functions for C-indenting.
@@ -218,7 +220,7 @@ static char_u *skip_string(char_u *p)
/*
* Return true if the string "line" starts with a word from 'cinwords'.
*/
-bool cin_is_cinword(char_u *line)
+bool cin_is_cinword(const char_u *line)
{
bool retval = false;
@@ -246,10 +248,10 @@ bool cin_is_cinword(char_u *line)
* Skip over white space and C comments within the line.
* Also skip over Perl/shell comments if desired.
*/
-static char_u *cin_skipcomment(char_u *s)
+static const char_u *cin_skipcomment(const char_u *s)
{
while (*s) {
- char_u *prev_s = s;
+ const char_u *prev_s = s;
s = skipwhite(s);
@@ -283,7 +285,7 @@ static char_u *cin_skipcomment(char_u *s)
* Return TRUE if there is no code at *s. White space and comments are
* not considered code.
*/
-static int cin_nocode(char_u *s)
+static int cin_nocode(const char_u *s)
{
return *cin_skipcomment(s) == NUL;
}
@@ -312,9 +314,9 @@ static pos_T *find_line_comment(void) // XXX
}
/// Checks if `text` starts with "key:".
-static bool cin_has_js_key(char_u *text)
+static bool cin_has_js_key(const char_u *text)
{
- char_u *s = skipwhite(text);
+ const char_u *s = skipwhite(text);
char_u quote = 0;
if (*s == '\'' || *s == '"') {
@@ -341,7 +343,7 @@ static bool cin_has_js_key(char_u *text)
/// Checks if string matches "label:"; move to character after ':' if true.
/// "*s" must point to the start of the label, if there is one.
-static bool cin_islabel_skip(char_u **s)
+static bool cin_islabel_skip(const char_u **s)
FUNC_ATTR_NONNULL_ALL
{
if (!vim_isIDc(**s)) { // need at least one ID character
@@ -361,7 +363,7 @@ static bool cin_islabel_skip(char_u **s)
// Note: curwin->w_cursor must be where we are looking for the label.
bool cin_islabel(void) // XXX
{
- char_u *s = cin_skipcomment(get_cursor_line_ptr());
+ const char_u *s = cin_skipcomment(get_cursor_line_ptr());
// Exclude "default" from labels, since it should be indented
// like a switch label. Same for C++ scope declarations.
@@ -380,8 +382,8 @@ bool cin_islabel(void) // XXX
* label.
*/
pos_T cursor_save;
- pos_T *trypos;
- char_u *line;
+ pos_T *trypos;
+ const char_u *line;
cursor_save = curwin->w_cursor;
while (curwin->w_cursor.lnum > 1) {
@@ -424,8 +426,8 @@ bool cin_islabel(void) // XXX
*/
static int cin_isinit(void)
{
- char_u *s;
- static char *skip[] = {"static", "public", "protected", "private"};
+ const char_u *s;
+ static char *skip[] = { "static", "public", "protected", "private" };
s = cin_skipcomment(get_cursor_line_ptr());
@@ -460,7 +462,7 @@ static int cin_isinit(void)
* Recognize a switch label: "case .*:" or "default:".
*/
bool cin_iscase(
- char_u *s,
+ const char_u *s,
bool strict // Allow relaxed check of case statement for JS
)
{
@@ -503,7 +505,7 @@ bool cin_iscase(
/*
* Recognize a "default" switch label.
*/
-static int cin_isdefault(char_u *s)
+static int cin_isdefault(const char_u *s)
{
return STRNCMP(s, "default", 7) == 0
&& *(s = cin_skipcomment(s + 7)) == ':'
@@ -513,7 +515,7 @@ static int cin_isdefault(char_u *s)
/*
* Recognize a "public/private/protected" scope declaration label.
*/
-bool cin_isscopedecl(char_u *s)
+bool cin_isscopedecl(const char_u *s)
{
int i;
@@ -534,13 +536,18 @@ bool cin_isscopedecl(char_u *s)
#define FIND_NAMESPACE_LIM 20
// Recognize a "namespace" scope declaration.
-static bool cin_is_cpp_namespace(char_u *s)
+static bool cin_is_cpp_namespace(const char_u *s)
{
- char_u *p;
+ const char_u *p;
bool has_name = false;
bool has_name_start = false;
s = cin_skipcomment(s);
+
+ if (STRNCMP(s, "inline", 6) == 0 && (s[6] == NUL || !vim_iswordc(s[6]))) {
+ s = cin_skipcomment(skipwhite(s + 6));
+ }
+
if (STRNCMP(s, "namespace", 9) == 0 && (s[9] == NUL || !vim_iswordc(s[9]))) {
p = cin_skipcomment(skipwhite(s + 9));
while (*p != NUL) {
@@ -576,7 +583,7 @@ static bool cin_is_cpp_namespace(char_u *s)
* case 234: a = b;
* ^
*/
-static char_u *after_label(char_u *l)
+static const char_u *after_label(const char_u *l)
{
for (; *l; ++l) {
if (*l == ':') {
@@ -603,10 +610,10 @@ static char_u *after_label(char_u *l)
*/
static int get_indent_nolabel(linenr_T lnum) // XXX
{
- char_u *l;
+ const char_u *l;
pos_T fp;
colnr_T col;
- char_u *p;
+ const char_u *p;
l = ml_get(lnum);
p = after_label(l);
@@ -625,9 +632,9 @@ static int get_indent_nolabel(linenr_T lnum) // XXX
* label: if (asdf && asdfasdf)
* ^
*/
-static int skip_label(linenr_T lnum, char_u **pp)
+static int skip_label(linenr_T lnum, const char_u **pp)
{
- char_u *l;
+ const char_u *l;
int amount;
pos_T cursor_save;
@@ -708,8 +715,8 @@ static int cin_first_id_amount(void)
*/
static int cin_get_equal_amount(linenr_T lnum)
{
- char_u *line;
- char_u *s;
+ const char_u *line;
+ const char_u *s;
colnr_T col;
pos_T fp;
@@ -747,7 +754,7 @@ static int cin_get_equal_amount(linenr_T lnum)
/*
* Recognize a preprocessor statement: Any line that starts with '#'.
*/
-static int cin_ispreproc(char_u *s)
+static int cin_ispreproc(const char_u *s)
{
if (*skipwhite(s) == '#')
return TRUE;
@@ -758,9 +765,9 @@ static int cin_ispreproc(char_u *s)
/// continuation line of a preprocessor statement. Decrease "*lnump" to the
/// start and return the line in "*pp".
/// Put the amount of indent in "*amount".
-static int cin_ispreproc_cont(char_u **pp, linenr_T *lnump, int *amount)
+static int cin_ispreproc_cont(const char_u **pp, linenr_T *lnump, int *amount)
{
- char_u *line = *pp;
+ const char_u *line = *pp;
linenr_T lnum = *lnump;
int retval = false;
int candidate_amount = *amount;
@@ -794,7 +801,7 @@ static int cin_ispreproc_cont(char_u **pp, linenr_T *lnump, int *amount)
/*
* Recognize the start of a C or C++ comment.
*/
-static int cin_iscomment(char_u *p)
+static int cin_iscomment(const char_u *p)
{
return p[0] == '/' && (p[1] == '*' || p[1] == '/');
}
@@ -802,7 +809,7 @@ static int cin_iscomment(char_u *p)
/*
* Recognize the start of a "//" comment.
*/
-static int cin_islinecomment(char_u *p)
+static int cin_islinecomment(const char_u *p)
{
return p[0] == '/' && p[1] == '/';
}
@@ -817,8 +824,8 @@ static int cin_islinecomment(char_u *p)
* both apply in order to determine initializations).
*/
static char_u
-cin_isterminated (
- char_u *s,
+cin_isterminated(
+ const char_u *s,
int incl_open, // include '{' at the end as terminator
int incl_comma // recognize a trailing comma
)
@@ -867,9 +874,9 @@ cin_isterminated (
/// lines here.
/// @param[in] first_lnum Where to start looking.
/// @param[in] min_lnum The line before which we will not be looking.
-static int cin_isfuncdecl(char_u **sp, linenr_T first_lnum, linenr_T min_lnum)
+static int cin_isfuncdecl(const char_u **sp, linenr_T first_lnum, linenr_T min_lnum)
{
- char_u *s;
+ const char_u *s;
linenr_T lnum = first_lnum;
linenr_T save_lnum = curwin->w_cursor.lnum;
int retval = false;
@@ -923,11 +930,10 @@ static int cin_isfuncdecl(char_u **sp, linenr_T first_lnum, linenr_T min_lnum)
while (*s && *s != ';' && *s != '\'' && *s != '"') {
if (*s == ')' && cin_nocode(s + 1)) {
- /* ')' at the end: may have found a match
- * Check for he previous line not to end in a backslash:
- * #if defined(x) && \
- * defined(y)
- */
+ // ')' at the end: may have found a match
+ // Check for the previous line not to end in a backslash:
+ // #if defined(x) && {backslash}
+ // defined(y)
lnum = first_lnum - 1;
s = ml_get(lnum);
if (*s == NUL || s[STRLEN(s) - 1] != '\\')
@@ -971,12 +977,12 @@ done:
return retval;
}
-static int cin_isif(char_u *p)
+static int cin_isif(const char_u *p)
{
return STRNCMP(p, "if", 2) == 0 && !vim_isIDc(p[2]);
}
-static int cin_iselse(char_u *p)
+static int cin_iselse(const char_u *p)
{
if (*p == '}') { // accept "} else"
p = cin_skipcomment(p + 1);
@@ -984,7 +990,7 @@ static int cin_iselse(char_u *p)
return STRNCMP(p, "else", 4) == 0 && !vim_isIDc(p[4]);
}
-static int cin_isdo(char_u *p)
+static int cin_isdo(const char_u *p)
{
return STRNCMP(p, "do", 2) == 0 && !vim_isIDc(p[2]);
}
@@ -994,7 +1000,7 @@ static int cin_isdo(char_u *p)
* We only accept a "while (condition) ;", with only white space between the
* ')' and ';'. The condition may be spread over several lines.
*/
-static int cin_iswhileofdo(char_u *p, linenr_T lnum) // XXX
+static int cin_iswhileofdo(const char_u *p, linenr_T lnum) // XXX
{
pos_T cursor_save;
pos_T *trypos;
@@ -1028,7 +1034,7 @@ static int cin_iswhileofdo(char_u *p, linenr_T lnum) // XXX
* Otherwise return !0 and update "*poffset" to point to the place where the
* string was found.
*/
-static int cin_is_if_for_while_before_offset(char_u *line, int *poffset)
+static int cin_is_if_for_while_before_offset(const char_u *line, int *poffset)
{
int offset = *poffset;
@@ -1072,10 +1078,10 @@ probablyFound:
*/
static int cin_iswhileofdo_end(int terminated)
{
- char_u *line;
- char_u *p;
- char_u *s;
- pos_T *trypos;
+ const char_u *line;
+ const char_u *p;
+ const char_u *s;
+ pos_T *trypos;
int i;
if (terminated != ';') { // there must be a ';' at the end
@@ -1115,7 +1121,7 @@ static int cin_iswhileofdo_end(int terminated)
return FALSE;
}
-static int cin_isbreak(char_u *p)
+static int cin_isbreak(const char_u *p)
{
return STRNCMP(p, "break", 5) == 0 && !vim_isIDc(p[5]);
}
@@ -1135,10 +1141,10 @@ static int cin_isbreak(char_u *p)
*/
static int cin_is_cpp_baseclass(cpp_baseclass_cache_T *cached) {
lpos_T *pos = &cached->lpos; // find position
- char_u *s;
+ const char_u *s;
int class_or_struct, lookfor_ctor_init, cpp_base_class;
linenr_T lnum = curwin->w_cursor.lnum;
- char_u *line = get_cursor_line_ptr();
+ const char_u *line = get_cursor_line_ptr();
if (pos->lnum <= lnum) {
return cached->found; // Use the cached result
@@ -1306,10 +1312,10 @@ static int get_baseclass_amount(int col)
* white space and comments. Skip strings and comments.
* Ignore "ignore" after "find" if it's not NULL.
*/
-static int cin_ends_in(char_u *s, char_u *find, char_u *ignore)
+static int cin_ends_in(const char_u *s, const char_u *find, const char_u *ignore)
{
- char_u *p = s;
- char_u *r;
+ const char_u *p = s;
+ const char_u *r;
int len = (int)STRLEN(find);
while (*p != NUL) {
@@ -1330,7 +1336,7 @@ static int cin_ends_in(char_u *s, char_u *find, char_u *ignore)
/*
* Return TRUE when "s" starts with "word" and then a non-ID character.
*/
-static int cin_starts_with(char_u *s, char *word)
+static int cin_starts_with(const char_u *s, const char *word)
{
int l = (int)STRLEN(word);
@@ -1338,10 +1344,10 @@ static int cin_starts_with(char_u *s, char *word)
}
/// Recognize a `extern "C"` or `extern "C++"` linkage specifications.
-static int cin_is_cpp_extern_c(char_u *s)
+static int cin_is_cpp_extern_c(const char_u *s)
{
- char_u *p;
- int has_string_literal = false;
+ const char_u *p;
+ int has_string_literal = false;
s = cin_skipcomment(s);
if (STRNCMP(s, "extern", 6) == 0 && (s[6] == NUL || !vim_iswordc(s[6]))) {
@@ -1380,9 +1386,9 @@ static int cin_is_cpp_extern_c(char_u *s)
*/
static int cin_skip2pos(pos_T *trypos)
{
- char_u *line;
- char_u *p;
- char_u *new_p;
+ const char_u *line;
+ const char_u *p;
+ const char_u *new_p;
p = line = ml_get(trypos->lnum);
while (*p && (colnr_T)(p - line) < trypos->col) {
@@ -1412,8 +1418,8 @@ static int cin_skip2pos(pos_T *trypos)
static pos_T *find_start_brace(void) // XXX
{
pos_T cursor_save;
- pos_T *trypos;
- pos_T *pos;
+ pos_T *trypos;
+ pos_T *pos;
static pos_T pos_copy;
cursor_save = curwin->w_cursor;
@@ -1428,7 +1434,7 @@ static pos_T *find_start_brace(void) // XXX
break;
}
if (pos != NULL) {
- curwin->w_cursor.lnum = pos->lnum;
+ curwin->w_cursor = *pos;
}
}
curwin->w_cursor = cursor_save;
@@ -1525,7 +1531,7 @@ static int corr_ind_maxparen(pos_T *startpos)
* Set w_cursor.col to the column number of the last unmatched ')' or '{' in
* line "l". "l" must point to the start of the line.
*/
-static int find_last_paren(char_u *l, int start, int end)
+static int find_last_paren(const char_u *l, int start, int end)
{
int i;
int retval = FALSE;
@@ -1634,8 +1640,8 @@ void parse_cino(buf_T *buf)
* itself is also unclosed. */
buf->b_ind_unclosed2 = sw;
- /* Suppress ignoring spaces from the indent of a line starting with an
- * unclosed parentheses. */
+ // Suppress ignoring spaces from the indent of a line starting with an
+ // unclosed parenthesis.
buf->b_ind_unclosed_noignore = 0;
/* If the opening paren is the last nonwhite character on the line, and
@@ -1647,11 +1653,11 @@ void parse_cino(buf_T *buf)
* an unclosed parentheses. */
buf->b_ind_unclosed_whiteok = 0;
- /* Indent a closing parentheses under the line start of the matching
- * opening parentheses. */
+ // Indent a closing parenthesis under the line start of the matching
+ // opening parenthesis.
buf->b_ind_matching_paren = 0;
- // Indent a closing parentheses under the previous line.
+ // Indent a closing parenthesis under the previous line.
buf->b_ind_paren_prev = 0;
// Extra indent for comments.
@@ -1797,8 +1803,8 @@ int get_c_indent(void)
#define BRACE_AT_START 2 // '{' is at start of line
#define BRACE_AT_END 3 // '{' is at end of line
linenr_T ourscope;
- char_u *l;
- char_u *look;
+ const char_u *l;
+ const char_u *look;
char_u terminated;
int lookfor;
#define LOOKFOR_INITIAL 0
@@ -1902,12 +1908,25 @@ int get_c_indent(void)
* If we're inside a "//" comment and there is a "//" comment in a
* previous line, lineup with that one.
*/
- if (cin_islinecomment(theline)
- && (trypos = find_line_comment()) != NULL) { // XXX
- // find how indented the line beginning the comment is
- getvcol(curwin, trypos, &col, NULL, NULL);
- amount = col;
- goto theend;
+ if (cin_islinecomment(theline)) {
+ pos_T linecomment_pos;
+
+ trypos = find_line_comment(); // XXX
+ if (trypos == NULL && curwin->w_cursor.lnum > 1) {
+ // There may be a statement before the comment, search from the end
+ // of the line for a comment start.
+ linecomment_pos.col = check_linecomment(ml_get(curwin->w_cursor.lnum - 1));
+ if (linecomment_pos.col != MAXCOL) {
+ trypos = &linecomment_pos;
+ trypos->lnum = curwin->w_cursor.lnum - 1;
+ }
+ }
+ if (trypos != NULL) {
+ // find how indented the line beginning the comment is
+ getvcol(curwin, trypos, &col, NULL, NULL);
+ amount = col;
+ goto theend;
+ }
}
/*
* If we're inside a comment and not looking at the start of the
@@ -3598,9 +3617,9 @@ laterend:
static int find_match(int lookfor, linenr_T ourscope)
{
- char_u *look;
- pos_T *theirscope;
- char_u *mightbeif;
+ const char_u *look;
+ pos_T *theirscope;
+ const char_u *mightbeif;
int elselevel;
int whilelevel;
diff --git a/src/nvim/input.c b/src/nvim/input.c
index 2f7c5c2c16..5fa9b8b343 100644
--- a/src/nvim/input.c
+++ b/src/nvim/input.c
@@ -105,7 +105,7 @@ int get_keystroke(MultiQueue *events)
// terminal code to complete.
n = os_inchar(buf + len, maxlen, len == 0 ? -1L : 100L, 0, events);
if (n > 0) {
- // Replace zero and CSI by a special key code.
+ // Replace zero and K_SPECIAL by a special key code.
n = fix_input_buffer(buf + len, n);
len += n;
waited = 0;
diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c
index abf016b832..32f2158d7b 100644
--- a/src/nvim/keymap.c
+++ b/src/nvim/keymap.c
@@ -158,7 +158,6 @@ static const struct key_name_entry {
{ ESC, "Esc" },
{ ESC, "Escape" }, // Alternative name
{ CSI, "CSI" },
- { K_CSI, "xCSI" },
{ '|', "Bar" },
{ '\\', "Bslash" },
{ K_DEL, "Del" },
@@ -964,7 +963,6 @@ char_u *replace_termcodes(const char_u *from, const size_t from_len, char_u **bu
for (i = utfc_ptr2len_len(src, (int)(end - src) + 1); i > 0; i--) {
// If the character is K_SPECIAL, replace it with K_SPECIAL
// KS_SPECIAL KE_FILLER.
- // If compiled with the GUI replace CSI with K_CSI.
if (*src == K_SPECIAL) {
result[dlen++] = K_SPECIAL;
result[dlen++] = KS_SPECIAL;
diff --git a/src/nvim/keymap.h b/src/nvim/keymap.h
index 5ff5a38614..ae2ec7835e 100644
--- a/src/nvim/keymap.h
+++ b/src/nvim/keymap.h
@@ -122,8 +122,6 @@
//
// Entries must be in the range 0x02-0x7f (see comment at K_SPECIAL).
enum key_extra {
- KE_NAME = 3, // name of this terminal entry
-
KE_S_UP = 4, // shift-up
KE_S_DOWN = 5, // shift-down
@@ -220,7 +218,7 @@ enum key_extra {
KE_KINS = 79, // keypad Insert key
KE_KDEL = 80, // keypad Delete key
- KE_CSI = 81, // CSI typed directly
+ // KE_CSI = 81, // Nvim doesn't need escaping CSI
KE_SNR = 82, // <SNR>
KE_PLUG = 83, // <Plug>
KE_CMDWIN = 84, // open command-line window from Command-line Mode
@@ -245,6 +243,7 @@ enum key_extra {
KE_MOUSEMOVE = 100, // mouse moved with no button down
// , KE_CANCEL = 101 // return from vgetc
KE_EVENT = 102, // event
+ KE_LUA = 103, // lua special key
KE_COMMAND = 104, // <Cmd> special key
};
@@ -434,7 +433,6 @@ enum key_extra {
#define K_MOUSELEFT TERMCAP2KEY(KS_EXTRA, KE_MOUSELEFT)
#define K_MOUSERIGHT TERMCAP2KEY(KS_EXTRA, KE_MOUSERIGHT)
-#define K_CSI TERMCAP2KEY(KS_EXTRA, KE_CSI)
#define K_SNR TERMCAP2KEY(KS_EXTRA, KE_SNR)
#define K_PLUG TERMCAP2KEY(KS_EXTRA, KE_PLUG)
#define K_CMDWIN TERMCAP2KEY(KS_EXTRA, KE_CMDWIN)
@@ -443,6 +441,7 @@ enum key_extra {
#define K_EVENT TERMCAP2KEY(KS_EXTRA, KE_EVENT)
#define K_COMMAND TERMCAP2KEY(KS_EXTRA, KE_COMMAND)
+#define K_LUA TERMCAP2KEY(KS_EXTRA, KE_LUA)
// Bits for modifier mask
// 0x01 cannot be used, because the modifier must be 0x02 or higher
diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c
index b6792a5a97..2b54e56df1 100644
--- a/src/nvim/lua/converter.c
+++ b/src/nvim/lua/converter.c
@@ -156,7 +156,7 @@ static LuaTableProps nlua_traverse_table(lua_State *const lstate)
&& ret.string_keys_num == 0)) {
ret.type = kObjectTypeArray;
if (tsize == 0 && lua_getmetatable(lstate, -1)) {
- nlua_pushref(lstate, nlua_empty_dict_ref);
+ nlua_pushref(lstate, nlua_global_refs->empty_dict_ref);
if (lua_rawequal(lstate, -2, -1)) {
ret.type = kObjectTypeDictionary;
}
@@ -316,7 +316,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv)
LuaRef table_ref = LUA_NOREF;
if (lua_getmetatable(lstate, -1)) {
lua_pop(lstate, 1);
- table_ref = nlua_ref(lstate, -1);
+ table_ref = nlua_ref_global(lstate, -1);
}
const LuaTableProps table_props = nlua_traverse_table(lstate);
@@ -389,7 +389,7 @@ nlua_pop_typval_table_processing_end:
}
case LUA_TFUNCTION: {
LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState));
- state->lua_callable.func_ref = nlua_ref(lstate, -1);
+ state->lua_callable.func_ref = nlua_ref_global(lstate, -1);
char_u *name = register_cfunc(&nlua_CFunction_func_call,
&nlua_CFunction_func_free,
@@ -401,7 +401,7 @@ nlua_pop_typval_table_processing_end:
}
case LUA_TUSERDATA: {
// TODO(bfredl): check mt.__call and convert to function?
- nlua_pushref(lstate, nlua_nil_ref);
+ nlua_pushref(lstate, nlua_global_refs->nil_ref);
bool is_nil = lua_rawequal(lstate, -2, -1);
lua_pop(lstate, 1);
if (is_nil) {
@@ -445,7 +445,7 @@ static bool typval_conv_special = false;
if (typval_conv_special) { \
lua_pushnil(lstate); \
} else { \
- nlua_pushref(lstate, nlua_nil_ref); \
+ nlua_pushref(lstate, nlua_global_refs->nil_ref); \
} \
} while (0)
@@ -495,7 +495,7 @@ static bool typval_conv_special = false;
nlua_create_typed_table(lstate, 0, 0, kObjectTypeDictionary); \
} else { \
lua_createtable(lstate, 0, 0); \
- nlua_pushref(lstate, nlua_empty_dict_ref); \
+ nlua_pushref(lstate, nlua_global_refs->empty_dict_ref); \
lua_setmetatable(lstate, -2); \
} \
} while (0)
@@ -617,6 +617,14 @@ bool nlua_push_typval(lua_State *lstate, typval_T *const tv, bool special)
semsg(_("E1502: Lua failed to grow stack to %i"), initial_size + 4);
return false;
}
+ if (tv->v_type == VAR_FUNC) {
+ ufunc_T *fp = find_func(tv->vval.v_string);
+ assert(fp != NULL);
+ if (fp->uf_cb == nlua_CFunction_func_call) {
+ nlua_pushref(lstate, ((LuaCFunctionState *)fp->uf_cb_state)->lua_callable.func_ref);
+ return true;
+ }
+ }
if (encode_vim_to_lua(lstate, tv, "nlua_push_typval argument") == FAIL) {
return false;
}
@@ -726,7 +734,7 @@ void nlua_push_Dictionary(lua_State *lstate, const Dictionary dict, bool special
} else {
lua_createtable(lstate, 0, (int)dict.size);
if (dict.size == 0 && !special) {
- nlua_pushref(lstate, nlua_empty_dict_ref);
+ nlua_pushref(lstate, nlua_global_refs->empty_dict_ref);
lua_setmetatable(lstate, -2);
}
}
@@ -774,7 +782,7 @@ void nlua_push_Object(lua_State *lstate, const Object obj, bool special)
if (special) {
lua_pushnil(lstate);
} else {
- nlua_pushref(lstate, nlua_nil_ref);
+ nlua_pushref(lstate, nlua_global_refs->nil_ref);
}
break;
case kObjectTypeLuaRef: {
@@ -1191,14 +1199,14 @@ Object nlua_pop_Object(lua_State *const lstate, bool ref, Error *const err)
case LUA_TFUNCTION:
if (ref) {
- *cur.obj = LUAREF_OBJ(nlua_ref(lstate, -1));
+ *cur.obj = LUAREF_OBJ(nlua_ref_global(lstate, -1));
} else {
goto type_error;
}
break;
case LUA_TUSERDATA: {
- nlua_pushref(lstate, nlua_nil_ref);
+ nlua_pushref(lstate, nlua_global_refs->nil_ref);
bool is_nil = lua_rawequal(lstate, -2, -1);
lua_pop(lstate, 1);
if (is_nil) {
@@ -1232,7 +1240,7 @@ type_error:
LuaRef nlua_pop_LuaRef(lua_State *const lstate, Error *err)
{
- LuaRef rv = nlua_ref(lstate, -1);
+ LuaRef rv = nlua_ref_global(lstate, -1);
lua_pop(lstate, 1);
return rv;
}
@@ -1242,7 +1250,12 @@ LuaRef nlua_pop_LuaRef(lua_State *const lstate, Error *err)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT \
{ \
type ret; \
- ret = (type)lua_tonumber(lstate, -1); \
+ if (lua_type(lstate, -1) != LUA_TNUMBER) { \
+ api_set_error(err, kErrorTypeValidation, "Expected Lua number"); \
+ ret = (type)-1; \
+ } else { \
+ ret = (type)lua_tonumber(lstate, -1); \
+ } \
lua_pop(lstate, 1); \
return ret; \
}
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index 107ff22913..7ac80f01f0 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -4,6 +4,7 @@
#include <lauxlib.h>
#include <lua.h>
#include <lualib.h>
+#include <tree_sitter/api.h>
#include "luv/luv.h"
#include "nvim/api/private/defs.h"
@@ -15,9 +16,11 @@
#include "nvim/change.h"
#include "nvim/cursor.h"
#include "nvim/eval/userfunc.h"
+#include "nvim/eval/typval.h"
#include "nvim/event/loop.h"
#include "nvim/event/time.h"
#include "nvim/ex_cmds2.h"
+#include "nvim/ex_docmd.h"
#include "nvim/ex_getln.h"
#include "nvim/extmark.h"
#include "nvim/func_attr.h"
@@ -43,6 +46,8 @@ static int in_fast_callback = 0;
// Initialized in nlua_init().
static lua_State *global_lstate = NULL;
+static uv_thread_t main_thread;
+
typedef struct {
Error err;
String lua_err_str;
@@ -63,11 +68,16 @@ typedef struct {
}
#if __has_feature(address_sanitizer)
-static PMap(handle_T) nlua_ref_markers = MAP_INIT;
static bool nlua_track_refs = false;
# define NLUA_TRACK_REFS
#endif
+typedef enum luv_err_type {
+ kCallback,
+ kThread,
+ kThreadCallback,
+} luv_err_t;
+
/// Convert lua error into a Vim error message
///
/// @param lstate Lua interpreter state.
@@ -120,8 +130,21 @@ static int nlua_nvim_version(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
static void nlua_luv_error_event(void **argv)
{
char *error = (char *)argv[0];
+ luv_err_t type = (luv_err_t)(intptr_t)argv[1];
msg_ext_set_kind("lua_error");
- semsg_multiline("Error executing luv callback:\n%s", error);
+ switch (type) {
+ case kCallback:
+ semsg_multiline("Error executing luv callback:\n%s", error);
+ break;
+ case kThread:
+ semsg_multiline("Error in luv thread:\n%s", error);
+ break;
+ case kThreadCallback:
+ semsg_multiline("Error in luv callback, thread:\n%s", error);
+ break;
+ default:
+ break;
+ }
xfree(error);
}
@@ -146,7 +169,7 @@ static int nlua_luv_cfpcall(lua_State *lstate, int nargs, int nresult, int flags
const char *error = lua_tostring(lstate, -1);
multiqueue_put(main_loop.events, nlua_luv_error_event,
- 1, xstrdup(error));
+ 2, xstrdup(error), (intptr_t)kCallback);
lua_pop(lstate, 1); // error message
retval = -status;
} else { // LUA_OK
@@ -160,12 +183,112 @@ static int nlua_luv_cfpcall(lua_State *lstate, int nargs, int nresult, int flags
return retval;
}
+static int nlua_luv_thread_cb_cfpcall(lua_State *lstate, int nargs, int nresult,
+ int flags)
+{
+ return nlua_luv_thread_common_cfpcall(lstate, nargs, nresult, flags, true);
+}
+
+static int nlua_luv_thread_cfpcall(lua_State *lstate, int nargs, int nresult,
+ int flags)
+ FUNC_ATTR_NONNULL_ALL
+{
+ return nlua_luv_thread_common_cfpcall(lstate, nargs, nresult, flags, false);
+}
+
+static int nlua_luv_thread_cfcpcall(lua_State *lstate, lua_CFunction func,
+ void *ud, int flags)
+ FUNC_ATTR_NONNULL_ARG(1, 2)
+{
+ lua_pushcfunction(lstate, func);
+ lua_pushlightuserdata(lstate, ud);
+ int retval = nlua_luv_thread_cfpcall(lstate, 1, 0, flags);
+ return retval;
+}
+
+static int nlua_luv_thread_common_cfpcall(lua_State *lstate, int nargs, int nresult,
+ int flags, bool is_callback)
+ FUNC_ATTR_NONNULL_ALL
+{
+ int retval;
+
+ int top = lua_gettop(lstate);
+ int status = lua_pcall(lstate, nargs, nresult, 0);
+ if (status) {
+ if (status == LUA_ERRMEM && !(flags & LUVF_CALLBACK_NOEXIT)) {
+ // Terminate this thread, as the main thread may be able to continue
+ // execution.
+ mch_errmsg(e_outofmem);
+ mch_errmsg("\n");
+ lua_close(lstate);
+#ifdef WIN32
+ ExitThread(0);
+#else
+ pthread_exit(0);
+#endif
+ }
+ const char *error = lua_tostring(lstate, -1);
+
+ loop_schedule_deferred(&main_loop,
+ event_create(nlua_luv_error_event, 2,
+ xstrdup(error),
+ is_callback
+ ? (intptr_t)kThreadCallback
+ : (intptr_t)kThread));
+ lua_pop(lstate, 1); // error message
+ retval = -status;
+ } else { // LUA_OK
+ if (nresult == LUA_MULTRET) {
+ nresult = lua_gettop(lstate) - top + nargs + 1;
+ }
+ retval = nresult;
+ }
+
+ return retval;
+}
+
+static int nlua_thr_api_nvim__get_runtime(lua_State *lstate)
+{
+ if (lua_gettop(lstate) != 3) {
+ return luaL_error(lstate, "Expected 3 arguments");
+ }
+
+ luaL_checktype(lstate, -1, LUA_TTABLE);
+ lua_getfield(lstate, -1, "is_lua");
+ if (!lua_isboolean(lstate, -1)) {
+ return luaL_error(lstate, "is_lua is not a boolean");
+ }
+ bool is_lua = lua_toboolean(lstate, -1);
+ lua_pop(lstate, 2);
+
+ luaL_checktype(lstate, -1, LUA_TBOOLEAN);
+ bool all = lua_toboolean(lstate, -1);
+ lua_pop(lstate, 1);
+
+ Error err = ERROR_INIT;
+ const Array pat = nlua_pop_Array(lstate, &err);
+ if (ERROR_SET(&err)) {
+ luaL_where(lstate, 1);
+ lua_pushstring(lstate, err.msg);
+ api_clear_error(&err);
+ lua_concat(lstate, 2);
+ return lua_error(lstate);
+ }
+
+ ArrayOf(String) ret = runtime_get_named_thread(is_lua, pat, all);
+ nlua_push_Array(lstate, ret, true);
+ api_free_array(ret);
+ api_free_array(pat);
+
+ return 1;
+}
+
static void nlua_schedule_event(void **argv)
{
LuaRef cb = (LuaRef)(ptrdiff_t)argv[0];
lua_State *const lstate = global_lstate;
nlua_pushref(lstate, cb);
- nlua_unref(lstate, cb);
+ nlua_unref_global(lstate, cb);
if (nlua_pcall(lstate, 0, 0)) {
nlua_error(lstate, _("Error executing vim.schedule lua callback: %.*s"));
}
@@ -182,7 +305,7 @@ static int nlua_schedule(lua_State *const lstate)
return lua_error(lstate);
}
- LuaRef cb = nlua_ref(lstate, 1);
+ LuaRef cb = nlua_ref_global(lstate, 1);
multiqueue_put(main_loop.events, nlua_schedule_event,
1, (void *)(ptrdiff_t)cb);
@@ -300,6 +423,152 @@ static int nlua_wait(lua_State *lstate)
return 2;
}
+static nlua_ref_state_t *nlua_new_ref_state(lua_State *lstate, bool is_thread)
+ FUNC_ATTR_NONNULL_ALL
+{
+ nlua_ref_state_t *ref_state = lua_newuserdata(lstate, sizeof(*ref_state));
+ memset(ref_state, 0, sizeof(*ref_state));
+ ref_state->nil_ref = LUA_NOREF;
+ ref_state->empty_dict_ref = LUA_NOREF;
+ if (!is_thread) {
+ nlua_global_refs = ref_state;
+ }
+ return ref_state;
+}
+
+static nlua_ref_state_t *nlua_get_ref_state(lua_State *lstate)
+ FUNC_ATTR_NONNULL_ALL
+{
+ lua_getfield(lstate, LUA_REGISTRYINDEX, "nlua.ref_state");
+ nlua_ref_state_t *ref_state = lua_touserdata(lstate, -1);
+ lua_pop(lstate, 1);
+
+ return ref_state;
+}
+
+LuaRef nlua_get_nil_ref(lua_State *lstate)
+ FUNC_ATTR_NONNULL_ALL
+{
+ nlua_ref_state_t *ref_state = nlua_get_ref_state(lstate);
+ return ref_state->nil_ref;
+}
+
+LuaRef nlua_get_empty_dict_ref(lua_State *lstate)
+ FUNC_ATTR_NONNULL_ALL
+{
+ nlua_ref_state_t *ref_state = nlua_get_ref_state(lstate);
+ return ref_state->empty_dict_ref;
+}
+
+int nlua_get_global_ref_count(void)
+{
+ return nlua_global_refs->ref_count;
+}
+
+static void nlua_common_vim_init(lua_State *lstate, bool is_thread)
+ FUNC_ATTR_NONNULL_ARG(1)
+{
+ nlua_ref_state_t *ref_state = nlua_new_ref_state(lstate, is_thread);
+ lua_setfield(lstate, LUA_REGISTRYINDEX, "nlua.ref_state");
+
+ // vim.is_thread
+ lua_pushboolean(lstate, is_thread);
+ lua_setfield(lstate, LUA_REGISTRYINDEX, "nvim.thread");
+ lua_pushcfunction(lstate, &nlua_is_thread);
+ lua_setfield(lstate, -2, "is_thread");
+
+ // vim.NIL
+ lua_newuserdata(lstate, 0);
+ lua_createtable(lstate, 0, 0);
+ lua_pushcfunction(lstate, &nlua_nil_tostring);
+ lua_setfield(lstate, -2, "__tostring");
+ lua_setmetatable(lstate, -2);
+ ref_state->nil_ref = nlua_ref(lstate, ref_state, -1);
+ lua_pushvalue(lstate, -1);
+ lua_setfield(lstate, LUA_REGISTRYINDEX, "mpack.NIL");
+ lua_setfield(lstate, -2, "NIL");
+
+ // vim._empty_dict_mt
+ lua_createtable(lstate, 0, 0);
+ lua_pushcfunction(lstate, &nlua_empty_dict_tostring);
+ lua_setfield(lstate, -2, "__tostring");
+ ref_state->empty_dict_ref = nlua_ref(lstate, ref_state, -1);
+ lua_pushvalue(lstate, -1);
+ lua_setfield(lstate, LUA_REGISTRYINDEX, "mpack.empty_dict");
+ lua_setfield(lstate, -2, "_empty_dict_mt");
+
+ // vim.loop
+ if (is_thread) {
+ luv_set_callback(lstate, nlua_luv_thread_cb_cfpcall);
+ luv_set_thread(lstate, nlua_luv_thread_cfpcall);
+ luv_set_cthread(lstate, nlua_luv_thread_cfcpcall);
+ } else {
+ luv_set_loop(lstate, &main_loop.uv);
+ luv_set_callback(lstate, nlua_luv_cfpcall);
+ }
+ luaopen_luv(lstate);
+ lua_pushvalue(lstate, -1);
+ lua_setfield(lstate, -3, "loop");
+
+ // package.loaded.luv = vim.loop
+ // otherwise luv will be reinitialized when require'luv'
+ lua_getglobal(lstate, "package");
+ lua_getfield(lstate, -1, "loaded");
+ lua_pushvalue(lstate, -3);
+ lua_setfield(lstate, -2, "luv");
+ lua_pop(lstate, 3);
+}
+
+static void nlua_common_package_init(lua_State *lstate)
+ FUNC_ATTR_NONNULL_ALL
+{
+ {
+ const char *code = (char *)&shared_module[0];
+ if (luaL_loadbuffer(lstate, code, sizeof(shared_module) - 1, "@vim/shared.lua")
+ || nlua_pcall(lstate, 0, 0)) {
+ nlua_error(lstate, _("E5106: Error while creating shared module: %.*s\n"));
+ return;
+ }
+ }
+
+ {
+ const char *code = (char *)&lua_load_package_module[0];
+ if (luaL_loadbuffer(lstate, code, sizeof(lua_load_package_module) - 1, "@vim/_load_package.lua")
+ || lua_pcall(lstate, 0, 0, 0)) {
+ nlua_error(lstate, _("E5106: Error while creating _load_package module: %.*s"));
+ return;
+ }
+ }
+
+ {
+ lua_getglobal(lstate, "package"); // [package]
+ lua_getfield(lstate, -1, "loaded"); // [package, loaded]
+
+ const char *code = (char *)&inspect_module[0];
+ if (luaL_loadbuffer(lstate, code, sizeof(inspect_module) - 1, "@vim/inspect.lua")
+ || nlua_pcall(lstate, 0, 1)) {
+ nlua_error(lstate, _("E5106: Error while creating inspect module: %.*s\n"));
+ return;
+ }
+
+ // [package, loaded, inspect]
+ lua_setfield(lstate, -2, "vim.inspect"); // [package, loaded]
+ }
+
+ {
+ const char *code = (char *)&lua_F_module[0];
+ if (luaL_loadbuffer(lstate, code, sizeof(lua_F_module) - 1, "@vim/F.lua")
+ || nlua_pcall(lstate, 0, 1)) {
+ nlua_error(lstate, _("E5106: Error while creating vim.F module: %.*s\n"));
+ return;
+ }
+ // [package, loaded, module]
+ lua_setfield(lstate, -2, "vim.F"); // [package, loaded]
+
+ lua_pop(lstate, 2); // []
+ }
+}
+
/// Initialize lua interpreter state
///
/// Called by lua interpreter itself to initialize state.
@@ -360,78 +629,38 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
lua_pushcfunction(lstate, &nlua_wait);
lua_setfield(lstate, -2, "wait");
- // vim.NIL
- lua_newuserdata(lstate, 0);
- lua_createtable(lstate, 0, 0);
- lua_pushcfunction(lstate, &nlua_nil_tostring);
- lua_setfield(lstate, -2, "__tostring");
- lua_setmetatable(lstate, -2);
- nlua_nil_ref = nlua_ref(lstate, -1);
- lua_pushvalue(lstate, -1);
- lua_setfield(lstate, LUA_REGISTRYINDEX, "mpack.NIL");
- lua_setfield(lstate, -2, "NIL");
-
- // vim._empty_dict_mt
- lua_createtable(lstate, 0, 0);
- lua_pushcfunction(lstate, &nlua_empty_dict_tostring);
- lua_setfield(lstate, -2, "__tostring");
- nlua_empty_dict_ref = nlua_ref(lstate, -1);
- lua_pushvalue(lstate, -1);
- lua_setfield(lstate, LUA_REGISTRYINDEX, "mpack.empty_dict");
- lua_setfield(lstate, -2, "_empty_dict_mt");
+ nlua_common_vim_init(lstate, false);
// internal vim._treesitter... API
nlua_add_treesitter(lstate);
- // vim.loop
- luv_set_loop(lstate, &main_loop.uv);
- luv_set_callback(lstate, nlua_luv_cfpcall);
- luaopen_luv(lstate);
- lua_pushvalue(lstate, -1);
- lua_setfield(lstate, -3, "loop");
-
- // package.loaded.luv = vim.loop
- // otherwise luv will be reinitialized when require'luv'
- lua_getglobal(lstate, "package");
- lua_getfield(lstate, -1, "loaded");
- lua_pushvalue(lstate, -3);
- lua_setfield(lstate, -2, "luv");
- lua_pop(lstate, 3);
-
- nlua_state_add_stdlib(lstate);
+ nlua_state_add_stdlib(lstate, false);
lua_setglobal(lstate, "vim");
- {
- const char *code = (char *)&shared_module[0];
- if (luaL_loadbuffer(lstate, code, sizeof(shared_module) - 1, "@vim/shared.lua")
- || nlua_pcall(lstate, 0, 0)) {
- nlua_error(lstate, _("E5106: Error while creating shared module: %.*s\n"));
- return 1;
- }
- }
+ nlua_common_package_init(lstate);
{
lua_getglobal(lstate, "package"); // [package]
lua_getfield(lstate, -1, "loaded"); // [package, loaded]
- const char *code = (char *)&inspect_module[0];
- if (luaL_loadbuffer(lstate, code, sizeof(inspect_module) - 1, "@vim/inspect.lua")
+ char *code = (char *)&lua_filetype_module[0];
+ if (luaL_loadbuffer(lstate, code, sizeof(lua_filetype_module) - 1, "@vim/filetype.lua")
|| nlua_pcall(lstate, 0, 1)) {
- nlua_error(lstate, _("E5106: Error while creating inspect module: %.*s\n"));
+ nlua_error(lstate, _("E5106: Error while creating vim.filetype module: %.*s"));
return 1;
}
- // [package, loaded, inspect]
- lua_setfield(lstate, -2, "vim.inspect"); // [package, loaded]
+ // [package, loaded, module]
+ lua_setfield(lstate, -2, "vim.filetype"); // [package, loaded]
- code = (char *)&lua_F_module[0];
- if (luaL_loadbuffer(lstate, code, sizeof(lua_F_module) - 1, "@vim/F.lua")
+ code = (char *)&lua_keymap_module[0];
+ if (luaL_loadbuffer(lstate, code, sizeof(lua_keymap_module) - 1, "@vim/keymap.lua")
|| nlua_pcall(lstate, 0, 1)) {
- nlua_error(lstate, _("E5106: Error while creating vim.F module: %.*s\n"));
+ nlua_error(lstate, _("E5106: Error while creating vim.keymap module: %.*s"));
return 1;
}
// [package, loaded, module]
- lua_setfield(lstate, -2, "vim.F"); // [package, loaded]
+ lua_setfield(lstate, -2, "vim.keymap"); // [package, loaded]
lua_pop(lstate, 2); // []
}
@@ -484,9 +713,60 @@ void nlua_init(void)
luaL_openlibs(lstate);
nlua_state_init(lstate);
+ luv_set_thread_cb(nlua_thread_acquire_vm, nlua_common_free_all_mem);
+
global_lstate = lstate;
+
+ main_thread = uv_thread_self();
}
+static lua_State *nlua_thread_acquire_vm(void)
+{
+ // If it is called from the main thread, it will attempt to rebuild the cache.
+ const uv_thread_t self = uv_thread_self();
+ if (uv_thread_equal(&main_thread, &self)) {
+ runtime_search_path_validate();
+ }
+
+ lua_State *lstate = luaL_newstate();
+
+ // Add in the lua standard libraries
+ luaL_openlibs(lstate);
+
+ // print
+ lua_pushcfunction(lstate, &nlua_print);
+ lua_setglobal(lstate, "print");
+
+ lua_pushinteger(lstate, 0);
+ lua_setfield(lstate, LUA_REGISTRYINDEX, "nlua.refcount");
+
+ // vim
+ lua_newtable(lstate);
+
+ nlua_common_vim_init(lstate, true);
+
+ nlua_state_add_stdlib(lstate, true);
+
+ lua_setglobal(lstate, "vim");
+
+ nlua_common_package_init(lstate);
+
+ lua_getglobal(lstate, "vim");
+ lua_getglobal(lstate, "package");
+ lua_getfield(lstate, -1, "loaded");
+ lua_getfield(lstate, -1, "vim.inspect");
+ lua_setfield(lstate, -4, "inspect");
+ lua_pop(lstate, 3);
+
+ lua_getglobal(lstate, "vim");
+ lua_createtable(lstate, 0, 0);
+ lua_pushcfunction(lstate, nlua_thr_api_nvim__get_runtime);
+ lua_setfield(lstate, -2, "nvim__get_runtime");
+ lua_setfield(lstate, -2, "api");
+ lua_pop(lstate, 1);
+
+ return lstate;
+}
void nlua_free_all_mem(void)
{
@@ -494,26 +774,30 @@ void nlua_free_all_mem(void)
return;
}
lua_State *lstate = global_lstate;
+ nlua_common_free_all_mem(lstate);
+}
- nlua_unref(lstate, nlua_nil_ref);
- nlua_unref(lstate, nlua_empty_dict_ref);
+static void nlua_common_free_all_mem(lua_State *lstate)
+ FUNC_ATTR_NONNULL_ALL
+{
+ nlua_ref_state_t *ref_state = nlua_get_ref_state(lstate);
+ nlua_unref(lstate, ref_state, ref_state->nil_ref);
+ nlua_unref(lstate, ref_state, ref_state->empty_dict_ref);
#ifdef NLUA_TRACK_REFS
- if (nlua_refcount) {
- fprintf(stderr, "%d lua references were leaked!", nlua_refcount);
+ if (ref_state->ref_count) {
+ fprintf(stderr, "%d lua references were leaked!", ref_state->ref_count);
}
if (nlua_track_refs) {
// in case there are leaked luarefs, leak the associated memory
// to get LeakSanitizer stacktraces on exit
- pmap_destroy(handle_T)(&nlua_ref_markers);
+ pmap_destroy(handle_T)(&ref_state->ref_markers);
}
#endif
- nlua_refcount = 0;
lua_close(lstate);
}
-
static void nlua_print_event(void **argv)
{
char *str = argv[0];
@@ -591,9 +875,18 @@ static int nlua_print(lua_State *const lstate)
#undef PRINT_ERROR
ga_append(&msg_ga, NUL);
- if (in_fast_callback) {
+ lua_getfield(lstate, LUA_REGISTRYINDEX, "nvim.thread");
+ bool is_thread = lua_toboolean(lstate, -1);
+ lua_pop(lstate, 1);
+
+ if (is_thread) {
+ loop_schedule_deferred(&main_loop,
+ event_create(nlua_print_event, 2,
+ msg_ga.ga_data,
+ (intptr_t)msg_ga.ga_len));
+ } else if (in_fast_callback) {
multiqueue_put(main_loop.events, nlua_print_event,
- 2, msg_ga.ga_data, msg_ga.ga_len);
+ 2, msg_ga.ga_data, (intptr_t)msg_ga.ga_len);
} else {
nlua_print_event((void *[]){ msg_ga.ga_data,
(void *)(intptr_t)msg_ga.ga_len });
@@ -602,10 +895,12 @@ static int nlua_print(lua_State *const lstate)
nlua_print_error:
ga_clear(&msg_ga);
+ char *buff = xmalloc(IOSIZE);
const char *fmt = _("E5114: Error while converting print argument #%i: %.*s");
- size_t len = (size_t)vim_snprintf((char *)IObuff, IOSIZE, fmt, curargidx,
+ size_t len = (size_t)vim_snprintf(buff, IOSIZE, fmt, curargidx,
(int)errmsg_len, errmsg);
- lua_pushlstring(lstate, (char *)IObuff, len);
+ lua_pushlstring(lstate, buff, len);
+ xfree(buff);
return lua_error(lstate);
}
@@ -671,7 +966,7 @@ int nlua_call(lua_State *lstate)
typval_T vim_args[MAX_FUNC_ARGS + 1];
int i = 0; // also used for freeing the variables
for (; i < nargs; i++) {
- lua_pushvalue(lstate, (int)i+2);
+ lua_pushvalue(lstate, i+2);
if (!nlua_pop_typval(lstate, &vim_args[i])) {
api_set_error(&err, kErrorTypeException,
"error converting argument %d", i+1);
@@ -736,7 +1031,7 @@ static int nlua_rpc(lua_State *lstate, bool request)
Array args = ARRAY_DICT_INIT;
for (int i = 0; i < nargs; i++) {
- lua_pushvalue(lstate, (int)i+3);
+ lua_pushvalue(lstate, i+3);
ADD(args, nlua_pop_Object(lstate, false, &err));
if (ERROR_SET(&err)) {
api_free_array(args);
@@ -795,40 +1090,53 @@ static int nlua_getenv(lua_State *lstate)
/// add the value to the registry
-LuaRef nlua_ref(lua_State *lstate, int index)
+/// The current implementation does not support calls from threads.
+LuaRef nlua_ref(lua_State *lstate, nlua_ref_state_t *ref_state, int index)
{
lua_pushvalue(lstate, index);
LuaRef ref = luaL_ref(lstate, LUA_REGISTRYINDEX);
if (ref > 0) {
- nlua_refcount++;
+ ref_state->ref_count++;
#ifdef NLUA_TRACK_REFS
if (nlua_track_refs) {
// dummy allocation to make LeakSanitizer track our luarefs
- pmap_put(handle_T)(&nlua_ref_markers, ref, xmalloc(3));
+ pmap_put(handle_T)(&ref_state->ref_markers, ref, xmalloc(3));
}
#endif
}
return ref;
}
+
+LuaRef nlua_ref_global(lua_State *lstate, int index)
+{
+ return nlua_ref(lstate, nlua_global_refs, index);
+}
+
/// remove the value from the registry
-void nlua_unref(lua_State *lstate, LuaRef ref)
+void nlua_unref(lua_State *lstate, nlua_ref_state_t *ref_state, LuaRef ref)
{
if (ref > 0) {
- nlua_refcount--;
+ ref_state->ref_count--;
#ifdef NLUA_TRACK_REFS
// NB: don't remove entry from map to track double-unref
if (nlua_track_refs) {
- xfree(pmap_get(handle_T)(&nlua_ref_markers, ref));
+ xfree(pmap_get(handle_T)(&ref_state->ref_markers, ref));
}
#endif
luaL_unref(lstate, LUA_REGISTRYINDEX, ref);
}
}
+void nlua_unref_global(lua_State *lstate, LuaRef ref)
+{
+ nlua_unref(lstate, nlua_global_refs, ref);
+}
+
+
void api_free_luaref(LuaRef ref)
{
- nlua_unref(global_lstate, ref);
+ nlua_unref_global(global_lstate, ref);
}
/// push a value referenced in the registry
@@ -850,7 +1158,7 @@ LuaRef api_new_luaref(LuaRef original_ref)
lua_State *const lstate = global_lstate;
nlua_pushref(lstate, original_ref);
- LuaRef new_ref = nlua_ref(lstate, -1);
+ LuaRef new_ref = nlua_ref_global(lstate, -1);
lua_pop(lstate, 1);
return new_ref;
}
@@ -914,6 +1222,24 @@ void nlua_typval_call(const char *str, size_t len, typval_T *const args, int arg
}
}
+void nlua_call_user_expand_func(expand_T *xp, typval_T *ret_tv)
+ FUNC_ATTR_NONNULL_ALL
+{
+ lua_State *const lstate = global_lstate;
+
+ nlua_pushref(lstate, xp->xp_luaref);
+ lua_pushstring(lstate, (char *)xp->xp_pattern);
+ lua_pushstring(lstate, (char *)xp->xp_line);
+ lua_pushinteger(lstate, xp->xp_col);
+
+ if (nlua_pcall(lstate, 3, 1)) {
+ nlua_error(lstate, _("E5108: Error executing Lua function: %.*s"));
+ return;
+ }
+
+ nlua_pop_typval(lstate, ret_tv);
+}
+
static void nlua_typval_exec(const char *lcmd, size_t lcmd_len, const char *name,
typval_T *const args, int argcount, bool special, typval_T *ret_tv)
{
@@ -1034,6 +1360,19 @@ Object nlua_exec(const String str, const Array args, Error *err)
return nlua_pop_Object(lstate, false, err);
}
+bool nlua_ref_is_function(LuaRef ref)
+{
+ lua_State *const lstate = global_lstate;
+ nlua_pushref(lstate, ref);
+
+ // TODO(tjdevries): This should probably check for callable tables as well.
+ // We should put some work maybe into simplifying how all of that works
+ bool is_function = (lua_type(lstate, -1) == LUA_TFUNCTION);
+ lua_pop(lstate, 1);
+
+ return is_function;
+}
+
/// call a LuaRef as a function (or table with __call metamethod)
///
/// @param ref the reference to call (not consumed)
@@ -1096,11 +1435,23 @@ void ex_lua(exarg_T *const eap)
FUNC_ATTR_NONNULL_ALL
{
size_t len;
- char *const code = script_get(eap, &len);
+ char *code = script_get(eap, &len);
if (eap->skip) {
xfree(code);
return;
}
+ // When =expr is used transform it to print(vim.inspect(expr))
+ if (code[0] == '=') {
+ len += sizeof("vim.pretty_print()") - sizeof("=");
+ // code_buf needs to be 1 char larger then len for null byte in the end.
+ // lua nlua_typval_exec doesn't expect null terminated string so len
+ // needs to end before null byte.
+ char *code_buf = xmallocz(len);
+ vim_snprintf(code_buf, len+1, "vim.pretty_print(%s)", code+1);
+ xfree(code);
+ code = code_buf;
+ }
+
nlua_typval_exec(code, len, ":lua", NULL, 0, false, NULL);
xfree(code);
@@ -1200,6 +1551,9 @@ void ex_luafile(exarg_T *const eap)
/// execute lua code from a file.
///
+/// Note: we call the lua global loadfile as opposed to calling luaL_loadfile
+/// in case loadfile has been overridden in the users environment.
+///
/// @param path path of the file
///
/// @return true if everything ok, false if there was an error (echoed)
@@ -1208,11 +1562,30 @@ bool nlua_exec_file(const char *path)
{
lua_State *const lstate = global_lstate;
- if (luaL_loadfile(lstate, path)) {
+ lua_getglobal(lstate, "loadfile");
+ lua_pushstring(lstate, path);
+
+ if (nlua_pcall(lstate, 1, 2)) {
+ nlua_error(lstate, _("E5111: Error calling lua: %.*s"));
+ return false;
+ }
+
+ // loadstring() returns either:
+ // 1. nil, error
+ // 2. chunk, nil
+
+ if (lua_isnil(lstate, -2)) {
+ // 1
nlua_error(lstate, _("E5112: Error while creating lua chunk: %.*s"));
+ assert(lua_isnil(lstate, -1));
+ lua_pop(lstate, 1);
return false;
}
+ // 2
+ assert(lua_isnil(lstate, -1));
+ lua_pop(lstate, 1);
+
if (nlua_pcall(lstate, 0, 0)) {
nlua_error(lstate, _("E5113: Error while calling lua chunk: %.*s"));
return false;
@@ -1227,6 +1600,12 @@ int tslua_get_language_version(lua_State *L)
return 1;
}
+int tslua_get_minimum_language_version(lua_State *L)
+{
+ lua_pushnumber(L, TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION);
+ return 1;
+}
+
static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
{
tslua_init(lstate);
@@ -1248,6 +1627,9 @@ static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
lua_pushcfunction(lstate, tslua_get_language_version);
lua_setfield(lstate, -2, "_ts_get_language_version");
+
+ lua_pushcfunction(lstate, tslua_get_minimum_language_version);
+ lua_setfield(lstate, -2, "_ts_get_minimum_language_version");
}
int nlua_expand_pat(expand_T *xp, char_u *pat, int *num_results, char_u ***results)
@@ -1319,6 +1701,13 @@ cleanup:
return ret;
}
+static int nlua_is_thread(lua_State *lstate)
+{
+ lua_getfield(lstate, LUA_REGISTRYINDEX, "nvim.thread");
+
+ return 1;
+}
+
// Required functions for lua c functions as VimL callbacks
int nlua_CFunction_func_call(int argcount, typval_T *argvars, typval_T *rettv, void *state)
@@ -1335,7 +1724,7 @@ void nlua_CFunction_func_free(void *state)
lua_State *const lstate = global_lstate;
LuaCFunctionState *funcstate = (LuaCFunctionState *)state;
- nlua_unref(lstate, funcstate->lua_callable.func_ref);
+ nlua_unref_global(lstate, funcstate->lua_callable.func_ref);
xfree(funcstate);
}
@@ -1385,7 +1774,7 @@ char_u *nlua_register_table_as_callable(typval_T *const arg)
lua_pop(lstate, 2); // [table]
LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState));
- state->lua_callable.func_ref = nlua_ref(lstate, -1);
+ state->lua_callable.func_ref = nlua_ref_global(lstate, -1);
char_u *name = register_cfunc(&nlua_CFunction_func_call,
&nlua_CFunction_func_free, state);
@@ -1432,3 +1821,123 @@ void nlua_execute_on_key(int c)
#endif
}
+// Sets the editor "script context" during Lua execution. Used by :verbose.
+// @param[out] current
+void nlua_set_sctx(sctx_T *current)
+{
+ if (p_verbose <= 0 || current->sc_sid != SID_LUA) {
+ return;
+ }
+ lua_State *const lstate = global_lstate;
+ lua_Debug *info = (lua_Debug *)xmalloc(sizeof(lua_Debug));
+
+ // Files where internal wrappers are defined so we can ignore them
+ // like vim.o/opt etc are defined in _meta.lua
+ char *ignorelist[] = {
+ "vim/_meta.lua",
+ "vim/keymap.lua",
+ };
+ int ignorelist_size = sizeof(ignorelist) / sizeof(ignorelist[0]);
+
+ for (int level = 1; true; level++) {
+ if (lua_getstack(lstate, level, info) != 1) {
+ goto cleanup;
+ }
+ if (lua_getinfo(lstate, "nSl", info) == 0) {
+ goto cleanup;
+ }
+
+ bool is_ignored = false;
+ if (info->what[0] == 'C' || info->source[0] != '@') {
+ is_ignored = true;
+ } else {
+ for (int i = 0; i < ignorelist_size; i++) {
+ if (strncmp(ignorelist[i], info->source+1, strlen(ignorelist[i])) == 0) {
+ is_ignored = true;
+ break;
+ }
+ }
+ }
+ if (is_ignored) {
+ continue;
+ }
+ break;
+ }
+ char *source_path = fix_fname(info->source + 1);
+ get_current_script_id((char_u *)source_path, current);
+ xfree(source_path);
+ current->sc_lnum = info->currentline;
+ current->sc_seq = -1;
+
+cleanup:
+ xfree(info);
+}
+
+void nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap)
+{
+ lua_State *const lstate = global_lstate;
+
+ nlua_pushref(lstate, cmd->uc_luaref);
+
+ lua_newtable(lstate);
+ lua_pushboolean(lstate, eap->forceit == 1);
+ lua_setfield(lstate, -2, "bang");
+
+ lua_pushinteger(lstate, eap->line1);
+ lua_setfield(lstate, -2, "line1");
+
+ lua_pushinteger(lstate, eap->line2);
+ lua_setfield(lstate, -2, "line2");
+
+ lua_newtable(lstate); // f-args table
+ lua_pushstring(lstate, (const char *)eap->arg);
+ lua_pushvalue(lstate, -1); // Reference for potential use on f-args
+ lua_setfield(lstate, -4, "args");
+
+ // Split args by unescaped whitespace |<f-args>| (nargs dependent)
+ if (cmd->uc_argt & EX_NOSPC) {
+ // Commands where nargs = 1 or "?" fargs is the same as args
+ lua_rawseti(lstate, -2, 1);
+ } else {
+ // Commands with more than one possible argument we split
+ lua_pop(lstate, 1); // Pop the reference of opts.args
+ int length = (int)STRLEN(eap->arg);
+ int start = 0;
+ int end = 0;
+ int i = 1;
+ bool res = true;
+ while (res) {
+ res = uc_split_args_iter(eap->arg, i, &start, &end, length);
+ lua_pushlstring(lstate, (const char *)eap->arg + start, (size_t)(end - start + 1));
+ lua_rawseti(lstate, -2, i);
+ i++;
+ }
+ }
+ lua_setfield(lstate, -2, "fargs");
+
+ lua_pushstring(lstate, (const char *)&eap->regname);
+ lua_setfield(lstate, -2, "reg");
+
+ lua_pushinteger(lstate, eap->addr_count);
+ lua_setfield(lstate, -2, "range");
+
+ if (eap->addr_count > 0) {
+ lua_pushinteger(lstate, eap->line2);
+ } else {
+ lua_pushinteger(lstate, cmd->uc_def);
+ }
+ lua_setfield(lstate, -2, "count");
+
+ // The size of this buffer is chosen empirically to be large enough to hold
+ // every possible modifier (with room to spare). If the list of possible
+ // modifiers grows this may need to be updated.
+ char buf[200] = { 0 };
+ (void)uc_mods(buf);
+ lua_pushstring(lstate, buf);
+ lua_setfield(lstate, -2, "mods");
+
+ if (nlua_pcall(lstate, 1, 0)) {
+ nlua_error(lstate, _("Error executing Lua callback: %.*s"));
+ }
+}
+
diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h
index a1f66bd02b..47ac51dadb 100644
--- a/src/nvim/lua/executor.h
+++ b/src/nvim/lua/executor.h
@@ -4,27 +4,25 @@
#include <lauxlib.h>
#include <lua.h>
+#include "nvim/assert.h"
#include "nvim/api/private/defs.h"
#include "nvim/eval/typval.h"
#include "nvim/ex_cmds_defs.h"
+#include "nvim/ex_docmd.h"
#include "nvim/func_attr.h"
#include "nvim/lua/converter.h"
// Generated by msgpack-gen.lua
void nlua_add_api_functions(lua_State *lstate) REAL_FATTR_NONNULL_ALL;
-EXTERN LuaRef nlua_nil_ref INIT(= LUA_NOREF);
-EXTERN LuaRef nlua_empty_dict_ref INIT(= LUA_NOREF);
-
-EXTERN int nlua_refcount INIT(= 0);
-
-#define set_api_error(s, err) \
- do { \
- Error *err_ = (err); \
- err_->type = kErrorTypeException; \
- err_->set = true; \
- memcpy(&err_->msg[0], s, sizeof(s)); \
- } while (0)
+typedef struct {
+ LuaRef nil_ref;
+ LuaRef empty_dict_ref;
+ int ref_count;
+#if __has_feature(address_sanitizer)
+ PMap(handle_T) ref_markers;
+#endif
+} nlua_ref_state_t;
#define NLUA_CLEAR_REF(x) \
do { \
@@ -38,4 +36,7 @@ EXTERN int nlua_refcount INIT(= 0);
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "lua/executor.h.generated.h"
#endif
+
+EXTERN nlua_ref_state_t *nlua_global_refs INIT(= NULL);
+
#endif // NVIM_LUA_EXECUTOR_H
diff --git a/src/nvim/lua/spell.c b/src/nvim/lua/spell.c
index b84124bc19..3a63f61200 100644
--- a/src/nvim/lua/spell.c
+++ b/src/nvim/lua/spell.c
@@ -1,3 +1,5 @@
+// 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
#include <lua.h>
#include <lauxlib.h>
diff --git a/src/nvim/lua/stdlib.c b/src/nvim/lua/stdlib.c
index 18a579ed0f..c2ce899a74 100644
--- a/src/nvim/lua/stdlib.c
+++ b/src/nvim/lua/stdlib.c
@@ -25,6 +25,7 @@
#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
+#include "nvim/globals.h"
#include "nvim/lua/converter.h"
#include "nvim/lua/executor.h"
#include "nvim/lua/stdlib.h"
@@ -408,6 +409,12 @@ int nlua_getvar(lua_State *lstate)
const char *name = luaL_checklstring(lstate, 3, &len);
dictitem_T *di = tv_dict_find(dict, name, (ptrdiff_t)len);
+ if (di == NULL && dict == &globvardict) { // try to autoload script
+ if (!script_autoload(name, len, false) || aborting()) {
+ return 0; // nil
+ }
+ di = tv_dict_find(dict, name, (ptrdiff_t)len);
+ }
if (di == NULL) {
return 0; // nil
}
@@ -464,43 +471,52 @@ static int nlua_stricmp(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
}
-void nlua_state_add_stdlib(lua_State *const lstate)
+void nlua_state_add_stdlib(lua_State *const lstate, bool is_thread)
{
- // 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");
- // str_utf_pos
- lua_pushcfunction(lstate, &nlua_str_utf_pos);
- lua_setfield(lstate, -2, "str_utf_pos");
- // str_utf_start
- lua_pushcfunction(lstate, &nlua_str_utf_start);
- lua_setfield(lstate, -2, "str_utf_start");
- // str_utf_end
- lua_pushcfunction(lstate, &nlua_str_utf_end);
- lua_setfield(lstate, -2, "str_utf_end");
- // regex
- lua_pushcfunction(lstate, &nlua_regex);
- lua_setfield(lstate, -2, "regex");
- luaL_newmetatable(lstate, "nvim_regex");
- luaL_register(lstate, NULL, regex_meta);
-
- lua_pushvalue(lstate, -1); // [meta, meta]
- lua_setfield(lstate, -2, "__index"); // [meta]
- lua_pop(lstate, 1); // don't use metatable now
-
- // _getvar
- lua_pushcfunction(lstate, &nlua_getvar);
- lua_setfield(lstate, -2, "_getvar");
-
- // _setvar
- lua_pushcfunction(lstate, &nlua_setvar);
- lua_setfield(lstate, -2, "_setvar");
+ if (!is_thread) {
+ // TODO(bfredl): some of basic string functions should already be
+ // (or be easy to make) threadsafe
+
+ // 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");
+ // str_utf_pos
+ lua_pushcfunction(lstate, &nlua_str_utf_pos);
+ lua_setfield(lstate, -2, "str_utf_pos");
+ // str_utf_start
+ lua_pushcfunction(lstate, &nlua_str_utf_start);
+ lua_setfield(lstate, -2, "str_utf_start");
+ // str_utf_end
+ lua_pushcfunction(lstate, &nlua_str_utf_end);
+ lua_setfield(lstate, -2, "str_utf_end");
+ // regex
+ lua_pushcfunction(lstate, &nlua_regex);
+ lua_setfield(lstate, -2, "regex");
+ luaL_newmetatable(lstate, "nvim_regex");
+ luaL_register(lstate, NULL, regex_meta);
+
+ lua_pushvalue(lstate, -1); // [meta, meta]
+ lua_setfield(lstate, -2, "__index"); // [meta]
+ lua_pop(lstate, 1); // don't use metatable now
+
+ // _getvar
+ lua_pushcfunction(lstate, &nlua_getvar);
+ lua_setfield(lstate, -2, "_getvar");
+
+ // _setvar
+ lua_pushcfunction(lstate, &nlua_setvar);
+ lua_setfield(lstate, -2, "_setvar");
+
+ // vim.spell
+ luaopen_spell(lstate);
+ lua_setfield(lstate, -2, "spell");
+ }
// vim.mpack
luaopen_mpack(lstate);
@@ -519,10 +535,7 @@ void nlua_state_add_stdlib(lua_State *const lstate)
lua_pushcfunction(lstate, &nlua_xdl_diff);
lua_setfield(lstate, -2, "diff");
- // vim.spell
- luaopen_spell(lstate);
- lua_setfield(lstate, -2, "spell");
-
+ // vim.json
lua_cjson_new(lstate);
lua_setfield(lstate, -2, "json");
}
diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c
index 60a000843f..f4067ad02f 100644
--- a/src/nvim/lua/treesitter.c
+++ b/src/nvim/lua/treesitter.c
@@ -129,6 +129,10 @@ void tslua_init(lua_State *L)
build_meta(L, TS_META_QUERY, query_meta);
build_meta(L, TS_META_QUERYCURSOR, querycursor_meta);
build_meta(L, TS_META_TREECURSOR, treecursor_meta);
+
+#ifdef NVIM_TS_HAS_SET_ALLOCATOR
+ ts_set_allocator(xmalloc, xcalloc, xrealloc, xfree);
+#endif
}
int tslua_has_language(lua_State *L)
diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua
index c1a1e7f162..c0247ad996 100644
--- a/src/nvim/lua/vim.lua
+++ b/src/nvim/lua/vim.lua
@@ -40,51 +40,11 @@ assert(vim)
vim.inspect = package.loaded['vim.inspect']
assert(vim.inspect)
-local pathtrails = {}
-vim._so_trails = {}
-for s in (package.cpath..';'):gmatch('[^;]*;') do
- s = s:sub(1, -2) -- Strip trailing semicolon
- -- Find out path patterns. pathtrail should contain something like
- -- /?.so, \?.dll. This allows not to bother determining what correct
- -- suffixes are.
- local pathtrail = s:match('[/\\][^/\\]*%?.*$')
- if pathtrail and not pathtrails[pathtrail] then
- pathtrails[pathtrail] = true
- table.insert(vim._so_trails, pathtrail)
- end
-end
-
-function vim._load_package(name)
- local basename = name:gsub('%.', '/')
- local paths = {"lua/"..basename..".lua", "lua/"..basename.."/init.lua"}
- local found = vim.api.nvim__get_runtime(paths, false, {is_lua=true})
- if #found > 0 then
- local f, err = loadfile(found[1])
- return f or error(err)
- end
+vim.filetype = package.loaded['vim.filetype']
+assert(vim.filetype)
- local so_paths = {}
- for _,trail in ipairs(vim._so_trails) do
- local path = "lua"..trail:gsub('?', basename) -- so_trails contains a leading slash
- table.insert(so_paths, path)
- end
-
- found = vim.api.nvim__get_runtime(so_paths, false, {is_lua=true})
- if #found > 0 then
- -- Making function name in Lua 5.1 (see src/loadlib.c:mkfuncname) is
- -- a) strip prefix up to and including the first dash, if any
- -- b) replace all dots by underscores
- -- c) prepend "luaopen_"
- -- So "foo-bar.baz" should result in "luaopen_bar_baz"
- local dash = name:find("-", 1, true)
- local modname = dash and name:sub(dash + 1) or name
- local f, err = package.loadlib(found[1], "luaopen_"..modname:gsub("%.", "_"))
- return f or error(err)
- end
- return nil
-end
-
-table.insert(package.loaders, 1, vim._load_package)
+vim.keymap = package.loaded['vim.keymap']
+assert(vim.keymap)
-- These are for loading runtime modules lazily since they aren't available in
-- the nvim binary as specified in executor.c
@@ -419,23 +379,43 @@ function vim.defer_fn(fn, timeout)
end
---- Notification provider
+--- Display a notification to the user.
---
---- Without a runtime, writes to :Messages
----@see :help nvim_notify
----@param msg string Content of the notification to show to the user
----@param log_level number|nil enum from vim.log.levels
----@param opts table|nil additional options (timeout, etc)
-function vim.notify(msg, log_level, opts) -- luacheck: no unused
- if log_level == vim.log.levels.ERROR then
+--- This function can be overridden by plugins to display notifications using a
+--- custom provider (such as the system notification provider). By default,
+--- writes to |:messages|.
+---
+---@param msg string Content of the notification to show to the user.
+---@param level number|nil One of the values from |vim.log.levels|.
+---@param opts table|nil Optional parameters. Unused by default.
+function vim.notify(msg, level, opts) -- luacheck: no unused args
+ if level == vim.log.levels.ERROR then
vim.api.nvim_err_writeln(msg)
- elseif log_level == vim.log.levels.WARN then
+ elseif level == vim.log.levels.WARN then
vim.api.nvim_echo({{msg, 'WarningMsg'}}, true, {})
else
vim.api.nvim_echo({{msg}}, true, {})
end
end
+do
+ local notified = {}
+
+ --- Display a notification only one time.
+ ---
+ --- Like |vim.notify()|, but subsequent calls with the same message will not
+ --- display a notification.
+ ---
+ ---@param msg string Content of the notification to show to the user.
+ ---@param level number|nil One of the values from |vim.log.levels|.
+ ---@param opts table|nil Optional parameters. Unused by default.
+ function vim.notify_once(msg, level, opts) -- luacheck: no unused args
+ if not notified[msg] then
+ vim.notify(msg, level, opts)
+ notified[msg] = true
+ end
+ end
+end
---@private
function vim.register_keystroke_callback()
@@ -663,4 +643,23 @@ vim._expand_pat_get_parts = function(lua_string)
return parts, search_index
end
+---Prints given arguments in human-readable format.
+---Example:
+---<pre>
+--- -- Print highlight group Normal and store it's contents in a variable.
+--- local hl_normal = vim.pretty_print(vim.api.nvim_get_hl_by_name("Normal", true))
+---</pre>
+---@see |vim.inspect()|
+---@return given arguments.
+function vim.pretty_print(...)
+ local objects = {}
+ for i = 1, select('#', ...) do
+ local v = select(i, ...)
+ table.insert(objects, vim.inspect(v))
+ end
+
+ print(table.concat(objects, ' '))
+ return ...
+end
+
return module
diff --git a/src/nvim/lua/xdiff.c b/src/nvim/lua/xdiff.c
index b2e971f9f3..ea7a700e1e 100644
--- a/src/nvim/lua/xdiff.c
+++ b/src/nvim/lua/xdiff.c
@@ -184,11 +184,11 @@ static NluaXdiffMode process_xdl_diff_opts(lua_State *lstate, xdemitconf_t *cfg,
if (strequal("myers", v->data.string.data)) {
// default
} else if (strequal("minimal", v->data.string.data)) {
- cfg->flags |= XDF_NEED_MINIMAL;
+ params->flags |= XDF_NEED_MINIMAL;
} else if (strequal("patience", v->data.string.data)) {
- cfg->flags |= XDF_PATIENCE_DIFF;
+ params->flags |= XDF_PATIENCE_DIFF;
} else if (strequal("histogram", v->data.string.data)) {
- cfg->flags |= XDF_HISTOGRAM_DIFF;
+ params->flags |= XDF_HISTOGRAM_DIFF;
} else {
api_set_error(err, kErrorTypeValidation, "not a valid algorithm");
goto exit_1;
diff --git a/src/nvim/macros.h b/src/nvim/macros.h
index c2b2c89abf..d5611f587b 100644
--- a/src/nvim/macros.h
+++ b/src/nvim/macros.h
@@ -105,6 +105,9 @@
#define MB_PTR_BACK(s, p) \
(p -= utf_head_off((char_u *)s, (char_u *)p - 1) + 1)
+// MB_CHAR2BYTES(): convert character to bytes and advance pointer to bytes
+#define MB_CHAR2BYTES(c, b) ((b) += utf_char2bytes((c), (b)))
+
#define RESET_BINDING(wp) \
do { \
(wp)->w_p_scb = false; \
diff --git a/src/nvim/main.c b/src/nvim/main.c
index cbd1f53727..d0b3a435c3 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -9,7 +9,7 @@
#include <string.h>
#include "nvim/ascii.h"
-#include "nvim/aucmd.h"
+#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/channel.h"
#include "nvim/charset.h"
@@ -154,10 +154,10 @@ bool event_teardown(void)
void early_init(mparm_T *paramp)
{
env_init();
- fs_init();
eval_init(); // init global variables
init_path(argv0 ? argv0 : "nvim");
init_normal_cmds(); // Init the table of Normal mode commands.
+ runtime_init();
highlight_init();
#ifdef WIN32
@@ -230,6 +230,10 @@ int main(int argc, char **argv)
// `argc` and `argv` are also copied, so that they can be changed.
init_params(&params, argc, argv);
+ // Since os_open is called during the init_startuptime, we need to call
+ // fs_init before it.
+ fs_init();
+
init_startuptime(&params);
// Need to find "--clean" before actually parsing arguments.
@@ -341,9 +345,11 @@ int main(int argc, char **argv)
init_default_autocmds();
TIME_MSG("init default autocommands");
+ bool vimrc_none = params.use_vimrc != NULL && strequal(params.use_vimrc, "NONE");
+
// Reset 'loadplugins' for "-u NONE" before "--cmd" arguments.
// Allows for setting 'loadplugins' there.
- if (params.use_vimrc != NULL && strequal(params.use_vimrc, "NONE")) {
+ if (vimrc_none) {
// When using --clean we still want to load plugins
p_lpl = params.clean;
}
@@ -351,14 +357,23 @@ int main(int argc, char **argv)
// Execute --cmd arguments.
exe_pre_commands(&params);
+ if (!vimrc_none || params.clean) {
+ // Sources ftplugin.vim and indent.vim. We do this *before* the user startup scripts to ensure
+ // ftplugins run before FileType autocommands defined in the init file (which allows those
+ // autocommands to overwrite settings from ftplugins).
+ filetype_plugin_enable();
+ }
+
// Source startup scripts.
source_startup_scripts(&params);
// If using the runtime (-u is not NONE), enable syntax & filetype plugins.
- if (params.use_vimrc == NULL || !strequal(params.use_vimrc, "NONE")) {
- // Does ":filetype plugin indent on".
+ if (!vimrc_none || params.clean) {
+ // Sources filetype.lua and filetype.vim unless the user explicitly disabled it with :filetype
+ // off.
filetype_maybe_enable();
- // Sources syntax/syntax.vim, which calls `:filetype on`.
+ // Sources syntax/syntax.vim. We do this *after* the user startup scripts so that users can
+ // disable syntax highlighting with `:syntax off` if they wish.
syn_maybe_enable();
}
@@ -1549,7 +1564,7 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd)
// When w_arg_idx is -1 remove the window (see create_windows()).
if (curwin->w_arg_idx == -1) {
- win_close(curwin, true);
+ win_close(curwin, true, false);
advance = false;
}
@@ -1561,7 +1576,7 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd)
// When w_arg_idx is -1 remove the window (see create_windows()).
if (curwin->w_arg_idx == -1) {
arg_idx++;
- win_close(curwin, true);
+ win_close(curwin, true, false);
advance = false;
continue;
}
@@ -1608,7 +1623,7 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd)
did_emsg = FALSE; // avoid hit-enter prompt
getout(1);
}
- win_close(curwin, true);
+ win_close(curwin, true, false);
advance = false;
}
if (arg_idx == GARGCOUNT - 1) {
diff --git a/src/nvim/map.c b/src/nvim/map.c
index 1d9abe3ef2..091d653046 100644
--- a/src/nvim/map.c
+++ b/src/nvim/map.c
@@ -29,8 +29,6 @@
#define uint32_t_eq kh_int_hash_equal
#define int_hash kh_int_hash_func
#define int_eq kh_int_hash_equal
-#define linenr_T_hash kh_int_hash_func
-#define linenr_T_eq kh_int_hash_equal
#define handle_T_hash kh_int_hash_func
#define handle_T_eq kh_int_hash_equal
@@ -173,15 +171,13 @@ MAP_IMPL(ptr_t, ptr_t, DEFAULT_INITIALIZER)
MAP_IMPL(uint64_t, ptr_t, DEFAULT_INITIALIZER)
MAP_IMPL(uint64_t, ssize_t, SSIZE_INITIALIZER)
MAP_IMPL(uint64_t, uint64_t, DEFAULT_INITIALIZER)
-#define EXTMARK_NS_INITIALIZER { { MAP_INIT }, 1 }
-MAP_IMPL(uint64_t, ExtmarkNs, EXTMARK_NS_INITIALIZER)
-#define EXTMARK_ITEM_INITIALIZER { 0, 0, NULL }
-MAP_IMPL(uint64_t, ExtmarkItem, EXTMARK_ITEM_INITIALIZER)
+MAP_IMPL(uint32_t, uint32_t, DEFAULT_INITIALIZER)
MAP_IMPL(handle_T, ptr_t, DEFAULT_INITIALIZER)
#define MSGPACK_HANDLER_INITIALIZER { .fn = NULL, .fast = false }
MAP_IMPL(String, MsgpackRpcRequestHandler, MSGPACK_HANDLER_INITIALIZER)
MAP_IMPL(HlEntry, int, DEFAULT_INITIALIZER)
MAP_IMPL(String, handle_T, 0)
+MAP_IMPL(String, int, DEFAULT_INITIALIZER)
MAP_IMPL(ColorKey, ColorItem, COLOR_ITEM_INITIALIZER)
diff --git a/src/nvim/map.h b/src/nvim/map.h
index dbd85a4e1f..c9c89bf2fd 100644
--- a/src/nvim/map.h
+++ b/src/nvim/map.h
@@ -40,20 +40,13 @@ MAP_DECLS(ptr_t, ptr_t)
MAP_DECLS(uint64_t, ptr_t)
MAP_DECLS(uint64_t, ssize_t)
MAP_DECLS(uint64_t, uint64_t)
+MAP_DECLS(uint32_t, uint32_t)
-// NB: this is the only way to define a struct both containing and contained
-// in a map...
-typedef struct ExtmarkNs { // For namespacing extmarks
- Map(uint64_t, uint64_t) map[1]; // For fast lookup
- uint64_t free_id; // For automatically assigning id's
-} ExtmarkNs;
-
-MAP_DECLS(uint64_t, ExtmarkNs)
-MAP_DECLS(uint64_t, ExtmarkItem)
MAP_DECLS(handle_T, ptr_t)
MAP_DECLS(String, MsgpackRpcRequestHandler)
MAP_DECLS(HlEntry, int)
MAP_DECLS(String, handle_T)
+MAP_DECLS(String, int)
MAP_DECLS(ColorKey, ColorItem)
diff --git a/src/nvim/mark.c b/src/nvim/mark.c
index 39f18b333d..8b29aa3676 100644
--- a/src/nvim/mark.c
+++ b/src/nvim/mark.c
@@ -217,8 +217,8 @@ void checkpcmark(void)
&& (equalpos(curwin->w_pcmark, curwin->w_cursor)
|| curwin->w_pcmark.lnum == 0)) {
curwin->w_pcmark = curwin->w_prev_pcmark;
- curwin->w_prev_pcmark.lnum = 0; // Show it has been checked
}
+ curwin->w_prev_pcmark.lnum = 0; // it has been checked
}
/*
diff --git a/src/nvim/marktree.c b/src/nvim/marktree.c
index 38014ab375..918db8b76c 100644
--- a/src/nvim/marktree.c
+++ b/src/nvim/marktree.c
@@ -56,12 +56,6 @@
#define T MT_BRANCH_FACTOR
#define ILEN (sizeof(mtnode_t)+(2 * T) * sizeof(void *))
-#define RIGHT_GRAVITY (((uint64_t)1) << 63)
-#define ANTIGRAVITY(id) ((id)&(RIGHT_GRAVITY-1))
-#define IS_RIGHT(id) ((id)&RIGHT_GRAVITY)
-
-#define PAIRED MARKTREE_PAIRED_FLAG
-#define END_FLAG MARKTREE_END_FLAG
#define ID_INCR (((uint64_t)1) << 2)
#define rawkey(itr) (itr->node->key[itr->i])
@@ -119,7 +113,7 @@ static int key_cmp(mtkey_t a, mtkey_t b)
}
// NB: keeping the events at the same pos sorted by id is actually not
// necessary only make sure that START is before END etc.
- return mt_generic_cmp(a.id, b.id);
+ return mt_generic_cmp(a.flags, b.flags);
}
static inline int marktree_getp_aux(const mtnode_t *x, mtkey_t k, int *r)
@@ -148,7 +142,7 @@ static inline int marktree_getp_aux(const mtnode_t *x, mtkey_t k, int *r)
static inline void refkey(MarkTree *b, mtnode_t *x, int i)
{
- pmap_put(uint64_t)(b->id2node, ANTIGRAVITY(x->key[i].id), x);
+ pmap_put(uint64_t)(b->id2node, mt_lookup_key(x->key[i]), x);
}
// put functions
@@ -221,38 +215,28 @@ static inline void marktree_putp_aux(MarkTree *b, mtnode_t *x, mtkey_t k)
}
}
-uint64_t marktree_put(MarkTree *b, int row, int col, bool right_gravity, uint8_t decor_level)
+void marktree_put(MarkTree *b, mtkey_t key, int end_row, int end_col, bool end_right)
{
- uint64_t id = (b->next_id+=ID_INCR);
- assert(decor_level < DECOR_LEVELS);
- id = id | ((uint64_t)decor_level << DECOR_OFFSET);
- uint64_t keyid = id;
- if (right_gravity) {
- // order all right gravity keys after the left ones, for effortless
- // insertion (but not deletion!)
- keyid |= RIGHT_GRAVITY;
- }
- marktree_put_key(b, row, col, keyid);
- return id;
-}
+ assert(!(key.flags & ~MT_FLAG_EXTERNAL_MASK));
+ if (end_row >= 0) {
+ key.flags |= MT_FLAG_PAIRED;
+ }
-uint64_t marktree_put_pair(MarkTree *b, int start_row, int start_col, bool start_right, int end_row,
- int end_col, bool end_right, uint8_t decor_level)
-{
- uint64_t id = (b->next_id+=ID_INCR)|PAIRED;
- assert(decor_level < DECOR_LEVELS);
- id = id | ((uint64_t)decor_level << DECOR_OFFSET);
- uint64_t start_id = id|(start_right?RIGHT_GRAVITY:0);
- uint64_t end_id = id|END_FLAG|(end_right?RIGHT_GRAVITY:0);
- marktree_put_key(b, start_row, start_col, start_id);
- marktree_put_key(b, end_row, end_col, end_id);
- return id;
+ marktree_put_key(b, key);
+
+ if (end_row >= 0) {
+ mtkey_t end_key = key;
+ end_key.flags = (uint16_t)((uint16_t)(key.flags & ~MT_FLAG_RIGHT_GRAVITY)
+ |(uint16_t)MT_FLAG_END
+ |(uint16_t)(end_right ? MT_FLAG_RIGHT_GRAVITY : 0));
+ end_key.pos = (mtpos_t){ end_row, end_col };
+ marktree_put_key(b, end_key);
+ }
}
-void marktree_put_key(MarkTree *b, int row, int col, uint64_t id)
+void marktree_put_key(MarkTree *b, mtkey_t k)
{
- mtkey_t k = { .pos = { .row = row, .col = col }, .id = id };
-
+ k.flags |= MT_FLAG_REAL; // let's be real.
if (!b->root) {
b->root = (mtnode_t *)xcalloc(1, ILEN);
b->n_nodes++;
@@ -302,7 +286,7 @@ void marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev)
mtnode_t *cur = itr->node;
int curi = itr->i;
- uint64_t id = cur->key[curi].id;
+ uint64_t id = mt_lookup_key(cur->key[curi]);
// fprintf(stderr, "\nDELET %lu\n", id);
if (itr->node->level) {
@@ -364,7 +348,7 @@ void marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev)
}
b->n_keys--;
- pmap_del(uint64_t)(b->id2node, ANTIGRAVITY(id));
+ pmap_del(uint64_t)(b->id2node, id);
// 5.
bool itr_dirty = false;
@@ -570,23 +554,29 @@ void marktree_free_node(mtnode_t *x)
}
/// NB: caller must check not pair!
-uint64_t marktree_revise(MarkTree *b, MarkTreeIter *itr, uint8_t decor_level)
+void marktree_revise(MarkTree *b, MarkTreeIter *itr, uint8_t decor_level, mtkey_t key)
{
- uint64_t old_id = rawkey(itr).id;
- pmap_del(uint64_t)(b->id2node, ANTIGRAVITY(old_id));
- uint64_t new_id = (b->next_id += ID_INCR) + ((uint64_t)decor_level << DECOR_OFFSET);
- rawkey(itr).id = new_id + (RIGHT_GRAVITY&old_id);
- refkey(b, itr->node, itr->i);
- return new_id;
+ // TODO(bfredl): clean up this mess and re-instantiate &= and |= forms
+ // once we upgrade to a non-broken version of gcc in functionaltest-lua CI
+ rawkey(itr).flags = (uint16_t)((uint16_t)rawkey(itr).flags & (uint16_t)~MT_FLAG_DECOR_MASK);
+ rawkey(itr).flags = (uint16_t)((uint16_t)rawkey(itr).flags
+ | (uint16_t)(decor_level << MT_FLAG_DECOR_OFFSET)
+ | (uint16_t)(key.flags & MT_FLAG_DECOR_MASK));
+ rawkey(itr).decor_full = key.decor_full;
+ rawkey(itr).hl_id = key.hl_id;
+ rawkey(itr).priority = key.priority;
}
void marktree_move(MarkTree *b, MarkTreeIter *itr, int row, int col)
{
- uint64_t old_id = rawkey(itr).id;
+ mtkey_t key = rawkey(itr);
// TODO(bfredl): optimize when moving a mark within a leaf without moving it
// across neighbours!
marktree_del_itr(b, itr, false);
- marktree_put_key(b, row, col, old_id);
+ key.pos = (mtpos_t){ row, col };
+
+
+ marktree_put_key(b, key);
itr->node = NULL; // itr might become invalid by put
}
@@ -602,14 +592,15 @@ bool marktree_itr_get(MarkTree *b, int row, int col, MarkTreeIter *itr)
bool marktree_itr_get_ext(MarkTree *b, mtpos_t p, MarkTreeIter *itr, bool last, bool gravity,
mtpos_t *oldbase)
{
- mtkey_t k = { .pos = p, .id = gravity ? RIGHT_GRAVITY : 0 };
- if (last && !gravity) {
- k.id = UINT64_MAX;
- }
if (b->n_keys == 0) {
itr->node = NULL;
return false;
}
+
+ mtkey_t k = { .pos = p, .flags = gravity ? MT_FLAG_RIGHT_GRAVITY : 0 };
+ if (last && !gravity) {
+ k.flags = MT_FLAG_LAST;
+ }
itr->pos = (mtpos_t){ 0, 0 };
itr->node = b->root;
itr->lvl = 0;
@@ -816,25 +807,29 @@ mtpos_t marktree_itr_pos(MarkTreeIter *itr)
return pos;
}
-mtmark_t marktree_itr_current(MarkTreeIter *itr)
+mtkey_t marktree_itr_current(MarkTreeIter *itr)
{
if (itr->node) {
- uint64_t keyid = rawkey(itr).id;
- mtpos_t pos = marktree_itr_pos(itr);
- mtmark_t mark = { .row = pos.row,
- .col = pos.col,
- .id = ANTIGRAVITY(keyid),
- .right_gravity = keyid & RIGHT_GRAVITY };
- return mark;
- }
- return (mtmark_t){ -1, -1, 0, false };
+ mtkey_t key = rawkey(itr);
+ key.pos = marktree_itr_pos(itr);
+ return key;
+ }
+ return MT_INVALID_KEY;
+}
+
+static bool itr_eq(MarkTreeIter *itr1, MarkTreeIter *itr2)
+{
+ return (&rawkey(itr1) == &rawkey(itr2));
}
-static void swap_id(uint64_t *id1, uint64_t *id2)
+static void itr_swap(MarkTreeIter *itr1, MarkTreeIter *itr2)
{
- uint64_t temp = *id1;
- *id1 = *id2;
- *id2 = temp;
+ mtkey_t key1 = rawkey(itr1);
+ mtkey_t key2 = rawkey(itr2);
+ rawkey(itr1) = key2;
+ rawkey(itr1).pos = key1.pos;
+ rawkey(itr2) = key1;
+ rawkey(itr2).pos = key2.pos;
}
bool marktree_splice(MarkTree *b, int start_line, int start_col, int old_extent_line,
@@ -865,7 +860,7 @@ bool marktree_splice(MarkTree *b, int start_line, int start_col, int old_extent_
mtpos_t ipos = marktree_itr_pos(itr);
if (!pos_leq(old_extent, ipos)
|| (old_extent.row == ipos.row && old_extent.col == ipos.col
- && !IS_RIGHT(rawkey(itr).id))) {
+ && !mt_right(rawkey(itr)))) {
marktree_itr_get_ext(b, old_extent, enditr, true, true, NULL);
assert(enditr->node);
// "assert" (itr <= enditr)
@@ -895,13 +890,13 @@ continue_same_node:
break;
}
- if (IS_RIGHT(rawkey(itr).id)) {
- while (rawkey(itr).id != rawkey(enditr).id
- && IS_RIGHT(rawkey(enditr).id)) {
+ if (mt_right(rawkey(itr))) {
+ while (!itr_eq(itr, enditr)
+ && mt_right(rawkey(enditr))) {
marktree_itr_prev(b, enditr);
}
- if (!IS_RIGHT(rawkey(enditr).id)) {
- swap_id(&rawkey(itr).id, &rawkey(enditr).id);
+ if (!mt_right(rawkey(enditr))) {
+ itr_swap(itr, enditr);
refkey(b, itr->node, itr->i);
refkey(b, enditr->node, enditr->i);
} else {
@@ -911,7 +906,7 @@ continue_same_node:
}
}
- if (rawkey(itr).id == rawkey(enditr).id) {
+ if (itr_eq(itr, enditr)) {
// actually, will be past_right after this key
past_right = true;
}
@@ -1006,13 +1001,13 @@ void marktree_move_region(MarkTree *b, int start_row, colnr_T start_col, int ext
marktree_itr_get_ext(b, start, itr, false, true, NULL);
kvec_t(mtkey_t) saved = KV_INITIAL_VALUE;
while (itr->node) {
- mtpos_t pos = marktree_itr_pos(itr);
- if (!pos_leq(pos, end) || (pos.row == end.row && pos.col == end.col
- && rawkey(itr).id & RIGHT_GRAVITY)) {
+ mtkey_t k = marktree_itr_current(itr);
+ if (!pos_leq(k.pos, end) || (k.pos.row == end.row && k.pos.col == end.col
+ && mt_right(k))) {
break;
}
- relative(start, &pos);
- kv_push(saved, ((mtkey_t){ .pos = pos, .id = rawkey(itr).id }));
+ relative(start, &k.pos);
+ kv_push(saved, k);
marktree_del_itr(b, itr, false);
}
@@ -1024,30 +1019,36 @@ void marktree_move_region(MarkTree *b, int start_row, colnr_T start_col, int ext
for (size_t i = 0; i < kv_size(saved); i++) {
mtkey_t item = kv_A(saved, i);
unrelative(new, &item.pos);
- marktree_put_key(b, item.pos.row, item.pos.col, item.id);
+ marktree_put_key(b, item);
}
kv_destroy(saved);
}
/// @param itr OPTIONAL. set itr to pos.
-mtpos_t marktree_lookup(MarkTree *b, uint64_t id, MarkTreeIter *itr)
+mtkey_t marktree_lookup_ns(MarkTree *b, uint32_t ns, uint32_t id, bool end, MarkTreeIter *itr)
+{
+ return marktree_lookup(b, mt_lookup_id(ns, id, end), itr);
+}
+
+/// @param itr OPTIONAL. set itr to pos.
+mtkey_t marktree_lookup(MarkTree *b, uint64_t id, MarkTreeIter *itr)
{
mtnode_t *n = pmap_get(uint64_t)(b->id2node, id);
if (n == NULL) {
if (itr) {
itr->node = NULL;
}
- return (mtpos_t){ -1, -1 };
+ return MT_INVALID_KEY;
}
int i = 0;
for (i = 0; i < n->n; i++) {
- if (ANTIGRAVITY(n->key[i].id) == id) {
+ if (mt_lookup_key(n->key[i]) == id) {
goto found;
}
}
abort();
found: {}
- mtpos_t pos = n->key[i].pos;
+ mtkey_t key = n->key[i];
if (itr) {
itr->i = i;
itr->node = n;
@@ -1066,14 +1067,28 @@ found_node:
itr->s[b->root->level-p->level].i = i;
}
if (i > 0) {
- unrelative(p->key[i-1].pos, &pos);
+ unrelative(p->key[i-1].pos, &key.pos);
}
n = p;
}
if (itr) {
marktree_itr_fix_pos(b, itr);
}
- return pos;
+ return key;
+}
+
+mtpos_t marktree_get_altpos(MarkTree *b, mtkey_t mark, MarkTreeIter *itr)
+{
+ return marktree_get_alt(b, mark, itr).pos;
+}
+
+mtkey_t marktree_get_alt(MarkTree *b, mtkey_t mark, MarkTreeIter *itr)
+{
+ mtkey_t end = MT_INVALID_KEY;
+ if (mt_paired(mark)) {
+ end = marktree_lookup_ns(b, mark.ns, mark.id, !mt_end(mark), itr);
+ }
+ return end;
}
static void marktree_itr_fix_pos(MarkTree *b, MarkTreeIter *itr)
@@ -1092,6 +1107,20 @@ static void marktree_itr_fix_pos(MarkTree *b, MarkTreeIter *itr)
assert(x == itr->node);
}
+// for unit test
+void marktree_put_test(MarkTree *b, uint32_t id, int row, int col, bool right_gravity)
+{
+ mtkey_t key = { { row, col }, UINT32_MAX, id, 0,
+ mt_flags(right_gravity, 0), 0, NULL };
+ marktree_put(b, key, -1, -1, false);
+}
+
+// for unit test
+bool mt_right_test(mtkey_t key)
+{
+ return mt_right(key);
+}
+
void marktree_check(MarkTree *b)
{
#ifndef NDEBUG
@@ -1134,11 +1163,11 @@ static size_t check_node(MarkTree *b, mtnode_t *x, mtpos_t *last, bool *last_rig
}
assert(pos_leq(*last, x->key[i].pos));
if (last->row == x->key[i].pos.row && last->col == x->key[i].pos.col) {
- assert(!*last_right || IS_RIGHT(x->key[i].id));
+ assert(!*last_right || mt_right(x->key[i]));
}
- *last_right = IS_RIGHT(x->key[i].id);
+ *last_right = mt_right(x->key[i]);
assert(x->key[i].pos.col >= 0);
- assert(pmap_get(uint64_t)(b->id2node, ANTIGRAVITY(x->key[i].id)) == x);
+ assert(pmap_get(uint64_t)(b->id2node, mt_lookup_key(x->key[i])) == x);
}
if (x->level) {
diff --git a/src/nvim/marktree.h b/src/nvim/marktree.h
index a1dcdf5164..30f5aacebc 100644
--- a/src/nvim/marktree.h
+++ b/src/nvim/marktree.h
@@ -2,9 +2,12 @@
#define NVIM_MARKTREE_H
#include <stdint.h>
+#include <assert.h>
+#include "nvim/assert.h"
#include "nvim/garray.h"
#include "nvim/map.h"
+#include "nvim/types.h"
#include "nvim/pos.h"
#define MT_MAX_DEPTH 20
@@ -15,13 +18,6 @@ typedef struct {
int32_t col;
} mtpos_t;
-typedef struct {
- int32_t row;
- int32_t col;
- uint64_t id;
- bool right_gravity;
-} mtmark_t;
-
typedef struct mtnode_s mtnode_t;
typedef struct {
int oldcol;
@@ -39,12 +35,75 @@ typedef struct {
// Internal storage
//
-// NB: actual marks have id > 0, so we can use (row,col,0) pseudo-key for
+// NB: actual marks have flags > 0, so we can use (row,col,0) pseudo-key for
// "space before (row,col)"
typedef struct {
mtpos_t pos;
- uint64_t id;
+ uint32_t ns;
+ uint32_t id;
+ int32_t hl_id;
+ uint16_t flags;
+ uint16_t priority;
+ Decoration *decor_full;
} mtkey_t;
+#define MT_INVALID_KEY (mtkey_t) { { -1, -1 }, 0, 0, 0, 0, 0, NULL }
+
+#define MT_FLAG_REAL (((uint16_t)1) << 0)
+#define MT_FLAG_END (((uint16_t)1) << 1)
+#define MT_FLAG_PAIRED (((uint16_t)1) << 2)
+#define MT_FLAG_HL_EOL (((uint16_t)1) << 3)
+
+#define DECOR_LEVELS 4
+#define MT_FLAG_DECOR_OFFSET 4
+#define MT_FLAG_DECOR_MASK (((uint16_t)(DECOR_LEVELS-1)) << MT_FLAG_DECOR_OFFSET)
+
+// next flag is (((uint16_t)1) << 6)
+
+// These _must_ be last to preserve ordering of marks
+#define MT_FLAG_RIGHT_GRAVITY (((uint16_t)1) << 14)
+#define MT_FLAG_LAST (((uint16_t)1) << 15)
+
+#define MT_FLAG_EXTERNAL_MASK (MT_FLAG_DECOR_MASK | MT_FLAG_RIGHT_GRAVITY | MT_FLAG_HL_EOL)
+
+#define MARKTREE_END_FLAG (((uint64_t)1) << 63)
+static inline uint64_t mt_lookup_id(uint32_t ns, uint32_t id, bool enda)
+{
+ return (uint64_t)ns << 32 | id | (enda?MARKTREE_END_FLAG:0);
+}
+#undef MARKTREE_END_FLAG
+
+static inline uint64_t mt_lookup_key(mtkey_t key)
+{
+ return mt_lookup_id(key.ns, key.id, key.flags & MT_FLAG_END);
+}
+
+static inline bool mt_paired(mtkey_t key)
+{
+ return key.flags & MT_FLAG_PAIRED;
+}
+
+static inline bool mt_end(mtkey_t key)
+{
+ return key.flags & MT_FLAG_END;
+}
+
+static inline bool mt_right(mtkey_t key)
+{
+ return key.flags & MT_FLAG_RIGHT_GRAVITY;
+}
+
+static inline uint8_t marktree_decor_level(mtkey_t key)
+{
+ return (uint8_t)((key.flags&MT_FLAG_DECOR_MASK) >> MT_FLAG_DECOR_OFFSET);
+}
+
+static inline uint16_t mt_flags(bool right_gravity, uint8_t decor_level)
+{
+ assert(decor_level < DECOR_LEVELS);
+ return (uint16_t)((right_gravity ? MT_FLAG_RIGHT_GRAVITY : 0)
+ | (decor_level << MT_FLAG_DECOR_OFFSET));
+}
+
struct mtnode_s {
int32_t n;
@@ -61,7 +120,6 @@ struct mtnode_s {
typedef struct {
mtnode_t *root;
size_t n_keys, n_nodes;
- uint64_t next_id;
// TODO(bfredl): the pointer to node could be part of the larger
// Map(uint64_t, ExtmarkItem) essentially;
PMap(uint64_t) id2node[1];
@@ -72,16 +130,4 @@ typedef struct {
# include "marktree.h.generated.h"
#endif
-#define MARKTREE_PAIRED_FLAG (((uint64_t)1) << 1)
-#define MARKTREE_END_FLAG (((uint64_t)1) << 0)
-
-#define DECOR_LEVELS 4
-#define DECOR_OFFSET 61
-#define DECOR_MASK (((uint64_t)(DECOR_LEVELS-1)) << DECOR_OFFSET)
-
-static inline uint8_t marktree_decor_level(uint64_t id)
-{
- return (uint8_t)((id&DECOR_MASK) >> DECOR_OFFSET);
-}
-
#endif // NVIM_MARKTREE_H
diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c
index 5eb209a6f6..f634c7dda8 100644
--- a/src/nvim/mbyte.c
+++ b/src/nvim/mbyte.c
@@ -1067,7 +1067,7 @@ bool utf_printable(int c)
static struct interval nonprint[] =
{
{ 0x070f, 0x070f }, { 0x180b, 0x180e }, { 0x200b, 0x200f }, { 0x202a, 0x202e },
- { 0x206a, 0x206f }, { 0xd800, 0xdfff }, { 0xfeff, 0xfeff }, { 0xfff9, 0xfffb },
+ { 0x2060, 0x206f }, { 0xd800, 0xdfff }, { 0xfeff, 0xfeff }, { 0xfff9, 0xfffb },
{ 0xfffe, 0xffff }
};
@@ -1317,6 +1317,12 @@ bool mb_isupper(int a)
return mb_tolower(a) != a;
}
+bool mb_isalpha(int a)
+ FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ return mb_islower(a) || mb_isupper(a);
+}
+
static int utf_strnicmp(const char_u *s1, const char_u *s2, size_t n1, size_t n2)
{
int c1, c2, cdiff;
@@ -1821,12 +1827,10 @@ void mb_copy_char(const char_u **const fp, char_u **const tp)
*fp += l;
}
-/*
- * Return the offset from "p" to the first byte of a character. When "p" is
- * at the start of a character 0 is returned, otherwise the offset to the next
- * character. Can start anywhere in a stream of bytes.
- */
-int mb_off_next(char_u *base, char_u *p)
+/// Return the offset from "p" to the first byte of a character. When "p" is
+/// at the start of a character 0 is returned, otherwise the offset to the next
+/// character. Can start anywhere in a stream of bytes.
+int mb_off_next(const char_u *base, const char_u *p)
{
int i;
int j;
@@ -1854,7 +1858,7 @@ int mb_off_next(char_u *base, char_u *p)
/// Return the offset from "p" to the last byte of the character it points
/// into. Can start anywhere in a stream of bytes.
/// Composing characters are not included.
-int mb_tail_off(char_u *base, char_u *p)
+int mb_tail_off(const char_u *base, const char_u *p)
{
int i;
int j;
@@ -1882,12 +1886,13 @@ int mb_tail_off(char_u *base, char_u *p)
/// Return the offset from "p" to the first byte of the character it points
/// into. Can start anywhere in a stream of bytes.
+/// Unlike utf_head_off() this doesn't include composing characters and returns a negative value.
///
/// @param[in] base Pointer to start of string
/// @param[in] p Pointer to byte for which to return the offset to the previous codepoint
//
/// @return 0 if invalid sequence, else offset to previous codepoint
-int mb_head_off(char_u *base, char_u *p)
+int mb_head_off(const char_u *base, const char_u *p)
{
int i;
int j;
@@ -2037,13 +2042,11 @@ char_u *mb_prevptr(char_u *line, char_u *p)
return p;
}
-/*
- * Return the character length of "str". Each multi-byte character (with
- * following composing characters) counts as one.
- */
-int mb_charlen(char_u *str)
+/// Return the character length of "str". Each multi-byte character (with
+/// following composing characters) counts as one.
+int mb_charlen(const char_u *str)
{
- char_u *p = str;
+ const char_u *p = str;
int count;
if (p == NULL) {
@@ -2057,12 +2060,10 @@ int mb_charlen(char_u *str)
return count;
}
-/*
- * Like mb_charlen() but for a string with specified length.
- */
-int mb_charlen_len(char_u *str, int len)
+/// Like mb_charlen() but for a string with specified length.
+int mb_charlen_len(const char_u *str, int len)
{
- char_u *p = str;
+ const char_u *p = str;
int count;
for (count = 0; *p != NUL && p < str + len; count++) {
@@ -2089,8 +2090,7 @@ const char *mb_unescape(const char **const pp)
size_t buf_idx = 0;
uint8_t *str = (uint8_t *)(*pp);
- // Must translate K_SPECIAL KS_SPECIAL KE_FILLER to K_SPECIAL and CSI
- // KS_EXTRA KE_CSI to CSI.
+ // Must translate K_SPECIAL KS_SPECIAL KE_FILLER to K_SPECIAL.
// Maximum length of a utf-8 character is 4 bytes.
for (size_t str_idx = 0; str[str_idx] != NUL && buf_idx < 4; str_idx++) {
if (str[str_idx] == K_SPECIAL
@@ -2098,11 +2098,6 @@ const char *mb_unescape(const char **const pp)
&& str[str_idx + 2] == KE_FILLER) {
buf[buf_idx++] = (char)K_SPECIAL;
str_idx += 2;
- } else if ((str[str_idx] == K_SPECIAL)
- && str[str_idx + 1] == KS_EXTRA
- && str[str_idx + 2] == KE_CSI) {
- buf[buf_idx++] = (char)CSI;
- str_idx += 2;
} else if (str[str_idx] == K_SPECIAL) {
break; // A special key can't be a multibyte char.
} else {
@@ -2207,11 +2202,9 @@ char_u *enc_canonize(char_u *enc) FUNC_ATTR_NONNULL_RET
return r;
}
-/*
- * Search for an encoding alias of "name".
- * Returns -1 when not found.
- */
-static int enc_alias_search(char_u *name)
+/// Search for an encoding alias of "name".
+/// Returns -1 when not found.
+static int enc_alias_search(const char_u *name)
{
int i;
diff --git a/src/nvim/memfile.c b/src/nvim/memfile.c
index 3397296b3a..2a72d1e6a0 100644
--- a/src/nvim/memfile.c
+++ b/src/nvim/memfile.c
@@ -247,7 +247,7 @@ bhdr_T *mf_new(memfile_T *mfp, bool negative, unsigned page_count)
} else { // need to allocate memory for this block
// If the number of pages matches use the bhdr_T from the free list and
// allocate the data.
- void *p = xmalloc(mfp->mf_page_size * page_count);
+ void *p = xmalloc((size_t)mfp->mf_page_size * page_count);
hp = mf_rem_free(mfp);
hp->bh_data = p;
}
@@ -269,7 +269,7 @@ bhdr_T *mf_new(memfile_T *mfp, bool negative, unsigned page_count)
// Init the data to all zero, to avoid reading uninitialized data.
// This also avoids that the passwd file ends up in the swap file!
- (void)memset(hp->bh_data, 0, mfp->mf_page_size * page_count);
+ (void)memset(hp->bh_data, 0, (size_t)mfp->mf_page_size * page_count);
return hp;
}
@@ -528,7 +528,7 @@ bool mf_release_all(void)
static bhdr_T *mf_alloc_bhdr(memfile_T *mfp, unsigned page_count)
{
bhdr_T *hp = xmalloc(sizeof(bhdr_T));
- hp->bh_data = xmalloc(mfp->mf_page_size * page_count);
+ hp->bh_data = xmalloc((size_t)mfp->mf_page_size * page_count);
hp->bh_page_count = page_count;
return hp;
}
diff --git a/src/nvim/memline.c b/src/nvim/memline.c
index 08521c0dc3..004ef36b36 100644
--- a/src/nvim/memline.c
+++ b/src/nvim/memline.c
@@ -704,11 +704,14 @@ static void set_b0_fname(ZERO_BL *b0p, buf_T *buf)
long_to_char((long)os_fileinfo_inode(&file_info), b0p->b0_ino);
buf_store_file_info(buf, &file_info);
buf->b_mtime_read = buf->b_mtime;
+ buf->b_mtime_read_ns = buf->b_mtime_ns;
} else {
long_to_char(0L, b0p->b0_mtime);
long_to_char(0L, b0p->b0_ino);
buf->b_mtime = 0;
+ buf->b_mtime_ns = 0;
buf->b_mtime_read = 0;
+ buf->b_mtime_read_ns = 0;
buf->b_orig_size = 0;
buf->b_orig_mode = 0;
}
@@ -1032,9 +1035,9 @@ void ml_recover(bool checkext)
line_count = 0;
idx = 0; // start with first index in block 1
error = 0;
- buf->b_ml.ml_stack_top = 0;
+ buf->b_ml.ml_stack_top = 0; // -V1048
buf->b_ml.ml_stack = NULL;
- buf->b_ml.ml_stack_size = 0; // no stack yet
+ buf->b_ml.ml_stack_size = 0; // -V1048
if (curbuf->b_ffname == NULL) {
cannot_open = true;
@@ -1720,6 +1723,7 @@ void ml_sync_all(int check_file, int check_char, bool do_fsync)
FileInfo file_info;
if (!os_fileinfo((char *)buf->b_ffname, &file_info)
|| file_info.stat.st_mtim.tv_sec != buf->b_mtime_read
+ || file_info.stat.st_mtim.tv_nsec != buf->b_mtime_read_ns
|| os_fileinfo_size(&file_info) != buf->b_orig_size) {
ml_preserve(buf, false, do_fsync);
did_check_timestamps = false;
@@ -4139,7 +4143,7 @@ long ml_find_line_or_offset(buf_T *buf, linenr_T lnum, long *offp, bool no_ff)
|| (offset != 0
&& offset > size +
buf->b_ml.ml_chunksize[curix].mlcs_totalsize
- + ffdos * buf->b_ml.ml_chunksize[curix].mlcs_numlines))) {
+ + (long)ffdos * buf->b_ml.ml_chunksize[curix].mlcs_numlines))) {
curline += buf->b_ml.ml_chunksize[curix].mlcs_numlines;
size += buf->b_ml.ml_chunksize[curix].mlcs_totalsize;
if (offset && ffdos) {
diff --git a/src/nvim/menu.c b/src/nvim/menu.c
index ac4d52c392..0db9d69a7e 100644
--- a/src/nvim/menu.c
+++ b/src/nvim/menu.c
@@ -361,7 +361,7 @@ static int add_menu_path(const char_u *const menu_path, vimmenu_T *menuarg,
goto erret;
}
- // Not already there, so lets add it
+ // Not already there, so let's add it
menu = xcalloc(1, sizeof(vimmenu_T));
menu->modes = modes;
diff --git a/src/nvim/message.c b/src/nvim/message.c
index befca8c76b..b39450cdc6 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -262,7 +262,6 @@ void msg_multiline_attr(const char *s, int attr, bool check_int, bool *need_clea
if (*s != NUL) {
msg_outtrans_attr((char_u *)s, attr);
}
- return;
}
@@ -329,7 +328,7 @@ bool msg_attr_keep(const char *s, int attr, bool keep, bool multiline)
}
retval = msg_end();
- if (keep && retval && vim_strsize((char_u *)s) < (int)(Rows - cmdline_row - 1)
+ if (keep && retval && vim_strsize((char_u *)s) < (Rows - cmdline_row - 1)
* Columns + sc_col) {
set_keep_msg((char *)s, 0);
}
@@ -356,10 +355,10 @@ char_u *msg_strtrunc(char_u *s, int force)
len = vim_strsize(s);
if (msg_scrolled != 0) {
// Use all the columns.
- room = (int)(Rows - msg_row) * Columns - 1;
+ room = (Rows - msg_row) * Columns - 1;
} else {
// Use up to 'showcmd' column.
- room = (int)(Rows - msg_row - 1) * Columns + sc_col - 1;
+ room = (Rows - msg_row - 1) * Columns + sc_col - 1;
}
if (len > room && room > 0) {
// may have up to 18 bytes per cell (6 per char, up to two
@@ -873,7 +872,7 @@ char_u *msg_may_trunc(bool force, char_u *s)
{
int room;
- room = (int)(Rows - cmdline_row - 1) * Columns + sc_col - 1;
+ room = (Rows - cmdline_row - 1) * Columns + sc_col - 1;
if ((force || (shortmess(SHM_TRUNC) && !exmode_active))
&& (int)STRLEN(s) - room > 0) {
int size = vim_strsize(s);
@@ -1095,6 +1094,10 @@ void wait_return(int redraw)
return;
}
+ if (headless_mode && !ui_active()) {
+ return;
+ }
+
/*
* When inside vgetc(), we can't wait for a typed character at all.
* With the global command (and some others) we only need one return at
@@ -1215,7 +1218,7 @@ void wait_return(int redraw)
} else if (vim_strchr((char_u *)"\r\n ", c) == NULL && c != Ctrl_C) {
// Put the character back in the typeahead buffer. Don't use the
// stuff buffer, because lmaps wouldn't work.
- ins_char_typebuf(c);
+ ins_char_typebuf(c, mod_mask);
do_redraw = true; // need a redraw even though there is
// typeahead
}
@@ -1269,7 +1272,7 @@ static void hit_return_msg(void)
{
int save_p_more = p_more;
- p_more = FALSE; // don't want see this message when scrolling back
+ p_more = false; // don't want to see this message when scrolling back
if (msg_didout) { // start on a new line
msg_putchar('\n');
}
@@ -2647,6 +2650,17 @@ static void msg_puts_printf(const char *str, const ptrdiff_t maxlen)
char buf[7];
char *p;
+ if (on_print.type != kCallbackNone) {
+ typval_T argv[1];
+ argv[0].v_type = VAR_STRING;
+ argv[0].v_lock = VAR_UNLOCKED;
+ argv[0].vval.v_string = (char_u *)str;
+ typval_T rettv = TV_INITIAL_VALUE;
+ callback_call(&on_print, 1, argv, &rettv);
+ tv_clear(&rettv);
+ return;
+ }
+
while ((maxlen < 0 || s - str < maxlen) && *s != NUL) {
int len = utf_ptr2len((const char_u *)s);
if (!(silent_mode && p_verbose == 0)) {
@@ -3450,6 +3464,7 @@ void msg_advance(int col)
///
/// @param textfiel IObuff for inputdialog(), NULL otherwise
/// @param ex_cmd when TRUE pressing : accepts default and starts Ex command
+/// @returns 0 if cancelled, otherwise the nth button (1-indexed).
int do_dialog(int type, char_u *title, char_u *message, char_u *buttons, int dfltbutton,
char_u *textfield, int ex_cmd)
{
@@ -3497,7 +3512,7 @@ int do_dialog(int type, char_u *title, char_u *message, char_u *buttons, int dfl
}
if (c == ':' && ex_cmd) {
retval = dfltbutton;
- ins_char_typebuf(':');
+ ins_char_typebuf(':', 0);
break;
}
diff --git a/src/nvim/move.c b/src/nvim/move.c
index 15ba6645f5..27cc2b341c 100644
--- a/src/nvim/move.c
+++ b/src/nvim/move.c
@@ -346,10 +346,10 @@ void update_topline(win_T *wp)
*/
void update_topline_win(win_T *win)
{
- win_T *save_curwin;
- switch_win(&save_curwin, NULL, win, NULL, true);
+ switchwin_T switchwin;
+ switch_win(&switchwin, win, NULL, true);
update_topline(curwin);
- restore_win(save_curwin, NULL, true);
+ restore_win(&switchwin, true);
}
/*
@@ -909,7 +909,7 @@ void curs_columns(win_T *wp, int may_scroll)
}
wp->w_skipcol = n * width;
} else if (extra == 1) {
- // less then 'scrolloff' lines above, decrease skipcol
+ // less than 'scrolloff' lines above, decrease skipcol
assert(so <= INT_MAX);
extra = (wp->w_skipcol + (int)so * width - wp->w_virtcol
+ width - 1) / width;
@@ -920,7 +920,7 @@ void curs_columns(win_T *wp, int may_scroll)
wp->w_skipcol -= extra * width;
}
} else if (extra == 2) {
- // less then 'scrolloff' lines below, increase skipcol
+ // less than 'scrolloff' lines below, increase skipcol
endcol = (n - wp->w_height_inner + 1) * width;
while (endcol > wp->w_virtcol) {
endcol -= width;
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 3246596f16..7fe6469527 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -164,7 +164,7 @@ static const struct nv_cmd {
{ Ctrl_O, nv_ctrlo, 0, 0 },
{ Ctrl_P, nv_up, NV_STS, false },
{ Ctrl_Q, nv_visual, 0, false },
- { Ctrl_R, nv_redo, 0, 0 },
+ { Ctrl_R, nv_redo_or_register, 0, 0 },
{ Ctrl_S, nv_ignore, 0, 0 },
{ Ctrl_T, nv_tagpop, NV_NCW, 0 },
{ Ctrl_U, nv_halfpage, 0, 0 },
@@ -334,6 +334,7 @@ static const struct nv_cmd {
{ K_SELECT, nv_select, 0, 0 },
{ K_EVENT, nv_event, NV_KEEPREG, 0 },
{ K_COMMAND, nv_colon, 0, 0 },
+ { K_LUA, nv_colon, 0, 0 },
};
// Number of commands in nv_cmds[].
@@ -823,15 +824,12 @@ static bool normal_get_command_count(NormalState *s)
if (s->c == K_DEL || s->c == K_KDEL) {
s->ca.count0 /= 10;
del_from_showcmd(4); // delete the digit and ~@%
+ } else if (s->ca.count0 > 99999999L) {
+ s->ca.count0 = 999999999L;
} else {
s->ca.count0 = s->ca.count0 * 10 + (s->c - '0');
}
- if (s->ca.count0 < 0) {
- // overflow
- s->ca.count0 = 999999999L;
- }
-
// Set v:count here, when called from main() and not a stuffed
// command, so that v:count can be used in an expression mapping
// right after the count. Do set it for redo.
@@ -960,6 +958,7 @@ normal_end:
&& s->oa.regname == 0) {
if (restart_VIsual_select == 1) {
VIsual_select = true;
+ VIsual_select_reg = 0;
trigger_modechanged();
showmode();
restart_VIsual_select = 0;
@@ -1009,7 +1008,14 @@ static int normal_execute(VimState *state, int key)
// restart automatically.
// Insert the typed character in the typeahead buffer, so that it can
// be mapped in Insert mode. Required for ":lmap" to work.
- ins_char_typebuf(s->c);
+ int len = ins_char_typebuf(s->c, mod_mask);
+
+ // When recording and gotchars() was called the character will be
+ // recorded again, remove the previous recording.
+ if (KeyTyped) {
+ ungetchars(len);
+ }
+
if (restart_edit != 0) {
s->c = 'd';
} else {
@@ -1021,7 +1027,7 @@ static int normal_execute(VimState *state, int key)
s->need_flushbuf = add_to_showcmd(s->c);
- while (normal_get_command_count(s)) { continue; }
+ while (normal_get_command_count(s)) { }
if (s->c == K_EVENT) {
// Save the count values so that ca.opcount and ca.count0 are exactly
@@ -1037,14 +1043,14 @@ static int normal_execute(VimState *state, int key)
// If you give a count before AND after the operator, they are
// multiplied.
if (s->ca.count0) {
- s->ca.count0 = (long)((uint64_t)s->ca.count0 * (uint64_t)s->ca.opcount);
+ if (s->ca.opcount >= 999999999L / s->ca.count0) {
+ s->ca.count0 = 999999999L;
+ } else {
+ s->ca.count0 *= s->ca.opcount;
+ }
} else {
s->ca.count0 = s->ca.opcount;
}
- if (s->ca.count0 < 0) {
- // overflow
- s->ca.count0 = 999999999L;
- }
}
// Always remember the count. It will be set to zero (on the next call,
@@ -3089,8 +3095,14 @@ static void nv_gd(oparg_T *oap, int nchar, int thisblock)
if ((len = find_ident_under_cursor(&ptr, FIND_IDENT)) == 0
|| !find_decl(ptr, len, nchar == 'd', thisblock, SEARCH_START)) {
clearopbeep(oap);
- } else if ((fdo_flags & FDO_SEARCH) && KeyTyped && oap->op_type == OP_NOP) {
- foldOpenCursor();
+ } else {
+ if ((fdo_flags & FDO_SEARCH) && KeyTyped && oap->op_type == OP_NOP) {
+ foldOpenCursor();
+ }
+ // clear any search statistics
+ if (messaging() && !msg_silent && !shortmess(SHM_SEARCHCOUNT)) {
+ clear_cmdline = true;
+ }
}
}
@@ -3280,7 +3292,7 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist)
int col_off1; // margin offset for first screen line
int col_off2; // margin offset for wrapped screen line
int width1; // text width for first screen line
- int width2; // test width for wrapped screen line
+ int width2; // text width for wrapped screen line
oap->motion_type = kMTCharWise;
oap->inclusive = (curwin->w_curswant == MAXCOL);
@@ -3404,6 +3416,13 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist)
virtcol -= vim_strsize(get_showbreak_value(curwin));
}
+ int c = utf_ptr2char(get_cursor_pos_ptr());
+ if (dir == FORWARD && virtcol < curwin->w_curswant
+ && (curwin->w_curswant <= (colnr_T)width1)
+ && !vim_isprintc(c) && c > 255) {
+ oneright();
+ }
+
if (virtcol > curwin->w_curswant
&& (curwin->w_curswant < (colnr_T)width1
? (curwin->w_curswant > (colnr_T)width1 / 2)
@@ -4043,21 +4062,22 @@ static void nv_regreplay(cmdarg_T *cap)
}
}
-/// Handle a ":" command and <Cmd>.
+/// Handle a ":" command and <Cmd> or Lua keymaps.
static void nv_colon(cmdarg_T *cap)
{
int old_p_im;
bool cmd_result;
bool is_cmdkey = cap->cmdchar == K_COMMAND;
+ bool is_lua = cap->cmdchar == K_LUA;
- if (VIsual_active && !is_cmdkey) {
+ if (VIsual_active && !is_cmdkey && !is_lua) {
nv_operator(cap);
} else {
if (cap->oap->op_type != OP_NOP) {
// Using ":" as a movement is charwise exclusive.
cap->oap->motion_type = kMTCharWise;
cap->oap->inclusive = false;
- } else if (cap->count0 && !is_cmdkey) {
+ } else if (cap->count0 && !is_cmdkey && !is_lua) {
// translate "count:" into ":.,.+(count - 1)"
stuffcharReadbuff('.');
if (cap->count0 > 1) {
@@ -4073,9 +4093,13 @@ static void nv_colon(cmdarg_T *cap)
old_p_im = p_im;
- // get a command line and execute it
- cmd_result = do_cmdline(NULL, is_cmdkey ? getcmdkeycmd : getexline, NULL,
- cap->oap->op_type != OP_NOP ? DOCMD_KEEPLINE : 0);
+ if (is_lua) {
+ cmd_result = map_execute_lua();
+ } else {
+ // get a command line and execute it
+ cmd_result = do_cmdline(NULL, is_cmdkey ? getcmdkeycmd : getexline, NULL,
+ cap->oap->op_type != OP_NOP ? DOCMD_KEEPLINE : 0);
+ }
// If 'insertmode' changed, enter or exit Insert mode
if (p_im != old_p_im) {
@@ -4431,11 +4455,7 @@ static void nv_ident(cmdarg_T *cap)
// Start insert mode in terminal buffer
restart_edit = 'i';
- add_map((char_u *)"<buffer> <esc> <Cmd>call jobstop(&channel)<CR>", TERM_FOCUS, true);
- do_cmdline_cmd("autocmd TermClose <buffer> "
- " if !v:event.status |"
- " exec 'bdelete! ' .. expand('<abuf>') |"
- " endif");
+ add_map((char_u *)"<buffer> <esc> <Cmd>bdelete!<CR>", TERM_FOCUS, true);
}
}
@@ -4470,8 +4490,13 @@ bool get_visual_text(cmdarg_T *cap, char_u **pp, size_t *lenp)
*pp = ml_get_pos(&VIsual);
*lenp = (size_t)curwin->w_cursor.col - (size_t)VIsual.col + 1;
}
- // Correct the length to include the whole last character.
- *lenp += (size_t)(utfc_ptr2len(*pp + (*lenp - 1)) - 1);
+ if (**pp == NUL) {
+ *lenp = 0;
+ }
+ if (*lenp > 0) {
+ // Correct the length to include all bytes of the last character.
+ *lenp += (size_t)(utfc_ptr2len(*pp + (*lenp - 1)) - 1);
+ }
}
reset_VIsual_and_resel();
return true;
@@ -5005,9 +5030,7 @@ static void nv_brackets(cmdarg_T *cap)
* identifier "]i" "[i" "]I" "[I" "]^I" "[^I"
* define "]d" "[d" "]D" "[D" "]^D" "[^D"
*/
- if (vim_strchr((char_u *)
- "iI\011dD\004",
- cap->nchar) != NULL) {
+ if (vim_strchr((char_u *)"iI\011dD\004", cap->nchar) != NULL) {
char_u *ptr;
size_t len;
@@ -5961,11 +5984,8 @@ static void nv_visual(cmdarg_T *cap)
* was only one -- webb
*/
if (resel_VIsual_mode != 'v' || resel_VIsual_line_count > 1) {
- curwin->w_cursor.lnum +=
- resel_VIsual_line_count * cap->count0 - 1;
- if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) {
- curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
- }
+ curwin->w_cursor.lnum += resel_VIsual_line_count * cap->count0 - 1;
+ check_cursor();
}
VIsual_mode = resel_VIsual_mode;
if (VIsual_mode == 'v') {
@@ -6044,7 +6064,7 @@ static void n_start_visual_mode(int c)
// Corner case: the 0 position in a tab may change when going into
// virtualedit. Recalculate curwin->w_cursor to avoid bad highlighting.
//
- if (c == Ctrl_V && (ve_flags & VE_BLOCK) && gchar_cursor() == TAB) {
+ if (c == Ctrl_V && (get_ve_flags() & VE_BLOCK) && gchar_cursor() == TAB) {
validate_virtcol();
coladvance(curwin->w_virtcol);
}
@@ -6184,6 +6204,7 @@ static void nv_g_cmd(cmdarg_T *cap)
// start Select mode.
if (cap->arg) {
VIsual_select = true;
+ VIsual_select_reg = 0;
} else {
may_start_select('c');
}
@@ -6310,20 +6331,17 @@ static void nv_g_cmd(cmdarg_T *cap)
curwin->w_set_curswant = true;
break;
- case 'M': {
- const char_u *const ptr = get_cursor_line_ptr();
-
+ case 'M':
oap->motion_type = kMTCharWise;
oap->inclusive = false;
- i = (int)mb_string2cells_len(ptr, STRLEN(ptr));
+ i = linetabsize(get_cursor_line_ptr());
if (cap->count0 > 0 && cap->count0 <= 100) {
coladvance((colnr_T)(i * cap->count0 / 100));
} else {
coladvance((colnr_T)(i / 2));
}
curwin->w_set_curswant = true;
- }
- break;
+ break;
case '_':
/* "g_": to the last non-blank character in the line or <count> lines
@@ -6667,9 +6685,8 @@ static void n_opencmd(cmdarg_T *cap)
(cap->cmdchar == 'o' ? 1 : 0))
)
&& open_line(cap->cmdchar == 'O' ? BACKWARD : FORWARD,
- has_format_option(FO_OPEN_COMS)
- ? OPENLINE_DO_COM : 0,
- 0)) {
+ has_format_option(FO_OPEN_COMS) ? OPENLINE_DO_COM : 0,
+ 0, NULL)) {
if (win_cursorline_standout(curwin)) {
// force redraw of cursorline
curwin->w_valid &= ~VALID_CROW;
@@ -6696,11 +6713,26 @@ static void nv_dot(cmdarg_T *cap)
}
}
-/*
- * CTRL-R: undo undo
- */
-static void nv_redo(cmdarg_T *cap)
+// CTRL-R: undo undo or specify register in select mode
+static void nv_redo_or_register(cmdarg_T *cap)
{
+ if (VIsual_select && VIsual_active) {
+ int reg;
+ // Get register name
+ no_mapping++;
+ reg = plain_vgetc();
+ LANGMAP_ADJUST(reg, true);
+ no_mapping--;
+
+ if (reg == '"') {
+ // the unnamed register is 0
+ reg = 0;
+ }
+
+ VIsual_select_reg = valid_yank_reg(reg, true) ? reg : 0;
+ return;
+ }
+
if (!checkclearopq(cap->oap)) {
u_redo((int)cap->count1);
curwin->w_set_curswant = true;
@@ -6949,7 +6981,7 @@ static void adjust_cursor(oparg_T *oap)
if (curwin->w_cursor.col > 0 && gchar_cursor() == NUL
&& (!VIsual_active || *p_sel == 'o')
&& !virtual_active()
- && (ve_flags & VE_ONEMORE) == 0) {
+ && (get_ve_flags() & VE_ONEMORE) == 0) {
curwin->w_cursor.col--;
// prevent cursor from moving on the trail byte
mb_adjust_cursor();
@@ -7021,6 +7053,7 @@ static void nv_select(cmdarg_T *cap)
{
if (VIsual_active) {
VIsual_select = true;
+ VIsual_select_reg = 0;
} else if (VIsual_reselect) {
cap->nchar = 'v'; // fake "gv" command
cap->arg = true;
@@ -7155,7 +7188,7 @@ static void nv_esc(cmdarg_T *cap)
void set_cursor_for_append_to_line(void)
{
curwin->w_set_curswant = true;
- if (ve_flags == VE_ALL) {
+ if (get_ve_flags() == VE_ALL) {
const int save_State = State;
// Pretend Insert mode here to allow the cursor on the
@@ -7473,9 +7506,9 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent)
// overwrites if the old contents is being put.
was_visual = true;
regname = cap->oap->regname;
+ bool save_unnamed = cap->cmdchar == 'P';
// '+' and '*' could be the same selection
- bool clipoverwrite = (regname == '+' || regname == '*')
- && (cb_flags & CB_UNNAMEDMASK);
+ bool clipoverwrite = (regname == '+' || regname == '*') && (cb_flags & CB_UNNAMEDMASK);
if (regname == 0 || regname == '"' || clipoverwrite
|| ascii_isdigit(regname) || regname == '-') {
// The delete might overwrite the register we want to put, save it first
@@ -7488,6 +7521,10 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent)
// do_put(), which requires the visual selection to still be active.
if (!VIsual_active || VIsual_mode == 'V' || regname != '.') {
// Now delete the selected text. Avoid messages here.
+ yankreg_T *old_y_previous;
+ if (save_unnamed) {
+ old_y_previous = get_y_previous();
+ }
cap->cmdchar = 'd';
cap->nchar = NUL;
cap->oap->regname = NUL;
@@ -7497,6 +7534,10 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent)
empty = (curbuf->b_ml.ml_flags & ML_EMPTY);
msg_silent--;
+ if (save_unnamed) {
+ set_y_previous(old_y_previous);
+ }
+
// delete PUT_LINE_BACKWARD;
cap->oap->regname = regname;
}
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index c6f9c5f04f..b5c7020dee 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -135,10 +135,18 @@ static char opchars[][3] =
{ Ctrl_X, NUL, OPF_CHANGE }, // OP_NR_SUB
};
-/*
- * Translate a command name into an operator type.
- * Must only be called with a valid operator name!
- */
+yankreg_T *get_y_previous(void)
+{
+ return y_previous;
+}
+
+void set_y_previous(yankreg_T *yreg)
+{
+ y_previous = yreg;
+}
+
+/// Translate a command name into an operator type.
+/// Must only be called with a valid operator name!
int get_op_type(int char1, int char2)
{
int i;
@@ -267,14 +275,14 @@ void op_shift(oparg_T *oap, int curs_top, int amount)
msg_attr_keep((char *)IObuff, 0, true, false);
}
- /*
- * Set "'[" and "']" marks.
- */
- curbuf->b_op_start = oap->start;
- curbuf->b_op_end.lnum = oap->end.lnum;
- curbuf->b_op_end.col = (colnr_T)STRLEN(ml_get(oap->end.lnum));
- if (curbuf->b_op_end.col > 0) {
- curbuf->b_op_end.col--;
+ if (!cmdmod.lockmarks) {
+ // Set "'[" and "']" marks.
+ curbuf->b_op_start = oap->start;
+ curbuf->b_op_end.lnum = oap->end.lnum;
+ curbuf->b_op_end.col = (colnr_T)STRLEN(ml_get(oap->end.lnum));
+ if (curbuf->b_op_end.col > 0) {
+ curbuf->b_op_end.col--;
+ }
}
changed_lines(oap->start.lnum, 0, oap->end.lnum + 1, 0L, true);
@@ -381,8 +389,8 @@ static void shift_block(oparg_T *oap, int amount)
}
}
for (; ascii_iswhite(*bd.textstart);) {
- // TODO: is passing bd.textstart for start of the line OK?
- incr = lbr_chartabsize_adv(bd.textstart, &bd.textstart, (bd.start_vcol));
+ // TODO(fmoralesc): is passing bd.textstart for start of the line OK?
+ incr = lbr_chartabsize_adv(bd.textstart, &bd.textstart, bd.start_vcol);
total += incr;
bd.start_vcol += incr;
}
@@ -560,21 +568,18 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def
}
if (spaces > 0) {
- int off;
-
- // Avoid starting halfway through a multi-byte character.
- if (b_insert) {
- off = utf_head_off(oldp, oldp + offset + spaces);
- } else {
- off = (*mb_off_next)(oldp, oldp + offset);
- offset += off;
- }
- spaces -= off;
- count -= off;
+ // avoid copying part of a multi-byte character
+ offset -= utf_head_off(oldp, oldp + offset);
+ }
+ if (spaces < 0) { // can happen when the cursor was moved
+ spaces = 0;
}
assert(count >= 0);
- newp = (char_u *)xmalloc(STRLEN(oldp) + s_len + (size_t)count + 1);
+ // Make sure the allocated size matches what is actually copied below.
+ newp = xmalloc(STRLEN(oldp) + (size_t)spaces + s_len
+ + (spaces > 0 && !bdp->is_short ? (size_t)p_ts - (size_t)spaces : 0)
+ + (size_t)count + 1);
// copy up to shifted part
memmove(newp, oldp, (size_t)offset);
@@ -589,14 +594,19 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def
offset += (int)s_len;
int skipped = 0;
- if (spaces && !bdp->is_short) {
- // insert post-padding
- memset(newp + offset + spaces, ' ', (size_t)(p_ts - spaces));
- // We're splitting a TAB, don't copy it.
- oldp++;
- // We allowed for that TAB, remember this now
- count++;
- skipped = 1;
+ if (spaces > 0 && !bdp->is_short) {
+ if (*oldp == TAB) {
+ // insert post-padding
+ memset(newp + offset + spaces, ' ', (size_t)(p_ts - spaces));
+ // We're splitting a TAB, don't copy it.
+ oldp++;
+ // We allowed for that TAB, remember this now
+ count++;
+ skipped = 1;
+ } else {
+ // Not a TAB, no extra spaces
+ count = spaces;
+ }
}
if (spaces > 0) {
@@ -694,9 +704,11 @@ void op_reindent(oparg_T *oap, Indenter how)
"%" PRId64 " lines indented ", i),
(int64_t)i);
}
- // set '[ and '] marks
- curbuf->b_op_start = oap->start;
- curbuf->b_op_end = oap->end;
+ if (!cmdmod.lockmarks) {
+ // set '[ and '] marks
+ curbuf->b_op_start = oap->start;
+ curbuf->b_op_end = oap->end;
+ }
}
/*
@@ -915,10 +927,29 @@ int do_record(int c)
apply_autocmds(EVENT_RECORDINGENTER, NULL, NULL, false, curbuf);
}
} else { // stop recording
- // Get the recorded key hits. K_SPECIAL and CSI will be escaped, this
+ save_v_event_T save_v_event;
+ // Set the v:event dictionary with information about the recording.
+ dict_T *dict = get_v_event(&save_v_event);
+
+ // The recorded text contents.
+ p = get_recorded();
+ if (p != NULL) {
+ // Remove escaping for K_SPECIAL in multi-byte chars.
+ vim_unescape_ks(p);
+ (void)tv_dict_add_str(dict, S_LEN("regcontents"), (const char *)p);
+ }
+
+ // Name of requested register, or empty string for unnamed operation.
+ char buf[NUMBUFLEN+2];
+ buf[0] = (char)regname;
+ buf[1] = NUL;
+ (void)tv_dict_add_str(dict, S_LEN("regname"), buf);
+
+ // Get the recorded key hits. K_SPECIAL will be escaped, this
// needs to be removed again to put it in a register. exec_reg then
// adds the escaping back later.
apply_autocmds(EVENT_RECORDINGLEAVE, NULL, NULL, false, curbuf);
+ restore_v_event(dict, &save_v_event);
reg_recorded = reg_recording;
reg_recording = 0;
if (ui_has(kUIMessages)) {
@@ -926,13 +957,9 @@ int do_record(int c)
} else {
msg("");
}
- p = get_recorded();
if (p == NULL) {
retval = FAIL;
} else {
- // Remove escaping for CSI and K_SPECIAL in multi-byte chars.
- vim_unescape_csi(p);
-
// We don't want to change the default register here, so save and
// restore the current register name.
old_y_previous = y_previous;
@@ -1084,7 +1111,7 @@ int do_execreg(int regname, int colon, int addcr, int silent)
return FAIL;
}
}
- escaped = vim_strsave_escape_csi(reg->y_array[i]);
+ escaped = vim_strsave_escape_ks(reg->y_array[i]);
retval = ins_typebuf(escaped, remap, 0, true, silent);
xfree(escaped);
if (retval == FAIL) {
@@ -1126,7 +1153,7 @@ static void put_reedit_in_typebuf(int silent)
/// Insert register contents "s" into the typeahead buffer, so that it will be
/// executed again.
///
-/// @param esc when true then it is to be taken literally: Escape CSI
+/// @param esc when true then it is to be taken literally: Escape K_SPECIAL
/// characters and no remapping.
/// @param colon add ':' before the line
static int put_in_typebuf(char_u *s, bool esc, bool colon, int silent)
@@ -1141,7 +1168,7 @@ static int put_in_typebuf(char_u *s, bool esc, bool colon, int silent)
char_u *p;
if (esc) {
- p = vim_strsave_escape_csi(s);
+ p = vim_strsave_escape_ks(s);
} else {
p = s;
}
@@ -1420,6 +1447,11 @@ int op_delete(oparg_T *oap)
return FAIL;
}
+ if (VIsual_select && oap->is_VIsual) {
+ // Use the register given with CTRL_R, defaults to zero
+ oap->regname = VIsual_select_reg;
+ }
+
mb_adjust_opend(oap);
/*
@@ -1716,13 +1748,15 @@ int op_delete(oparg_T *oap)
msgmore(curbuf->b_ml.ml_line_count - old_lcount);
setmarks:
- if (oap->motion_type == kMTBlockWise) {
- curbuf->b_op_end.lnum = oap->end.lnum;
- curbuf->b_op_end.col = oap->start.col;
- } else {
- curbuf->b_op_end = oap->start;
+ if (!cmdmod.lockmarks) {
+ if (oap->motion_type == kMTBlockWise) {
+ curbuf->b_op_end.lnum = oap->end.lnum;
+ curbuf->b_op_end.col = oap->start.col;
+ } else {
+ curbuf->b_op_end = oap->start;
+ }
+ curbuf->b_op_start = oap->start;
}
- curbuf->b_op_start = oap->start;
return OK;
}
@@ -1927,11 +1961,14 @@ static int op_replace(oparg_T *oap, int c)
while (ltoreq(curwin->w_cursor, oap->end)) {
n = gchar_cursor();
if (n != NUL) {
- if (utf_char2len(c) > 1 || utf_char2len(n) > 1) {
+ int new_byte_len = utf_char2len(c);
+ int old_byte_len = utfc_ptr2len(get_cursor_pos_ptr());
+
+ if (new_byte_len > 1 || old_byte_len > 1) {
// This is slow, but it handles replacing a single-byte
// with a multi-byte and the other way around.
if (curwin->w_cursor.lnum == oap->end.lnum) {
- oap->end.col += utf_char2len(c) - utf_char2len(n);
+ oap->end.col += new_byte_len - old_byte_len;
}
replace_character(c);
} else {
@@ -1987,9 +2024,11 @@ static int op_replace(oparg_T *oap, int c)
check_cursor();
changed_lines(oap->start.lnum, oap->start.col, oap->end.lnum + 1, 0L, true);
- // Set "'[" and "']" marks.
- curbuf->b_op_start = oap->start;
- curbuf->b_op_end = oap->end;
+ if (!cmdmod.lockmarks) {
+ // Set "'[" and "']" marks.
+ curbuf->b_op_start = oap->start;
+ curbuf->b_op_end = oap->end;
+ }
return OK;
}
@@ -2058,11 +2097,11 @@ void op_tilde(oparg_T *oap)
redraw_curbuf_later(INVERTED);
}
- /*
- * Set '[ and '] marks.
- */
- curbuf->b_op_start = oap->start;
- curbuf->b_op_end = oap->end;
+ if (!cmdmod.lockmarks) {
+ // Set '[ and '] marks.
+ curbuf->b_op_start = oap->start;
+ curbuf->b_op_end = oap->end;
+ }
if (oap->line_count > p_report) {
smsg(NGETTEXT("%" PRId64 " line changed",
@@ -2181,19 +2220,22 @@ void op_insert(oparg_T *oap, long count1)
// doing block_prep(). When only "block" is used, virtual edit is
// already disabled, but still need it when calling
// coladvance_force().
+ // coladvance_force() uses get_ve_flags() to get the 'virtualedit'
+ // state for the current window. To override that state, we need to
+ // set the window-local value of ve_flags rather than the global value.
if (curwin->w_cursor.coladd > 0) {
- unsigned old_ve_flags = ve_flags;
+ unsigned old_ve_flags = curwin->w_ve_flags;
- ve_flags = VE_ALL;
if (u_save_cursor() == FAIL) {
return;
}
+ curwin->w_ve_flags = VE_ALL;
coladvance_force(oap->op_type == OP_APPEND
? oap->end_vcol + 1 : getviscol());
if (oap->op_type == OP_APPEND) {
--curwin->w_cursor.col;
}
- ve_flags = old_ve_flags;
+ curwin->w_ve_flags = old_ve_flags;
}
// Get the info about the block before entering the text
block_prep(oap, &bd, oap->start.lnum, true);
@@ -2241,6 +2283,7 @@ void op_insert(oparg_T *oap, long count1)
}
t1 = oap->start;
+ const pos_T start_insert = curwin->w_cursor;
(void)edit(NUL, false, (linenr_T)count1);
// When a tab was inserted, and the characters in front of the tab
@@ -2275,23 +2318,18 @@ void op_insert(oparg_T *oap, long count1)
// The user may have moved the cursor before inserting something, try
// to adjust the block for that. But only do it, if the difference
// does not come from indent kicking in.
- if (oap->start.lnum == curbuf->b_op_start_orig.lnum
- && !bd.is_MAX
- && !did_indent) {
+ if (oap->start.lnum == curbuf->b_op_start_orig.lnum && !bd.is_MAX && !did_indent) {
+ const int t = getviscol2(curbuf->b_op_start_orig.col, curbuf->b_op_start_orig.coladd);
+
if (oap->op_type == OP_INSERT
&& oap->start.col + oap->start.coladd
!= curbuf->b_op_start_orig.col + curbuf->b_op_start_orig.coladd) {
- int t = getviscol2(curbuf->b_op_start_orig.col,
- curbuf->b_op_start_orig.coladd);
oap->start.col = curbuf->b_op_start_orig.col;
pre_textlen -= t - oap->start_vcol;
oap->start_vcol = t;
} else if (oap->op_type == OP_APPEND
- && oap->end.col + oap->end.coladd
- >= curbuf->b_op_start_orig.col
- + curbuf->b_op_start_orig.coladd) {
- int t = getviscol2(curbuf->b_op_start_orig.col,
- curbuf->b_op_start_orig.coladd);
+ && oap->start.col + oap->start.coladd
+ >= curbuf->b_op_start_orig.col + curbuf->b_op_start_orig.coladd) {
oap->start.col = curbuf->b_op_start_orig.col;
// reset pre_textlen to the value of OP_INSERT
pre_textlen += bd.textlen;
@@ -2339,15 +2377,27 @@ void op_insert(oparg_T *oap, long count1)
firstline = ml_get(oap->start.lnum);
const size_t len = STRLEN(firstline);
colnr_T add = bd.textcol;
+ colnr_T offset = 0; // offset when cursor was moved in insert mode
if (oap->op_type == OP_APPEND) {
add += bd.textlen;
+ // account for pressing cursor in insert mode when '$' was used
+ if (bd.is_MAX && start_insert.lnum == Insstart.lnum && start_insert.col > Insstart.col) {
+ offset = start_insert.col - Insstart.col;
+ add -= offset;
+ if (oap->end_vcol > offset) {
+ oap->end_vcol -= offset + 1;
+ } else {
+ // moved outside of the visual block, what to do?
+ return;
+ }
+ }
}
if ((size_t)add > len) {
firstline += len; // short line, point to the NUL
} else {
firstline += add;
}
- ins_len = (long)STRLEN(firstline) - pre_textlen;
+ ins_len = (long)STRLEN(firstline) - pre_textlen - offset;
if (pre_textlen >= 0 && ins_len > 0) {
ins_text = vim_strnsave(firstline, (size_t)ins_len);
// block handled here
@@ -2751,17 +2801,15 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append)
}
}
- /*
- * Set "'[" and "']" marks.
- */
- curbuf->b_op_start = oap->start;
- curbuf->b_op_end = oap->end;
- if (yank_type == kMTLineWise) {
- curbuf->b_op_start.col = 0;
- curbuf->b_op_end.col = MAXCOL;
+ if (!cmdmod.lockmarks) {
+ // Set "'[" and "']" marks.
+ curbuf->b_op_start = oap->start;
+ curbuf->b_op_end = oap->end;
+ if (yank_type == kMTLineWise) {
+ curbuf->b_op_start.col = 0;
+ curbuf->b_op_end.col = MAXCOL;
+ }
}
-
- return;
}
// Copy a block range into a register.
@@ -2786,7 +2834,7 @@ static void yank_copy_line(yankreg_T *reg, struct block_def *bd, size_t y_idx,
if (exclude_trailing_space) {
int s = bd->textlen + bd->endspaces;
- while (ascii_iswhite(*(bd->textstart + s - 1)) && s > 0) {
+ while (s > 0 && ascii_iswhite(*(bd->textstart + s - 1))) {
s = s - utf_head_off(bd->textstart, bd->textstart + s - 1) - 1;
pnew--;
}
@@ -2891,6 +2939,9 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
char_u *insert_string = NULL;
bool allocated = false;
long cnt;
+ const pos_T orig_start = curbuf->b_op_start;
+ const pos_T orig_end = curbuf->b_op_end;
+ unsigned int cur_ve_flags = get_ve_flags();
if (flags & PUT_FIXINDENT) {
orig_indent = get_indent();
@@ -2961,7 +3012,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
eol = (*(cursor_pos + utfc_ptr2len(cursor_pos)) == NUL);
}
- bool ve_allows = (ve_flags == VE_ALL || ve_flags == VE_ONEMORE);
+ bool ve_allows = (cur_ve_flags == VE_ALL || cur_ve_flags == VE_ONEMORE);
bool eof = curbuf->b_ml.ml_line_count == curwin->w_cursor.lnum
&& one_past_line;
if (ve_allows || !(eol || eof)) {
@@ -3137,13 +3188,14 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
yanklen = (int)STRLEN(y_array[0]);
- if (ve_flags == VE_ALL && y_type == kMTCharWise) {
+ if (cur_ve_flags == VE_ALL && y_type == kMTCharWise) {
if (gchar_cursor() == TAB) {
- /* Don't need to insert spaces when "p" on the last position of a
- * tab or "P" on the first position. */
int viscol = getviscol();
+ long ts = curbuf->b_p_ts;
+ // Don't need to insert spaces when "p" on the last position of a
+ // tab or "P" on the first position.
if (dir == FORWARD
- ? tabstop_padding(viscol, curbuf->b_p_ts, curbuf->b_p_vts_array) != 1
+ ? tabstop_padding(viscol, ts, curbuf->b_p_vts_array) != 1
: curwin->w_cursor.coladd > 0) {
coladvance_force(viscol);
} else {
@@ -3165,7 +3217,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
colnr_T endcol2 = 0;
if (dir == FORWARD && c != NUL) {
- if (ve_flags == VE_ALL) {
+ if (cur_ve_flags == VE_ALL) {
getvcol(curwin, &curwin->w_cursor, &col, NULL, &endcol2);
} else {
getvcol(curwin, &curwin->w_cursor, NULL, NULL, &col);
@@ -3179,9 +3231,8 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
}
col += curwin->w_cursor.coladd;
- if (ve_flags == VE_ALL
- && (curwin->w_cursor.coladd > 0
- || endcol2 == curwin->w_cursor.col)) {
+ if (cur_ve_flags == VE_ALL
+ && (curwin->w_cursor.coladd > 0 || endcol2 == curwin->w_cursor.col)) {
if (dir == FORWARD && c == NUL) {
col++;
}
@@ -3263,18 +3314,28 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
}
}
- // insert the new text
+ // Insert the new text.
+ // First check for multiplication overflow.
+ if (yanklen + spaces != 0
+ && count > ((INT_MAX - (bd.startspaces + bd.endspaces)) / (yanklen + spaces))) {
+ emsg(_(e_resulting_text_too_long));
+ break;
+ }
+
totlen = (size_t)(count * (yanklen + spaces)
+ bd.startspaces + bd.endspaces);
int addcount = (int)totlen + lines_appended;
newp = (char_u *)xmalloc(totlen + oldlen + 1);
+
// copy part up to cursor to new line
ptr = newp;
memmove(ptr, oldp, (size_t)bd.textcol);
ptr += bd.textcol;
+
// may insert some spaces before the new text
memset(ptr, ' ', (size_t)bd.startspaces);
ptr += bd.startspaces;
+
// insert the new text
for (long j = 0; j < count; j++) {
memmove(ptr, y_array[i], (size_t)yanklen);
@@ -3288,9 +3349,11 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
addcount -= spaces;
}
}
+
// may insert some spaces after the new text
memset(ptr, ' ', (size_t)bd.endspaces);
ptr += bd.endspaces;
+
// move the text after the cursor to the end of the line.
int columns = (int)oldlen - bd.textcol - delcount + 1;
assert(columns >= 0);
@@ -3379,10 +3442,18 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
}
}
- do {
+ if (count == 0 || yanklen == 0) {
+ if (VIsual_active) {
+ lnum = end_lnum;
+ }
+ } else if (count > INT_MAX / yanklen) {
+ // multiplication overflow
+ emsg(_(e_resulting_text_too_long));
+ } else {
totlen = (size_t)(count * yanklen);
- if (totlen > 0) {
+ do {
oldp = ml_get(lnum);
+ oldlen = STRLEN(oldp);
if (lnum > start_lnum) {
pos_T pos = {
.lnum = lnum,
@@ -3393,11 +3464,11 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
col = MAXCOL;
}
}
- if (VIsual_active && col > (int)STRLEN(oldp)) {
+ if (VIsual_active && col > (colnr_T)oldlen) {
lnum++;
continue;
}
- newp = (char_u *)xmalloc((size_t)(STRLEN(oldp) + totlen + 1));
+ newp = (char_u *)xmalloc(totlen + oldlen + 1);
memmove(newp, oldp, (size_t)col);
ptr = newp + col;
for (i = 0; i < (size_t)count; i++) {
@@ -3419,14 +3490,14 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
changed_bytes(lnum, col);
extmark_splice_cols(curbuf, (int)lnum-1, col,
0, (int)totlen, kExtmarkUndo);
- }
- if (VIsual_active) {
- lnum++;
- }
- } while (VIsual_active && lnum <= end_lnum);
+ if (VIsual_active) {
+ lnum++;
+ }
+ } while (VIsual_active && lnum <= end_lnum);
- if (VIsual_active) { // reset lnum to the last visual line
- lnum--;
+ if (VIsual_active) { // reset lnum to the last visual line
+ lnum--;
+ }
}
// put '] at the first byte of the last character
@@ -3440,6 +3511,9 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
curwin->w_cursor.col -= first_byte_off;
}
} else {
+ linenr_T new_lnum = new_cursor.lnum;
+ size_t len;
+
// Insert at least one line. When y_type is kMTCharWise, break the first
// line in two.
for (cnt = 1; cnt <= count; cnt++) {
@@ -3456,6 +3530,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
STRCAT(newp, ptr);
// insert second line
ml_append(lnum, newp, (colnr_T)0, false);
+ new_lnum++;
xfree(newp);
oldp = ml_get(lnum);
@@ -3471,10 +3546,11 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
}
for (; i < y_size; i++) {
- if ((y_type != kMTCharWise || i < y_size - 1)
- && ml_append(lnum, y_array[i], (colnr_T)0, false)
- == FAIL) {
- goto error;
+ if ((y_type != kMTCharWise || i < y_size - 1)) {
+ if (ml_append(lnum, y_array[i], (colnr_T)0, false) == FAIL) {
+ goto error;
+ }
+ new_lnum++;
}
lnum++;
++nr_lines;
@@ -3524,6 +3600,10 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
extmark_splice(curbuf, (int)new_cursor.lnum-1, col + 1, 0, 0, 0,
(int)y_size+1, 0, totsize+2, kExtmarkUndo);
}
+
+ if (cnt == 1) {
+ new_lnum = lnum;
+ }
}
error:
@@ -3551,11 +3631,12 @@ error:
// Put the '] mark on the first byte of the last inserted character.
// Correct the length for change in indent.
- curbuf->b_op_end.lnum = lnum;
- col = (colnr_T)STRLEN(y_array[y_size - 1]) - lendiff;
+ curbuf->b_op_end.lnum = new_lnum;
+ len = STRLEN(y_array[y_size - 1]);
+ col = (colnr_T)len - lendiff;
if (col > 1) {
curbuf->b_op_end.col = col - 1 - utf_head_off(y_array[y_size - 1],
- y_array[y_size - 1] + col - 1);
+ y_array[y_size - 1] + len - 1);
} else {
curbuf->b_op_end.col = 0;
}
@@ -3574,8 +3655,12 @@ error:
}
curwin->w_cursor.col = 0;
} else {
- curwin->w_cursor.lnum = lnum;
+ curwin->w_cursor.lnum = new_lnum;
curwin->w_cursor.col = col;
+ curbuf->b_op_end = curwin->w_cursor;
+ if (col > 1) {
+ curbuf->b_op_end.col = col - 1;
+ }
}
} else if (y_type == kMTLineWise) {
// put cursor on first non-blank in first inserted line
@@ -3594,6 +3679,10 @@ error:
curwin->w_set_curswant = TRUE;
end:
+ if (cmdmod.lockmarks) {
+ curbuf->b_op_start = orig_start;
+ curbuf->b_op_end = orig_end;
+ }
if (allocated) {
xfree(insert_string);
}
@@ -3613,14 +3702,16 @@ end:
*/
void adjust_cursor_eol(void)
{
+ unsigned int cur_ve_flags = get_ve_flags();
+
if (curwin->w_cursor.col > 0
&& gchar_cursor() == NUL
- && (ve_flags & VE_ONEMORE) == 0
+ && (cur_ve_flags & VE_ONEMORE) == 0
&& !(restart_edit || (State & INSERT))) {
// Put the cursor on the last character in the line.
dec_cursor();
- if (ve_flags == VE_ALL) {
+ if (cur_ve_flags == VE_ALL) {
colnr_T scol, ecol;
// Coladd is set to the width of the last character.
@@ -3674,7 +3765,7 @@ void ex_display(exarg_T *eap)
int name;
char_u *arg = eap->arg;
int clen;
- char_u type[2];
+ int type;
if (arg != NULL && *arg == NUL) {
arg = NULL;
@@ -3687,11 +3778,11 @@ void ex_display(exarg_T *eap)
name = get_register_name(i);
switch (get_reg_type(name, NULL)) {
case kMTLineWise:
- type[0] = 'l'; break;
+ type = 'l'; break;
case kMTCharWise:
- type[0] = 'c'; break;
+ type = 'c'; break;
default:
- type[0] = 'b'; break;
+ type = 'b'; break;
}
if (arg != NULL && vim_strchr(arg, name) == NULL) {
@@ -3718,88 +3809,87 @@ void ex_display(exarg_T *eap)
}
if (yb->y_array != NULL) {
- msg_putchar('\n');
- msg_puts(" ");
- msg_putchar(type[0]);
- msg_puts(" ");
- msg_putchar('"');
- msg_putchar(name);
- msg_puts(" ");
-
- int n = Columns - 11;
- for (size_t j = 0; j < yb->y_size && n > 1; j++) {
- if (j) {
- msg_puts_attr("^J", attr);
- n -= 2;
+ bool do_show = false;
+
+ for (size_t j = 0; !do_show && j < yb->y_size; j++) {
+ do_show = !message_filtered(yb->y_array[j]);
+ }
+
+ if (do_show || yb->y_size == 0) {
+ msg_putchar('\n');
+ msg_puts(" ");
+ msg_putchar(type);
+ msg_puts(" ");
+ msg_putchar('"');
+ msg_putchar(name);
+ msg_puts(" ");
+
+ int n = Columns - 11;
+ for (size_t j = 0; j < yb->y_size && n > 1; j++) {
+ if (j) {
+ msg_puts_attr("^J", attr);
+ n -= 2;
+ }
+ for (p = yb->y_array[j]; *p && (n -= ptr2cells(p)) >= 0; p++) {
+ clen = utfc_ptr2len(p);
+ msg_outtrans_len(p, clen);
+ p += clen - 1;
+ }
}
- for (p = yb->y_array[j]; *p && (n -= ptr2cells(p)) >= 0; p++) { // -V1019 NOLINT(whitespace/line_length)
- clen = utfc_ptr2len(p);
- msg_outtrans_len(p, clen);
- p += clen - 1;
+ if (n > 1 && yb->y_type == kMTLineWise) {
+ msg_puts_attr("^J", attr);
}
+ ui_flush(); // show one line at a time
}
- if (n > 1 && yb->y_type == kMTLineWise) {
- msg_puts_attr("^J", attr);
- }
- ui_flush(); // show one line at a time
+ os_breakcheck();
}
- os_breakcheck();
}
- /*
- * display last inserted text
- */
+ // display last inserted text
if ((p = get_last_insert()) != NULL
- && (arg == NULL || vim_strchr(arg, '.') != NULL) && !got_int) {
+ && (arg == NULL || vim_strchr(arg, '.') != NULL) && !got_int
+ && !message_filtered(p)) {
msg_puts("\n c \". ");
dis_msg(p, true);
}
- /*
- * display last command line
- */
+ // display last command line
if (last_cmdline != NULL && (arg == NULL || vim_strchr(arg, ':') != NULL)
- && !got_int) {
+ && !got_int && !message_filtered(last_cmdline)) {
msg_puts("\n c \": ");
dis_msg(last_cmdline, false);
}
- /*
- * display current file name
- */
+ // display current file name
if (curbuf->b_fname != NULL
- && (arg == NULL || vim_strchr(arg, '%') != NULL) && !got_int) {
+ && (arg == NULL || vim_strchr(arg, '%') != NULL) && !got_int
+ && !message_filtered(curbuf->b_fname)) {
msg_puts("\n c \"% ");
dis_msg(curbuf->b_fname, false);
}
- /*
- * display alternate file name
- */
+ // display alternate file name
if ((arg == NULL || vim_strchr(arg, '%') != NULL) && !got_int) {
char_u *fname;
linenr_T dummy;
- if (buflist_name_nr(0, &fname, &dummy) != FAIL) {
+ if (buflist_name_nr(0, &fname, &dummy) != FAIL && !message_filtered(fname)) {
msg_puts("\n c \"# ");
dis_msg(fname, false);
}
}
- /*
- * display last search pattern
- */
+ // display last search pattern
if (last_search_pat() != NULL
- && (arg == NULL || vim_strchr(arg, '/') != NULL) && !got_int) {
+ && (arg == NULL || vim_strchr(arg, '/') != NULL) && !got_int
+ && !message_filtered(last_search_pat())) {
msg_puts("\n c \"/ ");
dis_msg(last_search_pat(), false);
}
- /*
- * display last used expression
- */
+ // display last used expression
if (expr_line != NULL && (arg == NULL || vim_strchr(arg, '=') != NULL)
- && !got_int) {
+ && !got_int && !message_filtered(expr_line)) {
msg_puts("\n c \"= ");
dis_msg(expr_line, false);
}
@@ -3938,7 +4028,7 @@ int do_join(size_t count, int insert_space, int save_undo, int use_formatoptions
// and setup the array of space strings lengths
for (t = 0; t < (linenr_T)count; t++) {
curr = curr_start = ml_get((linenr_T)(curwin->w_cursor.lnum + t));
- if (t == 0 && setmark) {
+ if (t == 0 && setmark && !cmdmod.lockmarks) {
// Set the '[ mark.
curwin->w_buffer->b_op_start.lnum = curwin->w_cursor.lnum;
curwin->w_buffer->b_op_start.col = (colnr_T)STRLEN(curr);
@@ -4059,7 +4149,7 @@ int do_join(size_t count, int insert_space, int save_undo, int use_formatoptions
ml_replace(curwin->w_cursor.lnum, newp, false);
- if (setmark) {
+ if (setmark && !cmdmod.lockmarks) {
// Set the '] mark.
curwin->w_buffer->b_op_end.lnum = curwin->w_cursor.lnum;
curwin->w_buffer->b_op_end.col = sumsize;
@@ -4123,7 +4213,7 @@ static int same_leader(linenr_T lnum, int leader1_len, char_u *leader1_flags, in
* If first leader has 'f' flag, the lines can be joined only if the
* second line does not have a leader.
* If first leader has 'e' flag, the lines can never be joined.
- * If fist leader has 's' flag, the lines can only be joined if there is
+ * If first leader has 's' flag, the lines can only be joined if there is
* some text after it and the second line has the 'm' flag.
*/
if (leader1_flags != NULL) {
@@ -4197,8 +4287,10 @@ static void op_format(oparg_T *oap, int keep_cursor)
redraw_curbuf_later(INVERTED);
}
- // Set '[ mark at the start of the formatted area
- curbuf->b_op_start = oap->start;
+ if (!cmdmod.lockmarks) {
+ // Set '[ mark at the start of the formatted area
+ curbuf->b_op_start = oap->start;
+ }
// For "gw" remember the cursor position and put it back below (adjusted
// for joined and split lines).
@@ -4220,8 +4312,10 @@ static void op_format(oparg_T *oap, int keep_cursor)
old_line_count = curbuf->b_ml.ml_line_count - old_line_count;
msgmore(old_line_count);
- // put '] mark on the end of the formatted area
- curbuf->b_op_end = curwin->w_cursor;
+ if (!cmdmod.lockmarks) {
+ // put '] mark on the end of the formatted area
+ curbuf->b_op_end = curwin->w_cursor;
+ }
if (keep_cursor) {
curwin->w_cursor = saved_cursor;
@@ -4308,7 +4402,7 @@ void format_lines(linenr_T line_count, int avoid_fex)
int leader_len = 0; // leader len of current line
int next_leader_len; // leader len of next line
char_u *leader_flags = NULL; // flags for leader of current line
- char_u *next_leader_flags; // flags for leader of next line
+ char_u *next_leader_flags = NULL; // flags for leader of next line
bool advance = true;
int second_indent = -1; // indent for second line (comment aware)
bool first_par_line = true;
@@ -4425,7 +4519,14 @@ void format_lines(linenr_T line_count, int avoid_fex)
leader_len, leader_flags,
next_leader_len,
next_leader_flags)) {
- is_end_par = true;
+ // Special case: If the next line starts with a line comment
+ // and this line has a line comment after some text, the
+ // paragraph doesn't really end.
+ if (next_leader_flags == NULL
+ || STRNCMP(next_leader_flags, "://", 3) != 0
+ || check_linecomment(get_cursor_line_ptr()) == MAXCOL) {
+ is_end_par = true;
+ }
}
/*
@@ -4819,7 +4920,7 @@ void op_addsub(oparg_T *oap, linenr_T Prenum1, bool g_cmd)
// Set '[ mark if something changed. Keep the last end
// position from do_addsub().
- if (change_cnt > 0) {
+ if (change_cnt > 0 && !cmdmod.lockmarks) {
curbuf->b_op_start = startpos;
}
@@ -5173,11 +5274,13 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
}
}
- // set the '[ and '] marks
- curbuf->b_op_start = startpos;
- curbuf->b_op_end = endpos;
- if (curbuf->b_op_end.col > 0) {
- curbuf->b_op_end.col--;
+ if (!cmdmod.lockmarks) {
+ // set the '[ and '] marks
+ curbuf->b_op_start = startpos;
+ curbuf->b_op_end = endpos;
+ if (curbuf->b_op_end.col > 0) {
+ curbuf->b_op_end.col--;
+ }
}
theend:
@@ -5608,7 +5711,9 @@ static void str_to_reg(yankreg_T *y_ptr, MotionType yank_type, const char_u *str
// When appending, copy the previous line and free it after.
size_t extra = append ? STRLEN(pp[--lnum]) : 0;
char_u *s = xmallocz(line_len + extra);
- memcpy(s, pp[lnum], extra);
+ if (extra > 0) {
+ memcpy(s, pp[lnum], extra);
+ }
memcpy(s + extra, start, line_len);
size_t s_len = extra + line_len;
@@ -5990,6 +6095,8 @@ static void op_function(const oparg_T *oap)
{
const TriState save_virtual_op = virtual_op;
const bool save_finish_op = finish_op;
+ const pos_T orig_start = curbuf->b_op_start;
+ const pos_T orig_end = curbuf->b_op_end;
if (*p_opfunc == NUL) {
emsg(_("E774: 'operatorfunc' is empty"));
@@ -6023,6 +6130,10 @@ static void op_function(const oparg_T *oap)
virtual_op = save_virtual_op;
finish_op = save_finish_op;
+ if (cmdmod.lockmarks) {
+ curbuf->b_op_start = orig_start;
+ curbuf->b_op_end = orig_end;
+ }
}
}
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 65adda3c01..9068c90dd1 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -281,7 +281,7 @@ typedef struct vimoption {
# include "options.generated.h"
#endif
-#define PARAM_COUNT ARRAY_SIZE(options)
+#define OPTION_COUNT ARRAY_SIZE(options)
static char *(p_ambw_values[]) = { "single", "double", NULL };
static char *(p_bg_values[]) = { "light", "dark", NULL };
@@ -931,6 +931,21 @@ void set_title_defaults(void)
}
}
+void ex_set(exarg_T *eap)
+{
+ int flags = 0;
+
+ if (eap->cmdidx == CMD_setlocal) {
+ flags = OPT_LOCAL;
+ } else if (eap->cmdidx == CMD_setglobal) {
+ flags = OPT_GLOBAL;
+ }
+ if (eap->forceit) {
+ flags |= OPT_ONECOLUMN;
+ }
+ (void)do_set(eap->arg, flags);
+}
+
/// Parse 'arg' for option settings.
///
/// 'arg' may be IObuff, but only when no errors can be present and option
@@ -1345,7 +1360,7 @@ int do_set(char_u *arg, int opt_flags)
if (nextchar == '&') { // set to default val
newval = options[opt_idx].def_val;
- // expand environment variables and ~ (since the
+ // expand environment variables and ~ since the
// default value was already expanded, only
// required when an environment variable was set
// later
@@ -1766,7 +1781,7 @@ static char *illegal_char(char *errbuf, size_t errbuflen, int c)
if (errbuf == NULL) {
return "";
}
- vim_snprintf((char *)errbuf, errbuflen, _("E539: Illegal character <%s>"),
+ vim_snprintf(errbuf, errbuflen, _("E539: Illegal character <%s>"),
(char *)transchar(c));
return errbuf;
}
@@ -1969,10 +1984,9 @@ static void didset_options(void)
(void)did_set_spell_option(true);
// set cedit_key
(void)check_cedit();
- briopt_check(curwin);
// initialize the table for 'breakat'.
fill_breakat_flags();
- fill_culopt_flags(NULL, curwin);
+ didset_window_options(curwin);
}
// More side effects of setting options.
@@ -1993,9 +2007,9 @@ static void didset_options2(void)
// Parse default for 'wildmode'.
check_opt_wim();
xfree(curbuf->b_p_vsts_array);
- tabstop_set(curbuf->b_p_vsts, &curbuf->b_p_vsts_array);
+ (void)tabstop_set(curbuf->b_p_vsts, &curbuf->b_p_vsts_array);
xfree(curbuf->b_p_vts_array);
- tabstop_set(curbuf->b_p_vts, &curbuf->b_p_vts_array);
+ (void)tabstop_set(curbuf->b_p_vts, &curbuf->b_p_vts_array);
}
/// Check for string options that are NULL (normally only termcap options).
@@ -2269,12 +2283,12 @@ static char *set_string_option(const int opt_idx, const char *const value, const
*varp = s;
char *const saved_oldval = xstrdup(oldval);
- char *const saved_oldval_l = (oldval_l != NULL) ? xstrdup((char *)oldval_l) : 0;
- char *const saved_oldval_g = (oldval_g != NULL) ? xstrdup((char *)oldval_g) : 0;
+ char *const saved_oldval_l = (oldval_l != NULL) ? xstrdup(oldval_l) : 0;
+ char *const saved_oldval_g = (oldval_g != NULL) ? xstrdup(oldval_g) : 0;
char *const saved_newval = xstrdup(s);
int value_checked = false;
- char *const r = did_set_string_option(opt_idx, (char_u **)varp, (int)true,
+ char *const r = did_set_string_option(opt_idx, (char_u **)varp, true,
(char_u *)oldval,
NULL, 0, opt_flags, &value_checked);
if (r == NULL) {
@@ -2762,7 +2776,7 @@ ambw_end:
if (!ascii_isdigit(*(s - 1))) {
if (errbuf != NULL) {
- vim_snprintf((char *)errbuf, errbuflen,
+ vim_snprintf(errbuf, errbuflen,
_("E526: Missing number after <%s>"),
transchar_byte(*(s - 1)));
errmsg = errbuf;
@@ -2953,7 +2967,7 @@ ambw_end:
}
} else {
if (errbuf != NULL) {
- vim_snprintf((char *)errbuf, errbuflen,
+ vim_snprintf(errbuf, errbuflen,
_("E535: Illegal character after <%c>"),
*--s);
errmsg = errbuf;
@@ -3084,14 +3098,27 @@ ambw_end:
if (foldmethodIsIndent(curwin)) {
foldUpdateAll(curwin);
}
- } else if (varp == &p_ve) { // 'virtualedit'
- if (opt_strings_flags(p_ve, p_ve_values, &ve_flags, true) != OK) {
- errmsg = e_invarg;
- } else if (STRCMP(p_ve, oldval) != 0) {
- // Recompute cursor position in case the new 've' setting
- // changes something.
- validate_virtcol();
- coladvance(curwin->w_virtcol);
+ } else if (gvarp == &p_ve) { // 'virtualedit'
+ char_u *ve = p_ve;
+ unsigned int *flags = &ve_flags;
+
+ if (opt_flags & OPT_LOCAL) {
+ ve = curwin->w_p_ve;
+ flags = &curwin->w_ve_flags;
+ }
+
+ if ((opt_flags & OPT_LOCAL) && *ve == NUL) {
+ // make the local value empty: use the global value
+ *flags = 0;
+ } else {
+ if (opt_strings_flags(ve, p_ve_values, flags, true) != OK) {
+ errmsg = e_invarg;
+ } else if (STRCMP(p_ve, oldval) != 0) {
+ // Recompute cursor position in case the new 've' setting
+ // changes something.
+ validate_virtcol();
+ coladvance(curwin->w_virtcol);
+ }
}
} else if (varp == &p_csqf) {
if (p_csqf != NULL) {
@@ -3150,10 +3177,7 @@ ambw_end:
char_u *cp;
if (!(*varp)[0] || ((*varp)[0] == '0' && !(*varp)[1])) {
- if (curbuf->b_p_vsts_array) {
- xfree(curbuf->b_p_vsts_array);
- curbuf->b_p_vsts_array = 0;
- }
+ XFREE_CLEAR(curbuf->b_p_vsts_array);
} else {
for (cp = *varp; *cp; cp++) {
if (ascii_isdigit(*cp)) {
@@ -3178,10 +3202,7 @@ ambw_end:
char_u *cp;
if (!(*varp)[0] || ((*varp)[0] == '0' && !(*varp)[1])) {
- if (curbuf->b_p_vts_array) {
- xfree(curbuf->b_p_vts_array);
- curbuf->b_p_vts_array = NULL;
- }
+ XFREE_CLEAR(curbuf->b_p_vts_array);
} else {
for (cp = *varp; *cp; cp++) {
if (ascii_isdigit(*cp)) {
@@ -3857,6 +3878,7 @@ static void set_option_sctx_idx(int opt_idx, int opt_flags, sctx_T script_ctx)
{
int both = (opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0;
int indir = (int)options[opt_idx].indir;
+ nlua_set_sctx(&script_ctx);
const LastSet last_set = {
.script_ctx = {
script_ctx.sc_sid,
@@ -4132,7 +4154,7 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va
}
}
- // Arabic requires a utf-8 encoding, inform the user if its not
+ // Arabic requires a utf-8 encoding, inform the user if it's not
// set.
if (STRCMP(p_enc, "utf-8") != 0) {
static char *w_arabic = N_("W17: Arabic requires UTF-8, do ':set encoding=utf-8'");
@@ -4256,7 +4278,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf,
}
// Save the global value before changing anything. This is needed as for
- // a global-only option setting the "local value" infact sets the global
+ // a global-only option setting the "local value" in fact sets the global
// value (since there is only one value).
if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) {
old_global_value = *(long *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL);
@@ -4321,6 +4343,12 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf,
} else if (value > 10000) {
errmsg = e_invarg;
}
+ } else if (pp == &p_pyx) {
+ if (value == 0) {
+ value = 3;
+ } else if (value != 3) {
+ errmsg = e_invarg;
+ }
} else if (pp == &p_re) {
if (value < 0 || value > 2) {
errmsg = e_invarg;
@@ -4386,6 +4414,8 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf,
} else if (pp == &curbuf->b_p_ts || pp == &p_ts) {
if (value < 1) {
errmsg = e_positive;
+ } else if (value > TABSTOP_MAX) {
+ errmsg = e_invarg;
}
} else if (pp == &curbuf->b_p_tw || pp == &p_tw) {
if (value < 0) {
@@ -4399,7 +4429,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf,
// Don't change the value and return early if validation failed.
if (errmsg != NULL) {
- return (char *)errmsg;
+ return errmsg;
}
*pp = value;
@@ -4495,10 +4525,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf,
if (pum_drawn()) {
pum_redraw();
}
- } else if (pp == &p_pyx) {
- if (p_pyx != 0 && p_pyx != 2 && p_pyx != 3) {
- errmsg = e_invarg;
- }
} else if (pp == &p_ul || pp == &curbuf->b_p_ul) {
// sync undo before 'undolevels' changes
// use the old value, otherwise u_sync() may not work properly
@@ -4527,7 +4553,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf,
// Check the (new) bounds for Rows and Columns here.
if (p_lines < min_rows() && full_screen) {
if (errbuf != NULL) {
- vim_snprintf((char *)errbuf, errbuflen,
+ vim_snprintf(errbuf, errbuflen,
_("E593: Need at least %d lines"), min_rows());
errmsg = errbuf;
}
@@ -4535,7 +4561,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf,
}
if (p_columns < MIN_COLUMNS && full_screen) {
if (errbuf != NULL) {
- vim_snprintf((char *)errbuf, errbuflen,
+ vim_snprintf(errbuf, errbuflen,
_("E594: Need at least %d columns"), MIN_COLUMNS);
errmsg = errbuf;
}
@@ -4651,7 +4677,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf,
}
check_redraw(options[opt_idx].flags);
- return (char *)errmsg;
+ return errmsg;
}
/// Trigger the OptionSet autocommand.
@@ -5184,7 +5210,7 @@ static void showoptions(int all, int opt_flags)
#define INC 20
#define GAP 3
- vimoption_T **items = xmalloc(sizeof(vimoption_T *) * PARAM_COUNT);
+ vimoption_T **items = xmalloc(sizeof(vimoption_T *) * OPTION_COUNT);
// Highlight title
if (opt_flags & OPT_GLOBAL) {
@@ -5198,6 +5224,7 @@ static void showoptions(int all, int opt_flags)
// Do the loop two times:
// 1. display the short items
// 2. display the long items (only strings and numbers)
+ // When "opt_flags" has OPT_ONECOLUMN do everything in run 2.
for (run = 1; run <= 2 && !got_int; run++) {
// collect the items in items[]
item_count = 0;
@@ -5208,7 +5235,7 @@ static void showoptions(int all, int opt_flags)
}
varp = NULL;
- if (opt_flags != 0) {
+ if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) != 0) {
if (p->indir != PV_NONE) {
varp = get_varp_scope(p, opt_flags);
}
@@ -5217,8 +5244,10 @@ static void showoptions(int all, int opt_flags)
}
if (varp != NULL
&& (all == 1 || (all == 0 && !optval_default(p, varp)))) {
- if (p->flags & P_BOOL) {
- len = 1; // a toggle option fits always
+ if (opt_flags & OPT_ONECOLUMN) {
+ len = Columns;
+ } else if (p->flags & P_BOOL) {
+ len = 1; // a toggle option fits always
} else {
option_value2string(p, opt_flags);
len = (int)STRLEN(p->fullname) + vim_strsize(NameBuff) + 1;
@@ -5748,6 +5777,10 @@ void unset_global_local_option(char *name, void *from)
set_chars_option((win_T *)from, &((win_T *)from)->w_p_fcs, true);
redraw_later((win_T *)from, NOT_VALID);
break;
+ case PV_VE:
+ clear_string_option(&((win_T *)from)->w_p_ve);
+ ((win_T *)from)->w_ve_flags = 0;
+ break;
}
}
@@ -5814,6 +5847,8 @@ static char_u *get_varp_scope(vimoption_T *p, int opt_flags)
return (char_u *)&(curwin->w_p_fcs);
case PV_LCS:
return (char_u *)&(curwin->w_p_lcs);
+ case PV_VE:
+ return (char_u *)&(curwin->w_p_ve);
}
return NULL; // "cannot happen"
}
@@ -5908,6 +5943,9 @@ static char_u *get_varp(vimoption_T *p)
case PV_LCS:
return *curwin->w_p_lcs != NUL
? (char_u *)&(curwin->w_p_lcs) : p->var;
+ case PV_VE:
+ return *curwin->w_p_ve != NUL
+ ? (char_u *)&curwin->w_p_ve : p->var;
case PV_ARAB:
return (char_u *)&(curwin->w_p_arab);
@@ -6136,6 +6174,7 @@ void win_copy_options(win_T *wp_from, win_T *wp_to)
{
copy_winopt(&wp_from->w_onebuf_opt, &wp_to->w_onebuf_opt);
copy_winopt(&wp_from->w_allbuf_opt, &wp_to->w_allbuf_opt);
+ didset_window_options(wp_to);
}
/// Copy the options from one winopt_T to another.
@@ -6148,6 +6187,8 @@ void copy_winopt(winopt_T *from, winopt_T *to)
to->wo_list = from->wo_list;
to->wo_nu = from->wo_nu;
to->wo_rnu = from->wo_rnu;
+ to->wo_ve = vim_strsave(from->wo_ve);
+ to->wo_ve_flags = from->wo_ve_flags;
to->wo_nuw = from->wo_nuw;
to->wo_rl = from->wo_rl;
to->wo_rlc = vim_strsave(from->wo_rlc);
@@ -6192,6 +6233,9 @@ void copy_winopt(winopt_T *from, winopt_T *to)
to->wo_fcs = vim_strsave(from->wo_fcs);
to->wo_lcs = vim_strsave(from->wo_lcs);
to->wo_winbl = from->wo_winbl;
+
+ // Copy the script context so that we know were the value was last set.
+ memmove(to->wo_script_ctx, from->wo_script_ctx, sizeof(to->wo_script_ctx));
check_winopt(to); // don't want NULL pointers
}
@@ -6224,6 +6268,7 @@ static void check_winopt(winopt_T *wop)
check_string_option(&wop->wo_winhl);
check_string_option(&wop->wo_fcs);
check_string_option(&wop->wo_lcs);
+ check_string_option(&wop->wo_ve);
}
/// Free the allocated memory inside a winopt_T.
@@ -6248,6 +6293,7 @@ void clear_winopt(winopt_T *wop)
clear_string_option(&wop->wo_winhl);
clear_string_option(&wop->wo_fcs);
clear_string_option(&wop->wo_lcs);
+ clear_string_option(&wop->wo_ve);
}
void didset_window_options(win_T *wp)
@@ -6262,11 +6308,30 @@ void didset_window_options(win_T *wp)
wp->w_grid_alloc.blending = wp->w_p_winbl > 0;
}
+/// Index into the options table for a buffer-local option enum.
+static int buf_opt_idx[BV_COUNT];
+#define COPY_OPT_SCTX(buf, bv) buf->b_p_script_ctx[bv] = options[buf_opt_idx[bv]].last_set
+
+/// Initialize buf_opt_idx[] if not done already.
+static void init_buf_opt_idx(void)
+{
+ static int did_init_buf_opt_idx = false;
+
+ if (did_init_buf_opt_idx) {
+ return;
+ }
+ did_init_buf_opt_idx = true;
+ for (int i = 0; options[i].fullname != NULL; i++) {
+ if (options[i].indir & PV_BUF) {
+ buf_opt_idx[options[i].indir & PV_MASK] = i;
+ }
+ }
+}
/// Copy global option values to local options for one buffer.
/// Used when creating a new buffer and sometimes when entering a buffer.
/// flags:
-/// BCO_ENTER We will enter the buf buffer.
+/// BCO_ENTER We will enter the buffer "buf".
/// BCO_ALWAYS Always copy the options, but only set b_p_initialized when
/// appropriate.
/// BCO_NOHELP Don't copy the values to a help buffer.
@@ -6302,11 +6367,12 @@ void buf_copy_options(buf_T *buf, int flags)
}
if (should_copy || (flags & BCO_ALWAYS)) {
- /* Don't copy the options specific to a help buffer when
- * BCO_NOHELP is given or the options were initialized already
- * (jumping back to a help file with CTRL-T or CTRL-O) */
- dont_do_help = ((flags & BCO_NOHELP) && buf->b_help)
- || buf->b_p_initialized;
+ memset(buf->b_p_script_ctx, 0, sizeof(buf->b_p_script_ctx));
+ init_buf_opt_idx();
+ // Don't copy the options specific to a help buffer when
+ // BCO_NOHELP is given or the options were initialized already
+ // (jumping back to a help file with CTRL-T or CTRL-O)
+ dont_do_help = ((flags & BCO_NOHELP) && buf->b_help) || buf->b_p_initialized;
if (dont_do_help) { // don't free b_p_isk
save_p_isk = buf->b_p_isk;
buf->b_p_isk = NULL;
@@ -6338,80 +6404,129 @@ void buf_copy_options(buf_T *buf, int flags)
}
buf->b_p_ai = p_ai;
+ COPY_OPT_SCTX(buf, BV_AI);
buf->b_p_ai_nopaste = p_ai_nopaste;
buf->b_p_sw = p_sw;
+ COPY_OPT_SCTX(buf, BV_SW);
buf->b_p_scbk = p_scbk;
+ COPY_OPT_SCTX(buf, BV_SCBK);
buf->b_p_tw = p_tw;
+ COPY_OPT_SCTX(buf, BV_TW);
buf->b_p_tw_nopaste = p_tw_nopaste;
buf->b_p_tw_nobin = p_tw_nobin;
buf->b_p_wm = p_wm;
+ COPY_OPT_SCTX(buf, BV_WM);
buf->b_p_wm_nopaste = p_wm_nopaste;
buf->b_p_wm_nobin = p_wm_nobin;
buf->b_p_bin = p_bin;
+ COPY_OPT_SCTX(buf, BV_BIN);
buf->b_p_bomb = p_bomb;
+ COPY_OPT_SCTX(buf, BV_BOMB);
buf->b_p_et = p_et;
+ COPY_OPT_SCTX(buf, BV_ET);
buf->b_p_fixeol = p_fixeol;
+ COPY_OPT_SCTX(buf, BV_FIXEOL);
buf->b_p_et_nobin = p_et_nobin;
buf->b_p_et_nopaste = p_et_nopaste;
buf->b_p_ml = p_ml;
+ COPY_OPT_SCTX(buf, BV_ML);
buf->b_p_ml_nobin = p_ml_nobin;
buf->b_p_inf = p_inf;
- buf->b_p_swf = cmdmod.noswapfile ? false : p_swf;
+ COPY_OPT_SCTX(buf, BV_INF);
+ if (cmdmod.noswapfile) {
+ buf->b_p_swf = false;
+ } else {
+ buf->b_p_swf = p_swf;
+ COPY_OPT_SCTX(buf, BV_SWF);
+ }
buf->b_p_cpt = vim_strsave(p_cpt);
+ COPY_OPT_SCTX(buf, BV_CPT);
#ifdef BACKSLASH_IN_FILENAME
buf->b_p_csl = vim_strsave(p_csl);
+ COPY_OPT_SCTX(buf, BV_CSL);
#endif
buf->b_p_cfu = vim_strsave(p_cfu);
+ COPY_OPT_SCTX(buf, BV_CFU);
buf->b_p_ofu = vim_strsave(p_ofu);
+ COPY_OPT_SCTX(buf, BV_OFU);
buf->b_p_tfu = vim_strsave(p_tfu);
+ COPY_OPT_SCTX(buf, BV_TFU);
buf->b_p_sts = p_sts;
+ COPY_OPT_SCTX(buf, BV_STS);
buf->b_p_sts_nopaste = p_sts_nopaste;
buf->b_p_vsts = vim_strsave(p_vsts);
+ COPY_OPT_SCTX(buf, BV_VSTS);
if (p_vsts && p_vsts != empty_option) {
- tabstop_set(p_vsts, &buf->b_p_vsts_array);
+ (void)tabstop_set(p_vsts, &buf->b_p_vsts_array);
} else {
- buf->b_p_vsts_array = 0;
+ buf->b_p_vsts_array = NULL;
}
buf->b_p_vsts_nopaste = p_vsts_nopaste
? vim_strsave(p_vsts_nopaste)
: NULL;
buf->b_p_com = vim_strsave(p_com);
+ COPY_OPT_SCTX(buf, BV_COM);
buf->b_p_cms = vim_strsave(p_cms);
+ COPY_OPT_SCTX(buf, BV_CMS);
buf->b_p_fo = vim_strsave(p_fo);
+ COPY_OPT_SCTX(buf, BV_FO);
buf->b_p_flp = vim_strsave(p_flp);
+ COPY_OPT_SCTX(buf, BV_FLP);
buf->b_p_nf = vim_strsave(p_nf);
+ COPY_OPT_SCTX(buf, BV_NF);
buf->b_p_mps = vim_strsave(p_mps);
+ COPY_OPT_SCTX(buf, BV_MPS);
buf->b_p_si = p_si;
+ COPY_OPT_SCTX(buf, BV_SI);
buf->b_p_channel = 0;
buf->b_p_ci = p_ci;
+ COPY_OPT_SCTX(buf, BV_CI);
buf->b_p_cin = p_cin;
+ COPY_OPT_SCTX(buf, BV_CIN);
buf->b_p_cink = vim_strsave(p_cink);
+ COPY_OPT_SCTX(buf, BV_CINK);
buf->b_p_cino = vim_strsave(p_cino);
+ COPY_OPT_SCTX(buf, BV_CINO);
// Don't copy 'filetype', it must be detected
buf->b_p_ft = empty_option;
buf->b_p_pi = p_pi;
+ COPY_OPT_SCTX(buf, BV_PI);
buf->b_p_cinw = vim_strsave(p_cinw);
+ COPY_OPT_SCTX(buf, BV_CINW);
buf->b_p_lisp = p_lisp;
+ COPY_OPT_SCTX(buf, BV_LISP);
// Don't copy 'syntax', it must be set
buf->b_p_syn = empty_option;
buf->b_p_smc = p_smc;
+ COPY_OPT_SCTX(buf, BV_SMC);
buf->b_s.b_syn_isk = empty_option;
buf->b_s.b_p_spc = vim_strsave(p_spc);
+ COPY_OPT_SCTX(buf, BV_SPC);
(void)compile_cap_prog(&buf->b_s);
buf->b_s.b_p_spf = vim_strsave(p_spf);
+ COPY_OPT_SCTX(buf, BV_SPF);
buf->b_s.b_p_spl = vim_strsave(p_spl);
+ COPY_OPT_SCTX(buf, BV_SPL);
buf->b_s.b_p_spo = vim_strsave(p_spo);
+ COPY_OPT_SCTX(buf, BV_SPO);
buf->b_p_inde = vim_strsave(p_inde);
+ COPY_OPT_SCTX(buf, BV_INDE);
buf->b_p_indk = vim_strsave(p_indk);
+ COPY_OPT_SCTX(buf, BV_INDK);
buf->b_p_fp = empty_option;
buf->b_p_fex = vim_strsave(p_fex);
+ COPY_OPT_SCTX(buf, BV_FEX);
buf->b_p_sua = vim_strsave(p_sua);
+ COPY_OPT_SCTX(buf, BV_SUA);
buf->b_p_keymap = vim_strsave(p_keymap);
+ COPY_OPT_SCTX(buf, BV_KMAP);
buf->b_kmap_state |= KEYMAP_INIT;
// This isn't really an option, but copying the langmap and IME
// state from the current buffer is better than resetting it.
buf->b_p_iminsert = p_iminsert;
+ COPY_OPT_SCTX(buf, BV_IMI);
buf->b_p_imsearch = p_imsearch;
+ COPY_OPT_SCTX(buf, BV_IMS);
// options that are normally global but also have a local value
// are not copied, start using the global value
@@ -6431,11 +6546,14 @@ void buf_copy_options(buf_T *buf, int flags)
buf->b_p_def = empty_option;
buf->b_p_inc = empty_option;
buf->b_p_inex = vim_strsave(p_inex);
+ COPY_OPT_SCTX(buf, BV_INEX);
buf->b_p_dict = empty_option;
buf->b_p_tsr = empty_option;
buf->b_p_tsrfu = empty_option;
buf->b_p_qe = vim_strsave(p_qe);
+ COPY_OPT_SCTX(buf, BV_QE);
buf->b_p_udf = p_udf;
+ COPY_OPT_SCTX(buf, BV_UDF);
buf->b_p_lw = empty_option;
buf->b_p_menc = empty_option;
@@ -6448,17 +6566,20 @@ void buf_copy_options(buf_T *buf, int flags)
if (dont_do_help) {
buf->b_p_isk = save_p_isk;
if (p_vts && p_vts != empty_option && !buf->b_p_vts_array) {
- tabstop_set(p_vts, &buf->b_p_vts_array);
+ (void)tabstop_set(p_vts, &buf->b_p_vts_array);
} else {
buf->b_p_vts_array = NULL;
}
} else {
buf->b_p_isk = vim_strsave(p_isk);
+ COPY_OPT_SCTX(buf, BV_ISK);
did_isk = true;
buf->b_p_ts = p_ts;
+ COPY_OPT_SCTX(buf, BV_TS);
buf->b_p_vts = vim_strsave(p_vts);
+ COPY_OPT_SCTX(buf, BV_VTS);
if (p_vts && p_vts != empty_option && !buf->b_p_vts_array) {
- tabstop_set(p_vts, &buf->b_p_vts_array);
+ (void)tabstop_set(p_vts, &buf->b_p_vts_array);
} else {
buf->b_p_vts_array = NULL;
}
@@ -6467,6 +6588,7 @@ void buf_copy_options(buf_T *buf, int flags)
clear_string_option(&buf->b_p_bt);
}
buf->b_p_ma = p_ma;
+ COPY_OPT_SCTX(buf, BV_MA);
}
}
@@ -7107,10 +7229,7 @@ static void paste_option_changed(void)
free_string_option(buf->b_p_vsts);
}
buf->b_p_vsts = empty_option;
- if (buf->b_p_vsts_array) {
- xfree(buf->b_p_vsts_array);
- }
- buf->b_p_vsts_array = 0;
+ XFREE_CLEAR(buf->b_p_vsts_array);
}
// set global options
@@ -7147,13 +7266,11 @@ static void paste_option_changed(void)
buf->b_p_vsts = buf->b_p_vsts_nopaste
? vim_strsave(buf->b_p_vsts_nopaste)
: empty_option;
- if (buf->b_p_vsts_array) {
- xfree(buf->b_p_vsts_array);
- }
+ xfree(buf->b_p_vsts_array);
if (buf->b_p_vsts && buf->b_p_vsts != empty_option) {
- tabstop_set(buf->b_p_vsts, &buf->b_p_vsts_array);
+ (void)tabstop_set(buf->b_p_vsts, &buf->b_p_vsts_array);
} else {
- buf->b_p_vsts_array = 0;
+ buf->b_p_vsts_array = NULL;
}
}
@@ -7469,6 +7586,7 @@ int check_ff_value(char_u *p)
// Set the integer values corresponding to the string setting of 'vartabstop'.
// "array" will be set, caller must free it if needed.
+// Return false for an error.
bool tabstop_set(char_u *var, long **array)
{
long valcount = 1;
@@ -7488,7 +7606,7 @@ bool tabstop_set(char_u *var, long **array)
if (cp != end) {
emsg(_(e_positive));
} else {
- emsg(_(e_invarg));
+ semsg(_(e_invarg2), cp);
}
return false;
}
@@ -7501,7 +7619,7 @@ bool tabstop_set(char_u *var, long **array)
valcount++;
continue;
}
- emsg(_(e_invarg));
+ semsg(_(e_invarg2), var);
return false;
}
@@ -7510,7 +7628,15 @@ bool tabstop_set(char_u *var, long **array)
t = 1;
for (cp = var; *cp != NUL;) {
- (*array)[t++] = atoi((char *)cp);
+ int n = atoi((char *)cp);
+
+ // Catch negative values, overflow and ridiculous big values.
+ if (n <= 0 || n > TABSTOP_MAX) {
+ semsg(_(e_invarg2), cp);
+ XFREE_CLEAR(*array);
+ return false;
+ }
+ (*array)[t++] = n;
while (*cp != NUL && *cp != ',') {
cp++;
}
@@ -7815,6 +7941,12 @@ unsigned int get_bkc_value(buf_T *buf)
return buf->b_bkc_flags ? buf->b_bkc_flags : bkc_flags;
}
+/// Get the local or global value of the 'virtualedit' flags.
+unsigned int get_ve_flags(void)
+{
+ return (curwin->w_ve_flags ? curwin->w_ve_flags : ve_flags) & ~(VE_NONE | VE_NONEU);
+}
+
/// Get the local or global value of 'showbreak'.
///
/// @param win If not NULL, the window to get the local option from; global
@@ -7994,7 +8126,6 @@ int win_signcol_count(win_T *wp)
/// Return the number of requested sign columns, based on user / configuration.
int win_signcol_configured(win_T *wp, int *is_fixed)
{
- int minimum = 0, maximum = 1, needed_signcols;
const char *scl = (const char *)wp->w_p_scl;
if (is_fixed) {
@@ -8007,7 +8138,6 @@ int win_signcol_configured(win_T *wp, int *is_fixed)
&& (wp->w_p_nu || wp->w_p_rnu)))) {
return 0;
}
- needed_signcols = buf_signcols(wp->w_buffer);
// yes or yes
if (!strncmp(scl, "yes:", 4)) {
@@ -8023,6 +8153,8 @@ int win_signcol_configured(win_T *wp, int *is_fixed)
*is_fixed = 0;
}
+ int minimum = 0, maximum = 1;
+
if (!strncmp(scl, "auto:", 5)) {
// Variable depending on a configuration
maximum = scl[5] - '0';
@@ -8033,6 +8165,7 @@ int win_signcol_configured(win_T *wp, int *is_fixed)
}
}
+ int needed_signcols = buf_signcols(wp->w_buffer, maximum);
int ret = MAX(minimum, MIN(maximum, needed_signcols));
assert(ret <= SIGN_SHOW_MAX);
return ret;
diff --git a/src/nvim/option.h b/src/nvim/option.h
index f7dbaafeec..9321dd5454 100644
--- a/src/nvim/option.h
+++ b/src/nvim/option.h
@@ -13,16 +13,16 @@
/// When OPT_GLOBAL and OPT_LOCAL are both missing, set both local and global
/// values, get local value.
typedef enum {
- OPT_FREE = 1, ///< Free old value if it was allocated.
- OPT_GLOBAL = 2, ///< Use global value.
- OPT_LOCAL = 4, ///< Use local value.
- OPT_MODELINE = 8, ///< Option in modeline.
- OPT_WINONLY = 16, ///< Only set window-local options.
- OPT_NOWIN = 32, ///< Don’t set window-local options.
- OPT_ONECOLUMN = 64, ///< list options one per line
- OPT_NO_REDRAW = 128, ///< ignore redraw flags on option
- OPT_SKIPRTP = 256, ///< "skiprtp" in 'sessionoptions'
- OPT_CLEAR = 512, ///< Clear local value of an option.
+ OPT_FREE = 0x01, ///< Free old value if it was allocated.
+ OPT_GLOBAL = 0x02, ///< Use global value.
+ OPT_LOCAL = 0x04, ///< Use local value.
+ OPT_MODELINE = 0x08, ///< Option in modeline.
+ OPT_WINONLY = 0x10, ///< Only set window-local options.
+ OPT_NOWIN = 0x20, ///< Don’t set window-local options.
+ OPT_ONECOLUMN = 0x40, ///< list options one per line
+ OPT_NO_REDRAW = 0x80, ///< ignore redraw flags on option
+ OPT_SKIPRTP = 0x100, ///< "skiprtp" in 'sessionoptions'
+ OPT_CLEAR = 0x200, ///< Clear local value of an option.
} OptionFlags;
#ifdef INCLUDE_GENERATED_DECLARATIONS
diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h
index 09c3bf3800..d88cd6b9b9 100644
--- a/src/nvim/option_defs.h
+++ b/src/nvim/option_defs.h
@@ -705,12 +705,14 @@ EXTERN int p_vb; ///< 'visualbell'
EXTERN char_u *p_ve; ///< 'virtualedit'
EXTERN unsigned ve_flags;
#ifdef IN_OPTION_C
-static char *(p_ve_values[]) = { "block", "insert", "all", "onemore", NULL };
+static char *(p_ve_values[]) = { "block", "insert", "all", "onemore", "none", "NONE", NULL };
#endif
-#define VE_BLOCK 5 // includes "all"
-#define VE_INSERT 6 // includes "all"
-#define VE_ALL 4
-#define VE_ONEMORE 8
+#define VE_BLOCK 5U // includes "all"
+#define VE_INSERT 6U // includes "all"
+#define VE_ALL 4U
+#define VE_ONEMORE 8U
+#define VE_NONE 16U // "none"
+#define VE_NONEU 32U // "NONE"
EXTERN long p_verbose; // 'verbose'
#ifdef IN_OPTION_C
char_u *p_vfile = (char_u *)""; // used before options are initialized
@@ -869,6 +871,7 @@ enum {
WV_LBR,
WV_NU,
WV_RNU,
+ WV_VE,
WV_NUW,
WV_PVW,
WV_RL,
@@ -900,6 +903,8 @@ enum {
#define SB_MAX 100000 // Maximum 'scrollback' value.
+#define TABSTOP_MAX 9999
+
/// Stores an identifier of a script or channel that last set an option.
typedef struct {
sctx_T script_ctx; /// script context where the option was last set
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index 28b4eb9fe2..e665ffd346 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -411,7 +411,7 @@ return {
},
{
full_name='compatible', abbreviation='cp',
- short_desc=N_("No description"),
+ short_desc=N_("No description"),
type='bool', scope={'global'},
redraw={'all_windows'},
varname='p_force_off',
@@ -665,14 +665,14 @@ return {
},
{
full_name='edcompatible', abbreviation='ed',
- short_desc=N_("No description"),
+ short_desc=N_("No description"),
type='bool', scope={'global'},
varname='p_force_off',
defaults={if_true=false}
},
{
full_name='emoji', abbreviation='emo',
- short_desc=N_("No description"),
+ short_desc=N_("No description"),
type='bool', scope={'global'},
redraw={'all_windows', 'ui_option'},
varname='p_emoji',
@@ -1184,7 +1184,7 @@ return {
},
{
full_name='inccommand', abbreviation='icm',
- short_desc=N_("Live preview of substitution"),
+ short_desc=N_("Live preview of substitution"),
type='string', scope={'global'},
redraw={'all_windows'},
varname='p_icm',
@@ -1846,7 +1846,7 @@ return {
type='number', scope={'global'},
secure=true,
varname='p_pyx',
- defaults={if_true=0}
+ defaults={if_true=3}
},
{
full_name='quickfixtextfunc', abbreviation='qftf',
@@ -2499,7 +2499,7 @@ return {
},
{
full_name='termencoding', abbreviation='tenc',
- short_desc=N_("Terminal encodig"),
+ short_desc=N_("Terminal encoding"),
type='string', scope={'global'},
defaults={if_true=""}
},
@@ -2622,7 +2622,7 @@ return {
},
{
full_name='ttyfast', abbreviation='tf',
- short_desc=N_("No description"),
+ short_desc=N_("No description"),
type='bool', scope={'global'},
no_mkrc=true,
varname='p_force_on',
@@ -2736,7 +2736,7 @@ return {
{
full_name='virtualedit', abbreviation='ve',
short_desc=N_("when to use virtual editing"),
- type='string', list='onecomma', scope={'global'},
+ type='string', list='onecomma', scope={'global', 'window'},
deny_duplicates=true,
redraw={'curswant'},
varname='p_ve',
diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c
index e9f44d2775..e9868d6b61 100644
--- a/src/nvim/os/env.c
+++ b/src/nvim/os/env.c
@@ -1111,10 +1111,9 @@ size_t home_replace(const buf_T *const buf, const char_u *src, char_u *const dst
*dst_p++ = '~';
}
- // If it's just the home directory, add "/".
- if (!vim_ispathsep(src[0]) && --dstlen > 0) {
- *dst_p++ = '/';
- }
+ // Do not add directory separator into dst, because dst is
+ // expected to just return the directory name without the
+ // directory separator '/'.
break;
}
if (p == homedir_env_mod) {
diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c
index 24c7678633..daf974ee74 100644
--- a/src/nvim/os/fs.c
+++ b/src/nvim/os/fs.c
@@ -40,8 +40,10 @@
bool did_try_to_free = false; \
uv_call_start: {} \
uv_fs_t req; \
+ fs_loop_lock(); \
ret = func(&fs_loop, &req, __VA_ARGS__); \
uv_fs_req_cleanup(&req); \
+ fs_loop_unlock(); \
if (ret == UV_ENOMEM && !did_try_to_free) { \
try_to_free_memory(); \
did_try_to_free = true; \
@@ -52,12 +54,27 @@ uv_call_start: {} \
// Many fs functions from libuv return that value on success.
static const int kLibuvSuccess = 0;
static uv_loop_t fs_loop;
+static uv_mutex_t fs_loop_mutex;
// Initialize the fs module
void fs_init(void)
{
uv_loop_init(&fs_loop);
+ uv_mutex_init_recursive(&fs_loop_mutex);
+}
+
+/// TODO(bfredl): some of these operations should
+/// be possible to do the private libuv loop of the
+/// thread, instead of contending the global fs loop
+void fs_loop_lock(void)
+{
+ uv_mutex_lock(&fs_loop_mutex);
+}
+
+void fs_loop_unlock(void)
+{
+ uv_mutex_unlock(&fs_loop_mutex);
}
@@ -98,9 +115,12 @@ bool os_isrealdir(const char *name)
FUNC_ATTR_NONNULL_ALL
{
uv_fs_t request;
+ fs_loop_lock();
if (uv_fs_lstat(&fs_loop, &request, name, NULL) != kLibuvSuccess) {
+ fs_loop_unlock();
return false;
}
+ fs_loop_unlock();
if (S_ISLNK(request.statbuf.st_mode)) {
return false;
} else {
@@ -738,7 +758,9 @@ static int os_stat(const char *name, uv_stat_t *statbuf)
return UV_EINVAL;
}
uv_fs_t request;
+ fs_loop_lock();
int result = uv_fs_stat(&fs_loop, &request, name, NULL);
+ fs_loop_unlock();
if (result == kLibuvSuccess) {
*statbuf = request.statbuf;
}
@@ -935,9 +957,11 @@ int os_mkdtemp(const char *template, char *path)
FUNC_ATTR_NONNULL_ALL
{
uv_fs_t request;
+ fs_loop_lock();
int result = uv_fs_mkdtemp(&fs_loop, &request, template, NULL);
+ fs_loop_unlock();
if (result == kLibuvSuccess) {
- STRNCPY(path, request.path, TEMP_FILE_PATH_MAXLEN);
+ xstrlcpy(path, request.path, TEMP_FILE_PATH_MAXLEN);
}
uv_fs_req_cleanup(&request);
return result;
@@ -962,7 +986,9 @@ int os_rmdir(const char *path)
bool os_scandir(Directory *dir, const char *path)
FUNC_ATTR_NONNULL_ALL
{
+ fs_loop_lock();
int r = uv_fs_scandir(&fs_loop, &dir->request, path, 0, NULL);
+ fs_loop_unlock();
if (r < 0) {
os_closedir(dir);
}
@@ -1023,7 +1049,9 @@ bool os_fileinfo_link(const char *path, FileInfo *file_info)
return false;
}
uv_fs_t request;
+ fs_loop_lock();
bool ok = uv_fs_lstat(&fs_loop, &request, path, NULL) == kLibuvSuccess;
+ fs_loop_unlock();
if (ok) {
file_info->stat = request.statbuf;
}
@@ -1041,6 +1069,7 @@ bool os_fileinfo_fd(int file_descriptor, FileInfo *file_info)
{
uv_fs_t request;
memset(file_info, 0, sizeof(*file_info));
+ fs_loop_lock();
bool ok = uv_fs_fstat(&fs_loop,
&request,
file_descriptor,
@@ -1049,6 +1078,7 @@ bool os_fileinfo_fd(int file_descriptor, FileInfo *file_info)
file_info->stat = request.statbuf;
}
uv_fs_req_cleanup(&request);
+ fs_loop_unlock();
return ok;
}
@@ -1165,6 +1195,7 @@ char *os_realpath(const char *name, char *buf)
FUNC_ATTR_NONNULL_ARG(1)
{
uv_fs_t request;
+ fs_loop_lock();
int result = uv_fs_realpath(&fs_loop, &request, name, NULL);
if (result == kLibuvSuccess) {
if (buf == NULL) {
@@ -1173,6 +1204,7 @@ char *os_realpath(const char *name, char *buf)
xstrlcpy(buf, request.ptr, MAXPATHL + 1);
}
uv_fs_req_cleanup(&request);
+ fs_loop_unlock();
return result == kLibuvSuccess ? buf : NULL;
}
diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c
index 3790eba212..54cfaee80a 100644
--- a/src/nvim/os/input.c
+++ b/src/nvim/os/input.c
@@ -234,9 +234,9 @@ size_t input_enqueue(String keys)
while (rbuffer_space(input_buffer) >= 19 && ptr < end) {
// A "<x>" form occupies at least 1 characters, and produces up
// to 19 characters (1 + 5 * 3 for the char and 3 for a modifier).
- // In the case of K_SPECIAL(0x80) or CSI(0x9B), 3 bytes are escaped and
- // needed, but since the keys are UTF-8, so the first byte cannot be
- // K_SPECIAL(0x80) or CSI(0x9B).
+ // In the case of K_SPECIAL(0x80), 3 bytes are escaped and needed,
+ // but since the keys are UTF-8, so the first byte cannot be
+ // K_SPECIAL(0x80).
uint8_t buf[19] = { 0 };
unsigned int new_size
= trans_special((const uint8_t **)&ptr, (size_t)(end - ptr), buf, true,
@@ -263,12 +263,8 @@ size_t input_enqueue(String keys)
continue;
}
- // copy the character, escaping CSI and K_SPECIAL
- if ((uint8_t)*ptr == CSI) {
- rbuffer_write(input_buffer, (char *)&(uint8_t){ K_SPECIAL }, 1);
- rbuffer_write(input_buffer, (char *)&(uint8_t){ KS_EXTRA }, 1);
- rbuffer_write(input_buffer, (char *)&(uint8_t){ KE_CSI }, 1);
- } else if ((uint8_t)*ptr == K_SPECIAL) {
+ // copy the character, escaping K_SPECIAL
+ if ((uint8_t)(*ptr) == K_SPECIAL) {
rbuffer_write(input_buffer, (char *)&(uint8_t){ K_SPECIAL }, 1);
rbuffer_write(input_buffer, (char *)&(uint8_t){ KS_SPECIAL }, 1);
rbuffer_write(input_buffer, (char *)&(uint8_t){ KE_FILLER }, 1);
diff --git a/src/nvim/path.c b/src/nvim/path.c
index 674d67e21a..d3aa5e5bf2 100644
--- a/src/nvim/path.c
+++ b/src/nvim/path.c
@@ -269,16 +269,17 @@ int vim_ispathlistsep(int c)
#endif
}
-/*
- * Shorten the path of a file from "~/foo/../.bar/fname" to "~/f/../.b/fname"
- * It's done in-place.
- */
-char_u *shorten_dir(char_u *str)
+/// Shorten the path of a file from "~/foo/../.bar/fname" to "~/f/../.b/fname"
+/// "trim_len" specifies how many characters to keep for each directory.
+/// Must be 1 or more.
+/// It's done in-place.
+void shorten_dir_len(char_u *str, int trim_len)
{
char_u *tail = path_tail(str);
char_u *d = str;
bool skip = false;
- for (char_u *s = str;; ++s) {
+ int dirchunk_len = 0;
+ for (char_u *s = str;; s++) {
if (s >= tail) { // copy the whole tail
*d++ = *s;
if (*s == NUL) {
@@ -287,10 +288,16 @@ char_u *shorten_dir(char_u *str)
} else if (vim_ispathsep(*s)) { // copy '/' and next char
*d++ = *s;
skip = false;
+ dirchunk_len = 0;
} else if (!skip) {
*d++ = *s; // copy next char
if (*s != '~' && *s != '.') { // and leading "~" and "."
- skip = true;
+ dirchunk_len++; // only count word chars for the size
+ // keep copying chars until we have our preferred length (or
+ // until the above if/else branches move us along)
+ if (dirchunk_len >= trim_len) {
+ skip = true;
+ }
}
int l = utfc_ptr2len(s);
while (--l > 0) {
@@ -298,7 +305,13 @@ char_u *shorten_dir(char_u *str)
}
}
}
- return str;
+}
+
+/// Shorten the path of a file from "~/foo/../.bar/fname" to "~/f/../.b/fname"
+/// It's done in-place.
+void shorten_dir(char_u *str)
+{
+ shorten_dir_len(str, 1);
}
/*
@@ -629,8 +642,7 @@ static size_t do_path_expand(garray_T *gap, const char_u *path, size_t wildoff,
} else if (path_end >= path + wildoff
&& (vim_strchr((char_u *)"*?[{~$", *path_end) != NULL
#ifndef WIN32
- || (!p_fic && (flags & EW_ICASE)
- && isalpha(utf_ptr2char(path_end)))
+ || (!p_fic && (flags & EW_ICASE) && mb_isalpha(utf_ptr2char(path_end)))
#endif
)) {
e = p;
@@ -1508,7 +1520,7 @@ void simplify_filename(char_u *filename)
p = filename;
#ifdef BACKSLASH_IN_FILENAME
- if (p[1] == ':') { // skip "x:"
+ if (p[0] != NUL && p[1] == ':') { // skip "x:"
p += 2;
}
#endif
@@ -1682,6 +1694,10 @@ char_u *find_file_name_in_path(char_u *ptr, size_t len, int options, long count,
char_u *file_name;
char_u *tofree = NULL;
+ if (len == 0) {
+ return NULL;
+ }
+
if ((options & FNAME_INCL) && *curbuf->b_p_inex != NUL) {
tofree = (char_u *)eval_includeexpr((char *)ptr, len);
if (tofree != NULL) {
@@ -1743,14 +1759,32 @@ int path_is_url(const char *p)
return 0;
}
-/// Check if "fname" starts with "name://". Return URL_SLASH if it does.
+/// Check if "fname" starts with "name://" or "name:\\".
///
/// @param fname is the filename to test
-/// @return URL_BACKSLASH for "name:\\", zero otherwise.
+/// @return URL_SLASH for "name://", URL_BACKSLASH for "name:\\", zero otherwise.
int path_with_url(const char *fname)
{
const char *p;
- for (p = fname; isalpha(*p); p++) {}
+
+ // We accept alphabetic characters and a dash in scheme part.
+ // RFC 3986 allows for more, but it increases the risk of matching
+ // non-URL text.
+
+ // first character must be alpha
+ if (!isalpha(*fname)) {
+ return 0;
+ }
+
+ // check body: alpha or dash
+ for (p = fname; (isalpha(*p) || (*p == '-')); p++) {}
+
+ // check last char is not a dash
+ if (p[-1] == '-') {
+ return 0;
+ }
+
+ // "://" or ":\\" must follow
return path_is_url(p);
}
@@ -2367,9 +2401,11 @@ static int path_to_absolute(const char_u *fname, char_u *buf, size_t len, int fo
int path_is_absolute(const char_u *fname)
{
#ifdef WIN32
+ if (*fname == NUL) {
+ return false;
+ }
// A name like "d:/foo" and "//server/share" is absolute
- return ((isalpha(fname[0]) && fname[1] == ':'
- && vim_ispathsep_nocolon(fname[2]))
+ return ((isalpha(fname[0]) && fname[1] == ':' && vim_ispathsep_nocolon(fname[2]))
|| (vim_ispathsep_nocolon(fname[0]) && fname[0] == fname[1]));
#else
// UNIX: This just checks if the file name starts with '/' or '~'.
diff --git a/src/nvim/plines.c b/src/nvim/plines.c
index a061f76f34..a572f747df 100644
--- a/src/nvim/plines.c
+++ b/src/nvim/plines.c
@@ -83,7 +83,7 @@ int plines_win_nofill(win_T *wp, linenr_T lnum, bool winheight)
return 1;
}
- // A folded lines is handled just like an empty line.
+ // Folded lines are handled just like an empty line.
if (lineFolded(wp, lnum)) {
return 1;
}
diff --git a/src/nvim/po/ja.euc-jp.po b/src/nvim/po/ja.euc-jp.po
index 5dda7c59f5..9633bec9f2 100644
--- a/src/nvim/po/ja.euc-jp.po
+++ b/src/nvim/po/ja.euc-jp.po
@@ -1644,15 +1644,6 @@ msgstr " [w]"
msgid " written"
msgstr " "
-msgid "E205: Patchmode: can't save original file"
-msgstr "E205: patchmode: ܥե¸Ǥޤ"
-
-msgid "E206: patchmode: can't touch empty original file"
-msgstr "E206: patchmode: θܥեtouchǤޤ"
-
-msgid "E207: Can't delete backup file"
-msgstr "E207: Хååץեäޤ"
-
msgid ""
"\n"
"WARNING: Original file may be lost or damaged\n"
diff --git a/src/nvim/po/ja.po b/src/nvim/po/ja.po
index a169bd3589..c363c00fa6 100644
--- a/src/nvim/po/ja.po
+++ b/src/nvim/po/ja.po
@@ -1644,15 +1644,6 @@ msgstr " [w]"
msgid " written"
msgstr " 書込み"
-msgid "E205: Patchmode: can't save original file"
-msgstr "E205: patchmode: 原本ファイルを保存できません"
-
-msgid "E206: patchmode: can't touch empty original file"
-msgstr "E206: patchmode: 空の原本ファイルをtouchできません"
-
-msgid "E207: Can't delete backup file"
-msgstr "E207: バックアップファイルを消せません"
-
msgid ""
"\n"
"WARNING: Original file may be lost or damaged\n"
diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c
index da2ada791f..d7726409b5 100644
--- a/src/nvim/popupmnu.c
+++ b/src/nvim/popupmnu.c
@@ -291,7 +291,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
} else {
assert(Columns - pum_col - pum_scrollbar >= INT_MIN
&& Columns - pum_col - pum_scrollbar <= INT_MAX);
- pum_width = (int)(Columns - pum_col - pum_scrollbar);
+ pum_width = Columns - pum_col - pum_scrollbar;
}
if ((pum_width > max_width + pum_kind_width + pum_extra_width + 1)
@@ -352,12 +352,12 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
// not enough room, will use what we have
if (pum_rl) {
assert(Columns - 1 >= INT_MIN);
- pum_col = (int)(Columns - 1);
+ pum_col = Columns - 1;
} else {
pum_col = 0;
}
assert(Columns - 1 >= INT_MIN);
- pum_width = (int)(Columns - 1);
+ pum_width = Columns - 1;
} else {
if (max_width > p_pw) {
// truncate
@@ -369,7 +369,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
} else {
assert(Columns - max_width >= INT_MIN
&& Columns - max_width <= INT_MAX);
- pum_col = (int)(Columns - max_width);
+ pum_col = Columns - max_width;
}
pum_width = max_width - pum_scrollbar;
}
diff --git a/src/nvim/pos.h b/src/nvim/pos.h
index d17e27906e..51991ed314 100644
--- a/src/nvim/pos.h
+++ b/src/nvim/pos.h
@@ -16,7 +16,9 @@ typedef int colnr_T;
/// Maximal (invalid) line number
enum { MAXLNUM = 0x7fffffff, };
/// Maximal column number
-enum { MAXCOL = INT_MAX, };
+/// MAXCOL used to be INT_MAX, but with 64 bit ints that results in running
+/// out of memory when trying to allocate a very long line.
+enum { MAXCOL = 0x7fffffff, };
// Minimum line number
enum { MINLNUM = 1, };
// minimum column number
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index 32d0ebe8eb..7e29aed51b 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -131,7 +131,7 @@ struct qf_info_S {
static qf_info_T ql_info; // global quickfix list
static unsigned last_qf_id = 0; // Last Used quickfix list id
-#define FMT_PATTERNS 11 // maximum number of % recognized
+#define FMT_PATTERNS 13 // maximum number of % recognized
// Structure used to hold the info of one part of 'errorformat'
typedef struct efm_S efm_T;
@@ -209,6 +209,17 @@ typedef struct {
bool valid;
} qffields_T;
+/// :vimgrep command arguments
+typedef struct vgr_args_S {
+ long tomatch; ///< maximum number of matches to find
+ char_u *spat; ///< search pattern
+ int flags; ///< search modifier
+ char_u **fnames; ///< list of files to search
+ int fcount; ///< number of files
+ regmmatch_T regmatch; ///< compiled search pattern
+ char_u *qf_title; ///< quickfix list title
+} vgr_args_T;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "quickfix.c.generated.h"
#endif
@@ -321,22 +332,27 @@ int qf_init(win_T *wp, const char_u *restrict efile, char_u *restrict errorforma
// Maximum number of bytes allowed per line while reading an errorfile.
static const size_t LINE_MAXLEN = 4096;
+/// Patterns used. Keep in sync with qf_parse_fmt[].
static struct fmtpattern {
char_u convchar;
char *pattern;
} fmt_pat[FMT_PATTERNS] =
{
- { 'f', ".\\+" }, // only used when at end
- { 'n', "\\d\\+" },
- { 'l', "\\d\\+" },
- { 'c', "\\d\\+" },
- { 't', "." },
- { 'm', ".\\+" },
- { 'r', ".*" },
- { 'p', "[- .]*"}, // NOLINT(whitespace/tab)
- { 'v', "\\d\\+" },
- { 's', ".\\+" },
- { 'o', ".\\+" }
+ { 'f', ".\\+" }, // only used when at end
+ { 'n', "\\d\\+" }, // 1
+ { 'l', "\\d\\+" }, // 2
+ { 'e', "\\d\\+" }, // 3
+ { 'c', "\\d\\+" }, // 4
+ { 'k', "\\d\\+" }, // 5
+ { 't', "." }, // 6
+#define FMT_PATTERN_M 7
+ { 'm', ".\\+" }, // 7
+#define FMT_PATTERN_R 8
+ { 'r', ".*" }, // 8
+ { 'p', "[- \t.]*" }, // 9
+ { 'v', "\\d\\+" }, // 10
+ { 's', ".\\+" }, // 11
+ { 'o', ".\\+" } // 12
};
/// Convert an errorformat pattern to a regular expression pattern.
@@ -352,9 +368,9 @@ static char_u *efmpat_to_regpat(const char_u *efmpat, char_u *regpat, efm_T *efm
semsg(_("E372: Too many %%%c in format string"), *efmpat);
return NULL;
}
- if ((idx && idx < 6
+ if ((idx && idx < FMT_PATTERN_R
&& vim_strchr((char_u *)"DXOPQ", efminfo->prefix) != NULL)
- || (idx == 6
+ || (idx == FMT_PATTERN_R
&& vim_strchr((char_u *)"OPQ", efminfo->prefix) == NULL)) {
semsg(_("E373: Unexpected %%%c in format string"), *efmpat);
return NULL;
@@ -1277,7 +1293,7 @@ static int qf_parse_fmt_n(regmatch_T *rmp, int midx, qffields_T *fields)
return QF_OK;
}
-/// Parse the match for line number (%l') pattern in regmatch.
+/// Parse the match for line number ('%l') pattern in regmatch.
/// Return the matched value in "fields->lnum".
static int qf_parse_fmt_l(regmatch_T *rmp, int midx, qffields_T *fields)
{
@@ -1288,6 +1304,17 @@ static int qf_parse_fmt_l(regmatch_T *rmp, int midx, qffields_T *fields)
return QF_OK;
}
+/// Parse the match for end line number ('%e') pattern in regmatch.
+/// Return the matched value in "fields->end_lnum".
+static int qf_parse_fmt_e(regmatch_T *rmp, int midx, qffields_T *fields)
+{
+ if (rmp->startp[midx] == NULL) {
+ return QF_FAIL;
+ }
+ fields->end_lnum = atol((char *)rmp->startp[midx]);
+ return QF_OK;
+}
+
/// Parse the match for column number ('%c') pattern in regmatch.
/// Return the matched value in "fields->col".
static int qf_parse_fmt_c(regmatch_T *rmp, int midx, qffields_T *fields)
@@ -1299,6 +1326,17 @@ static int qf_parse_fmt_c(regmatch_T *rmp, int midx, qffields_T *fields)
return QF_OK;
}
+/// Parse the match for end line number ('%e') pattern in regmatch.
+/// Return the matched value in "fields->end_lnum".
+static int qf_parse_fmt_k(regmatch_T *rmp, int midx, qffields_T *fields)
+{
+ if (rmp->startp[midx] == NULL) {
+ return QF_FAIL;
+ }
+ fields->end_col = (int)atol((char *)rmp->startp[midx]);
+ return QF_OK;
+}
+
/// Parse the match for error type ('%t') pattern in regmatch.
/// Return the matched value in "fields->type".
static int qf_parse_fmt_t(regmatch_T *rmp, int midx, qffields_T *fields)
@@ -1431,14 +1469,17 @@ static int qf_parse_fmt_o(regmatch_T *rmp, int midx, qffields_T *fields)
/// 'errorformat' format pattern parser functions.
/// The '%f' and '%r' formats are parsed differently from other formats.
/// See qf_parse_match() for details.
+/// Keep in sync with fmt_pat[].
static int (*qf_parse_fmt[FMT_PATTERNS])(regmatch_T *, int, qffields_T *) = {
- NULL,
+ NULL, // %f
qf_parse_fmt_n,
qf_parse_fmt_l,
+ qf_parse_fmt_e,
qf_parse_fmt_c,
+ qf_parse_fmt_k,
qf_parse_fmt_t,
qf_parse_fmt_m,
- NULL,
+ NULL, // %r
qf_parse_fmt_p,
qf_parse_fmt_v,
qf_parse_fmt_s,
@@ -1474,13 +1515,13 @@ static int qf_parse_match(char_u *linebuf, size_t linelen, efm_T *fmt_ptr, regma
midx = (int)fmt_ptr->addr[i];
if (i == 0 && midx > 0) { // %f
status = qf_parse_fmt_f(regmatch, midx, fields, idx);
- } else if (i == 5) {
+ } else if (i == FMT_PATTERN_M) {
if (fmt_ptr->flags == '+' && !qf_multiscan) { // %+
qf_parse_fmt_plus(linebuf, linelen, fields);
} else if (midx > 0) { // %m
status = qf_parse_fmt_m(regmatch, midx, fields);
}
- } else if (i == 6 && midx > 0) { // %r
+ } else if (i == FMT_PATTERN_R && midx > 0) { // %r
status = qf_parse_fmt_r(regmatch, midx, tail);
} else if (midx > 0) { // others
status = (qf_parse_fmt[i])(regmatch, midx, fields);
@@ -1625,10 +1666,16 @@ static int qf_parse_multiline_pfx(int idx, qf_list_T *qfl, qffields_T *fields)
if (!qfprev->qf_lnum) {
qfprev->qf_lnum = fields->lnum;
}
+ if (!qfprev->qf_end_lnum) {
+ qfprev->qf_end_lnum = fields->end_lnum;
+ }
if (!qfprev->qf_col) {
qfprev->qf_col = fields->col;
qfprev->qf_viscol = fields->use_viscol;
}
+ if (!qfprev->qf_end_col) {
+ qfprev->qf_end_col = fields->end_col;
+ }
if (!qfprev->qf_fnum) {
qfprev->qf_fnum = qf_get_fnum(qfl, qfl->qf_directory,
*fields->namebuf || qfl->qf_directory
@@ -2975,7 +3022,7 @@ static void qf_jump_newwin(qf_info_T *qi, int dir, int errornr, int forceit, boo
if (retval != OK) {
if (opened_window) {
- win_close(curwin, true); // Close opened window
+ win_close(curwin, true, false); // Close opened window
}
if (qf_ptr != NULL && qf_ptr->qf_fnum != 0) {
// Couldn't open file, so put index back where it was. This could
@@ -3530,7 +3577,7 @@ void ex_cclose(exarg_T *eap)
// Find existing quickfix window and close it.
win = qf_find_win(qi);
if (win != NULL) {
- win_close(win, false);
+ win_close(win, false, false);
}
}
@@ -3604,7 +3651,7 @@ static int qf_open_new_cwindow(qf_info_T *qi, int height)
// win_split, so add a check to ensure that the win is still here
if (IS_LL_STACK(qi) && !win_valid(win)) {
// close the window that was supposed to be for the loclist
- win_close(curwin, false);
+ win_close(curwin, false, false);
return FAIL;
}
@@ -3947,7 +3994,7 @@ static int qf_buf_add_line(qf_list_T *qfl, buf_T *buf, linenr_T lnum, const qfli
int len;
buf_T *errbuf;
- // If the 'quickfixtextfunc' function returned an non-empty custom string
+ // If the 'quickfixtextfunc' function returned a non-empty custom string
// for this entry, then use it.
if (qftf_str != NULL && *qftf_str != NUL) {
STRLCPY(IObuff, qftf_str, IOSIZE);
@@ -4849,11 +4896,12 @@ static qfline_T *qf_find_closest_entry(qf_list_T *qfl, int bnr, const pos_T *pos
/// Get the nth quickfix entry below the specified entry. Searches forward in
/// the list. If linewise is true, then treat multiple entries on a single line
/// as one.
-static void qf_get_nth_below_entry(qfline_T *entry, linenr_T n, bool linewise, int *errornr)
+static void qf_get_nth_below_entry(qfline_T *entry_arg, linenr_T n, bool linewise, int *errornr)
FUNC_ATTR_NONNULL_ALL
{
+ qfline_T *entry = entry_arg;
+
while (n-- > 0 && !got_int) {
- // qfline_T *first_entry = entry;
int first_errornr = *errornr;
if (linewise) {
@@ -4864,9 +4912,6 @@ static void qf_get_nth_below_entry(qfline_T *entry, linenr_T n, bool linewise, i
if (entry->qf_next == NULL
|| entry->qf_next->qf_fnum != entry->qf_fnum) {
if (linewise) {
- // If multiple entries are on the same line, then use the first
- // entry
- // entry = first_entry;
*errornr = first_errornr;
}
break;
@@ -5194,49 +5239,93 @@ static bool vgr_qflist_valid(win_T *wp, qf_info_T *qi, unsigned qfid, char_u *ti
/// Search for a pattern in all the lines in a buffer and add the matching lines
/// to a quickfix list.
-static bool vgr_match_buflines(qf_list_T *qfl, char_u *fname, buf_T *buf, regmmatch_T *regmatch,
- long *tomatch, int duplicate_name, int flags)
- FUNC_ATTR_NONNULL_ARG(1, 3, 4, 5)
+static bool vgr_match_buflines(qf_list_T *qfl, char_u *fname, buf_T *buf, char_u *spat,
+ regmmatch_T *regmatch, long *tomatch, int duplicate_name, int flags)
+ FUNC_ATTR_NONNULL_ARG(1, 3, 4, 5, 6)
{
bool found_match = false;
for (long lnum = 1; lnum <= buf->b_ml.ml_line_count && *tomatch > 0; lnum++) {
colnr_T col = 0;
- while (vim_regexec_multi(regmatch, curwin, buf, lnum, col, NULL,
- NULL) > 0) {
- // Pass the buffer number so that it gets used even for a
- // dummy buffer, unless duplicate_name is set, then the
- // buffer will be wiped out below.
- if (qf_add_entry(qfl,
- NULL, // dir
- fname,
- NULL,
- duplicate_name ? 0 : buf->b_fnum,
- ml_get_buf(buf, regmatch->startpos[0].lnum + lnum,
- false),
- regmatch->startpos[0].lnum + lnum,
- regmatch->endpos[0].lnum + lnum,
- regmatch->startpos[0].col + 1,
- regmatch->endpos[0].col + 1,
- false, // vis_col
- NULL, // search pattern
- 0, // nr
- 0, // type
- true) // valid
- == QF_FAIL) {
- got_int = true;
- break;
- }
- found_match = true;
- if (--*tomatch == 0) {
- break;
- }
- if ((flags & VGR_GLOBAL) == 0 || regmatch->endpos[0].lnum > 0) {
- break;
+ if (!(flags & VGR_FUZZY)) {
+ // Regular expression match
+ while (vim_regexec_multi(regmatch, curwin, buf, lnum, col, NULL, NULL) > 0) {
+ // Pass the buffer number so that it gets used even for a
+ // dummy buffer, unless duplicate_name is set, then the
+ // buffer will be wiped out below.
+ if (qf_add_entry(qfl,
+ NULL, // dir
+ fname,
+ NULL,
+ duplicate_name ? 0 : buf->b_fnum,
+ ml_get_buf(buf, regmatch->startpos[0].lnum + lnum, false),
+ regmatch->startpos[0].lnum + lnum,
+ regmatch->endpos[0].lnum + lnum,
+ regmatch->startpos[0].col + 1,
+ regmatch->endpos[0].col + 1,
+ false, // vis_col
+ NULL, // search pattern
+ 0, // nr
+ 0, // type
+ true) // valid
+ == QF_FAIL) {
+ got_int = true;
+ break;
+ }
+ found_match = true;
+ if (--*tomatch == 0) {
+ break;
+ }
+ if ((flags & VGR_GLOBAL) == 0 || regmatch->endpos[0].lnum > 0) {
+ break;
+ }
+ col = regmatch->endpos[0].col + (col == regmatch->endpos[0].col);
+ if (col > (colnr_T)STRLEN(ml_get_buf(buf, lnum, false))) {
+ break;
+ }
}
- col = regmatch->endpos[0].col + (col == regmatch->endpos[0].col);
- if (col > (colnr_T)STRLEN(ml_get_buf(buf, lnum, false))) {
- break;
+ } else {
+ const size_t pat_len = STRLEN(spat);
+ char_u *const str = ml_get_buf(buf, lnum, false);
+ int score;
+ uint32_t matches[MAX_FUZZY_MATCHES];
+ const size_t sz = sizeof(matches) / sizeof(matches[0]);
+
+ // Fuzzy string match
+ while (fuzzy_match(str + col, spat, false, &score, matches, (int)sz) > 0) {
+ // Pass the buffer number so that it gets used even for a
+ // dummy buffer, unless duplicate_name is set, then the
+ // buffer will be wiped out below.
+ if (qf_add_entry(qfl,
+ NULL, // dir
+ fname,
+ NULL,
+ duplicate_name ? 0 : buf->b_fnum,
+ str,
+ lnum,
+ 0,
+ (colnr_T)matches[0] + col + 1,
+ 0,
+ false, // vis_col
+ NULL, // search pattern
+ 0, // nr
+ 0, // type
+ true) // valid
+ == QF_FAIL) {
+ got_int = true;
+ break;
+ }
+ found_match = true;
+ if (--*tomatch == 0) {
+ break;
+ }
+ if ((flags & VGR_GLOBAL) == 0) {
+ break;
+ }
+ col = (colnr_T)matches[pat_len - 1] + col + 1;
+ if (col > (colnr_T)STRLEN(str)) {
+ break;
+ }
}
}
line_breakcheck();
@@ -5249,7 +5338,7 @@ static bool vgr_match_buflines(qf_list_T *qfl, char_u *fname, buf_T *buf, regmma
}
/// Jump to the first match and update the directory.
-static void vgr_jump_to_match(qf_info_T *qi, int forceit, int *redraw_for_dummy,
+static void vgr_jump_to_match(qf_info_T *qi, int forceit, bool *redraw_for_dummy,
buf_T *first_match_buf, char_u *target_dir)
{
buf_T *buf = curbuf;
@@ -5284,104 +5373,72 @@ static bool existing_swapfile(const buf_T *buf)
return false;
}
-// ":vimgrep {pattern} file(s)"
-// ":vimgrepadd {pattern} file(s)"
-// ":lvimgrep {pattern} file(s)"
-// ":lvimgrepadd {pattern} file(s)"
-void ex_vimgrep(exarg_T *eap)
+/// Process :vimgrep command arguments. The command syntax is:
+///
+/// :{count}vimgrep /{pattern}/[g][j] {file} ...
+static int vgr_process_args(exarg_T *eap, vgr_args_T *args)
{
- regmmatch_T regmatch;
- int fcount;
- char_u **fnames;
- char_u *fname;
- char_u *s;
- char_u *p;
- int fi;
- qf_list_T *qfl;
- win_T *wp = NULL;
- buf_T *buf;
- int duplicate_name = FALSE;
- int using_dummy;
- int redraw_for_dummy = FALSE;
- int found_match;
- buf_T *first_match_buf = NULL;
- time_t seconds = 0;
- aco_save_T aco;
- int flags = 0;
- long tomatch;
- char_u *dirname_start = NULL;
- char_u *dirname_now = NULL;
- char_u *target_dir = NULL;
- char_u *au_name = NULL;
-
- au_name = vgr_get_auname(eap->cmdidx);
- if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name,
- curbuf->b_fname, true, curbuf)) {
- if (aborting()) {
- return;
- }
- }
+ memset(args, 0, sizeof(*args));
- qf_info_T *qi = qf_cmd_get_or_alloc_stack(eap, &wp);
+ args->regmatch.regprog = NULL;
+ args->qf_title = vim_strsave(qf_cmdtitle(*eap->cmdlinep));
if (eap->addr_count > 0) {
- tomatch = eap->line2;
+ args->tomatch = eap->line2;
} else {
- tomatch = MAXLNUM;
+ args->tomatch = MAXLNUM;
}
// Get the search pattern: either white-separated or enclosed in //
- regmatch.regprog = NULL;
- char_u *title = vim_strsave(qf_cmdtitle(*eap->cmdlinep));
- p = skip_vimgrep_pat(eap->arg, &s, &flags);
+ char_u *p = skip_vimgrep_pat(eap->arg, &args->spat, &args->flags);
if (p == NULL) {
emsg(_(e_invalpat));
- goto theend;
+ return FAIL;
}
- vgr_init_regmatch(&regmatch, s);
- if (regmatch.regprog == NULL) {
- goto theend;
+ vgr_init_regmatch(&args->regmatch, args->spat);
+ if (args->regmatch.regprog == NULL) {
+ return FAIL;
}
p = skipwhite(p);
if (*p == NUL) {
emsg(_("E683: File name missing or invalid pattern"));
- goto theend;
- }
-
- if ((eap->cmdidx != CMD_grepadd && eap->cmdidx != CMD_lgrepadd
- && eap->cmdidx != CMD_vimgrepadd && eap->cmdidx != CMD_lvimgrepadd)
- || qf_stack_empty(qi)) {
- // make place for a new list
- qf_new_list(qi, title);
+ return FAIL;
}
// Parse the list of arguments, wildcards have already been expanded.
- if (get_arglist_exp(p, &fcount, &fnames, true) == FAIL) {
- goto theend;
+ if (get_arglist_exp(p, &args->fcount, &args->fnames, true) == FAIL) {
+ return FAIL;
}
- if (fcount == 0) {
+ if (args->fcount == 0) {
emsg(_(e_nomatch));
- goto theend;
+ return FAIL;
}
- dirname_start = xmalloc(MAXPATHL);
- dirname_now = xmalloc(MAXPATHL);
+ return OK;
+}
+
+/// Search for a pattern in a list of files and populate the quickfix list with
+/// the matches.
+static int vgr_process_files(win_T *wp, qf_info_T *qi, vgr_args_T *cmd_args,
+ bool *redraw_for_dummy, buf_T **first_match_buf,
+ char_u **target_dir)
+{
+ int status = FAIL;
+ unsigned save_qfid = qf_get_curlist(qi)->qf_id;
+ bool duplicate_name = false;
+
+ char_u *dirname_start = xmalloc(MAXPATHL);
+ char_u *dirname_now = xmalloc(MAXPATHL);
// Remember the current directory, because a BufRead autocommand that does
// ":lcd %:p:h" changes the meaning of short path names.
os_dirname(dirname_start, MAXPATHL);
- incr_quickfix_busy();
-
- // Remember the current quickfix list identifier, so that we can check for
- // autocommands changing the current quickfix list.
- unsigned save_qfid = qf_get_curlist(qi)->qf_id;
-
- seconds = (time_t)0;
- for (fi = 0; fi < fcount && !got_int && tomatch > 0; fi++) {
- fname = path_try_shorten_fname(fnames[fi]);
+ time_t seconds = (time_t)0;
+ for (int fi = 0; fi < cmd_args->fcount && !got_int && cmd_args->tomatch > 0; fi++) {
+ char_u *fname = path_try_shorten_fname(cmd_args->fnames[fi]);
if (time(NULL) > seconds) {
// Display the file name every second or so, show the user we are
// working on it.
@@ -5389,13 +5446,13 @@ void ex_vimgrep(exarg_T *eap)
vgr_display_fname(fname);
}
- buf = buflist_findname_exp(fnames[fi]);
+ buf_T *buf = buflist_findname_exp(cmd_args->fnames[fi]);
+ bool using_dummy;
if (buf == NULL || buf->b_ml.ml_mfp == NULL) {
// Remember that a buffer with this name already exists.
duplicate_name = (buf != NULL);
- using_dummy = TRUE;
- redraw_for_dummy = TRUE;
-
+ using_dummy = true;
+ *redraw_for_dummy = true;
buf = vgr_load_dummy_buf(fname, dirname_start, dirname_now);
} else {
// Use existing, loaded buffer.
@@ -5404,11 +5461,10 @@ void ex_vimgrep(exarg_T *eap)
// Check whether the quickfix list is still valid. When loading a
// buffer above, autocommands might have changed the quickfix list.
- if (!vgr_qflist_valid(wp, qi, save_qfid, *eap->cmdlinep)) {
- FreeWild(fcount, fnames);
- decr_quickfix_busy();
+ if (!vgr_qflist_valid(wp, qi, save_qfid, cmd_args->qf_title)) {
goto theend;
}
+
save_qfid = qf_get_curlist(qi)->qf_id;
if (buf == NULL) {
@@ -5418,13 +5474,18 @@ void ex_vimgrep(exarg_T *eap)
} else {
// Try for a match in all lines of the buffer.
// For ":1vimgrep" look for first match only.
- found_match = vgr_match_buflines(qf_get_curlist(qi),
- fname, buf, &regmatch, &tomatch,
- duplicate_name, flags);
+ bool found_match = vgr_match_buflines(qf_get_curlist(qi),
+ fname,
+ buf,
+ cmd_args->spat,
+ &cmd_args->regmatch,
+ &cmd_args->tomatch,
+ duplicate_name,
+ cmd_args->flags);
if (using_dummy) {
- if (found_match && first_match_buf == NULL) {
- first_match_buf = buf;
+ if (found_match && *first_match_buf == NULL) {
+ *first_match_buf = buf;
}
if (duplicate_name) {
// Never keep a dummy buffer if there is another buffer
@@ -5444,8 +5505,8 @@ void ex_vimgrep(exarg_T *eap)
if (!found_match) {
wipe_dummy_buffer(buf, dirname_start);
buf = NULL;
- } else if (buf != first_match_buf
- || (flags & VGR_NOJUMP)
+ } else if (buf != *first_match_buf
+ || (cmd_args->flags & VGR_NOJUMP)
|| existing_swapfile(buf)) {
unload_dummy_buffer(buf, dirname_start);
// Keeping the buffer, remove the dummy flag.
@@ -5460,16 +5521,17 @@ void ex_vimgrep(exarg_T *eap)
// If the buffer is still loaded we need to use the
// directory we jumped to below.
- if (buf == first_match_buf
- && target_dir == NULL
+ if (buf == *first_match_buf
+ && *target_dir == NULL
&& STRCMP(dirname_start, dirname_now) != 0) {
- target_dir = vim_strsave(dirname_now);
+ *target_dir = vim_strsave(dirname_now);
}
// The buffer is still loaded, the Filetype autocommands
// need to be done now, in that buffer. And the modelines
// need to be done (again). But not the window-local
// options!
+ aco_save_T aco;
aucmd_prepbuf(&aco, buf);
apply_autocmds(EVENT_FILETYPE, buf->b_p_ft, buf->b_fname, true, buf);
do_modelines(OPT_NOWIN);
@@ -5479,9 +5541,58 @@ void ex_vimgrep(exarg_T *eap)
}
}
- FreeWild(fcount, fnames);
+ status = OK;
- qfl = qf_get_curlist(qi);
+theend:
+ xfree(dirname_now);
+ xfree(dirname_start);
+ return status;
+}
+
+/// ":vimgrep {pattern} file(s)"
+/// ":vimgrepadd {pattern} file(s)"
+/// ":lvimgrep {pattern} file(s)"
+/// ":lvimgrepadd {pattern} file(s)"
+void ex_vimgrep(exarg_T *eap)
+{
+ char_u *au_name = vgr_get_auname(eap->cmdidx);
+ if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name,
+ curbuf->b_fname, true, curbuf)) {
+ if (aborting()) {
+ return;
+ }
+ }
+
+ win_T *wp = NULL;
+ qf_info_T *qi = qf_cmd_get_or_alloc_stack(eap, &wp);
+ char_u *target_dir = NULL;
+ vgr_args_T args;
+ if (vgr_process_args(eap, &args) == FAIL) {
+ goto theend;
+ }
+
+ if ((eap->cmdidx != CMD_grepadd && eap->cmdidx != CMD_lgrepadd
+ && eap->cmdidx != CMD_vimgrepadd && eap->cmdidx != CMD_lvimgrepadd)
+ || qf_stack_empty(qi)) {
+ // make place for a new list
+ qf_new_list(qi, args.qf_title);
+ }
+
+ incr_quickfix_busy();
+
+ bool redraw_for_dummy = false;
+ buf_T *first_match_buf = NULL;
+ int status = vgr_process_files(wp, qi, &args, &redraw_for_dummy, &first_match_buf, &target_dir);
+
+ if (status != OK) {
+ FreeWild(args.fcount, args.fnames);
+ decr_quickfix_busy();
+ goto theend;
+ }
+
+ FreeWild(args.fcount, args.fnames);
+
+ qf_list_T *qfl = qf_get_curlist(qi);
qfl->qf_nonevalid = false;
qfl->qf_ptr = qfl->qf_start;
qfl->qf_index = 1;
@@ -5489,26 +5600,28 @@ void ex_vimgrep(exarg_T *eap)
qf_update_buffer(qi, NULL);
+ // Remember the current quickfix list identifier, so that we can check for
+ // autocommands changing the current quickfix list.
+ unsigned save_qfid = qf_get_curlist(qi)->qf_id;
+
if (au_name != NULL) {
apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, curbuf->b_fname, true, curbuf);
}
// The QuickFixCmdPost autocmd may free the quickfix list. Check the list
// is still valid.
- if (!qflist_valid(wp, save_qfid)
- || qf_restore_list(qi, save_qfid) == FAIL) {
+ if (!qflist_valid(wp, save_qfid) || qf_restore_list(qi, save_qfid) == FAIL) {
decr_quickfix_busy();
goto theend;
}
// Jump to first match.
if (!qf_list_empty(qf_get_curlist(qi))) {
- if ((flags & VGR_NOJUMP) == 0) {
- vgr_jump_to_match(qi, eap->forceit, &redraw_for_dummy, first_match_buf,
- target_dir);
+ if ((args.flags & VGR_NOJUMP) == 0) {
+ vgr_jump_to_match(qi, eap->forceit, &redraw_for_dummy, first_match_buf, target_dir);
}
} else {
- semsg(_(e_nomatch2), s);
+ semsg(_(e_nomatch2), args.spat);
}
decr_quickfix_busy();
@@ -5520,11 +5633,9 @@ void ex_vimgrep(exarg_T *eap)
}
theend:
- xfree(title);
- xfree(dirname_now);
- xfree(dirname_start);
+ xfree(args.qf_title);
xfree(target_dir);
- vim_regfree(regmatch.regprog);
+ vim_regfree(args.regmatch.regprog);
}
// Restore current working directory to "dirname_start" if they differ, taking
@@ -5656,7 +5767,7 @@ static void wipe_dummy_buffer(buf_T *buf, char_u *dirname_start)
if (firstwin->w_next != NULL) {
for (win_T *wp = firstwin; wp != NULL; wp = wp->w_next) {
if (wp->w_buffer == buf) {
- if (win_close(wp, false) == OK) {
+ if (win_close(wp, false, false) == OK) {
did_one = true;
}
break;
diff --git a/src/nvim/quickfix.h b/src/nvim/quickfix.h
index f5178e332a..0da43e436c 100644
--- a/src/nvim/quickfix.h
+++ b/src/nvim/quickfix.h
@@ -7,6 +7,7 @@
// flags for skip_vimgrep_pat()
#define VGR_GLOBAL 1
#define VGR_NOJUMP 2
+#define VGR_FUZZY 4
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "quickfix.h.generated.h"
diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c
index 45e580dbee..6a6c915094 100644
--- a/src/nvim/regexp.c
+++ b/src/nvim/regexp.c
@@ -2901,18 +2901,14 @@ static int peekchr(void)
{
int c = regparse[1];
- if (c == NUL)
- curchr = '\\'; /* trailing '\' */
- else if (
- c <= '~' && META_flags[c]
- ) {
- /*
- * META contains everything that may be magic sometimes,
- * except ^ and $ ("\^" and "\$" are only magic after
- * "\V"). We now fetch the next character and toggle its
- * magicness. Therefore, \ is so meta-magic that it is
- * not in META.
- */
+ if (c == NUL) {
+ curchr = '\\'; // trailing '\'
+ } else if (c <= '~' && META_flags[c]) {
+ // META contains everything that may be magic sometimes,
+ // except ^ and $ ("\^" and "\$" are only magic after
+ // "\V"). We now fetch the next character and toggle its
+ // magicness. Therefore, \ is so meta-magic that it is
+ // not in META.
curchr = -1;
prev_at_start = at_start;
at_start = false; // be able to say "/\*ptr"
@@ -3232,7 +3228,7 @@ typedef struct {
// The current match-position is remembered with these variables:
linenr_T lnum; ///< line number, relative to first line
char_u *line; ///< start of current line
- char_u *input; ///< current input, points into "regline"
+ char_u *input; ///< current input, points into "line"
int need_clear_subexpr; ///< subexpressions still need to be cleared
int need_clear_zsubexpr; ///< extmatch subexpressions still need to be
@@ -6538,11 +6534,16 @@ char_u *regtilde(char_u *source, int magic)
}
}
- xfree(reg_prev_sub);
- if (newsub != source) /* newsub was allocated, just keep it */
- reg_prev_sub = newsub;
- else /* no ~ found, need to save newsub */
- reg_prev_sub = vim_strsave(newsub);
+ // Only change reg_prev_sub when not previewing.
+ if (!(State & CMDPREVIEW)) {
+ xfree(reg_prev_sub);
+ if (newsub != source) { // newsub was allocated, just keep it
+ reg_prev_sub = newsub;
+ } else { // no ~ found, need to save newsub
+ reg_prev_sub = vim_strsave(newsub);
+ }
+ }
+
return newsub;
}
diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c
index 41c927eaa6..133858f113 100644
--- a/src/nvim/regexp_nfa.c
+++ b/src/nvim/regexp_nfa.c
@@ -2013,7 +2013,7 @@ static int nfa_regpiece(void)
// will emit NFA_STAR.
// Bail out if we can use the other engine, but only, when the
// pattern does not need the NFA engine like (e.g. [[:upper:]]\{2,\}
- // does not work with with characters > 8 bit with the BT engine)
+ // does not work with characters > 8 bit with the BT engine)
if ((nfa_re_flags & RE_AUTO)
&& (maxval > 500 || maxval > minval + 200)
&& (maxval != MAX_LIMIT && minval < 200)
@@ -4367,7 +4367,7 @@ static regsubs_T *addstate_here(
// First add the state(s) at the end, so that we know how many there are.
// Pass the listidx as offset (avoids adding another argument to
- // addstate().
+ // addstate()).
regsubs_T *r = addstate(l, state, subs, pim, -listidx - ADDSTATE_HERE_OFFSET);
if (r == NULL) {
return NULL;
@@ -6071,8 +6071,15 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
case NFA_MARK_GT:
case NFA_MARK_LT:
{
+ size_t col = rex.input - rex.line;
pos_T *pos = getmark_buf(rex.reg_buf, t->state->val, false);
+ // Line may have been freed, get it again.
+ if (REG_MULTI) {
+ rex.line = reg_getline(rex.lnum);
+ rex.input = rex.line + col;
+ }
+
// Compare the mark position to the match position, if the mark
// exists and mark is set in reg_buf.
if (pos != NULL && pos->lnum > 0) {
diff --git a/src/nvim/runtime.c b/src/nvim/runtime.c
index 1c04cb16b3..1102096b32 100644
--- a/src/nvim/runtime.c
+++ b/src/nvim/runtime.c
@@ -24,6 +24,13 @@
static bool runtime_search_path_valid = false;
static int *runtime_search_path_ref = NULL;
static RuntimeSearchPath runtime_search_path;
+static RuntimeSearchPath runtime_search_path_thread;
+static uv_mutex_t runtime_search_path_mutex;
+
+void runtime_init(void)
+{
+ uv_mutex_init(&runtime_search_path_mutex);
+}
/// ":runtime [what] {name}"
void ex_runtime(exarg_T *eap)
@@ -146,7 +153,7 @@ int do_in_path(char_u *path, char *name, int flags, DoInRuntimepathCB callback,
if (flags & DIP_ERR) {
semsg(_(e_dirnotf), basepath, name);
- } else if (p_verbose > 0) {
+ } else if (p_verbose > 1) {
verbose_enter();
smsg(_("not found in '%s': \"%s\""), basepath, name);
verbose_leave();
@@ -172,6 +179,17 @@ RuntimeSearchPath runtime_search_path_get_cached(int *ref)
return runtime_search_path;
}
+RuntimeSearchPath copy_runtime_search_path(const RuntimeSearchPath src)
+{
+ RuntimeSearchPath dst = KV_INITIAL_VALUE;
+ for (size_t j = 0; j < kv_size(src); j++) {
+ SearchPathItem src_item = kv_A(src, j);
+ kv_push(dst, ((SearchPathItem){ xstrdup(src_item.path), src_item.after, src_item.has_lua }));
+ }
+
+ return dst;
+}
+
void runtime_search_path_unref(RuntimeSearchPath path, int *ref)
FUNC_ATTR_NONNULL_ALL
{
@@ -268,7 +286,7 @@ int do_in_cached_path(char_u *name, int flags, DoInRuntimepathCB callback, void
if (!did_one && name != NULL) {
if (flags & DIP_ERR) {
semsg(_(e_dirnotf), "runtime path", name);
- } else if (p_verbose > 0) {
+ } else if (p_verbose > 1) {
verbose_enter();
smsg(_("not found in runtime path: \"%s\""), name);
verbose_leave();
@@ -302,15 +320,35 @@ ArrayOf(String) runtime_get_named(bool lua, Array pat, bool all)
{
int ref;
RuntimeSearchPath path = runtime_search_path_get_cached(&ref);
- ArrayOf(String) rv = ARRAY_DICT_INIT;
static char buf[MAXPATHL];
+ ArrayOf(String) rv = runtime_get_named_common(lua, pat, all, path, buf, sizeof buf);
+
+ runtime_search_path_unref(path, &ref);
+ return rv;
+}
+
+ArrayOf(String) runtime_get_named_thread(bool lua, Array pat, bool all)
+{
+ // TODO(bfredl): avoid contention between multiple worker threads?
+ uv_mutex_lock(&runtime_search_path_mutex);
+ static char buf[MAXPATHL];
+ ArrayOf(String) rv = runtime_get_named_common(lua, pat, all, runtime_search_path_thread,
+ buf, sizeof buf);
+ uv_mutex_unlock(&runtime_search_path_mutex);
+ return rv;
+}
+
+ArrayOf(String) runtime_get_named_common(bool lua, Array pat, bool all,
+ RuntimeSearchPath path, char *buf, size_t buf_len)
+{
+ ArrayOf(String) rv = ARRAY_DICT_INIT;
for (size_t i = 0; i < kv_size(path); i++) {
SearchPathItem *item = &kv_A(path, i);
if (lua) {
if (item->has_lua == kNone) {
- size_t size = (size_t)snprintf(buf, sizeof buf, "%s/lua/", item->path);
- item->has_lua = (size < sizeof buf && os_isdir((char_u *)buf)) ? kTrue : kFalse;
+ size_t size = (size_t)snprintf(buf, buf_len, "%s/lua/", item->path);
+ item->has_lua = (size < buf_len && os_isdir((char_u *)buf));
}
if (item->has_lua == kFalse) {
continue;
@@ -320,9 +358,9 @@ ArrayOf(String) runtime_get_named(bool lua, Array pat, bool all)
for (size_t j = 0; j < pat.size; j++) {
Object pat_item = pat.items[j];
if (pat_item.type == kObjectTypeString) {
- size_t size = (size_t)snprintf(buf, sizeof buf, "%s/%s",
+ size_t size = (size_t)snprintf(buf, buf_len, "%s/%s",
item->path, pat_item.data.string.data);
- if (size < sizeof buf) {
+ if (size < buf_len) {
if (os_file_is_readable(buf)) {
ADD(rv, STRING_OBJ(cstr_to_string(buf)));
if (!all) {
@@ -333,9 +371,7 @@ ArrayOf(String) runtime_get_named(bool lua, Array pat, bool all)
}
}
}
-
done:
- runtime_search_path_unref(path, &ref);
return rv;
}
@@ -569,6 +605,10 @@ void runtime_search_path_validate(void)
runtime_search_path = runtime_search_path_build();
runtime_search_path_valid = true;
runtime_search_path_ref = NULL; // initially unowned
+ uv_mutex_lock(&runtime_search_path_mutex);
+ runtime_search_path_free(runtime_search_path_thread);
+ runtime_search_path_thread = copy_runtime_search_path(runtime_search_path);
+ uv_mutex_unlock(&runtime_search_path_mutex);
}
}
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index e62e3ca7bc..7fafe3dd6e 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -326,6 +326,18 @@ void redraw_buf_status_later(buf_T *buf)
}
}
+void redraw_win_signcol(win_T *wp)
+{
+ // If we can compute a change in the automatic sizing of the sign column
+ // under 'signcolumn=auto:X' and signs currently placed in the buffer, better
+ // figuring it out here so we can redraw the entire screen for it.
+ int scwidth = wp->w_scwidth;
+ wp->w_scwidth = win_signcol_count(wp);
+ if (wp->w_scwidth != scwidth) {
+ changed_line_abv_curs_win(wp);
+ }
+}
+
/// Redraw the parts of the screen that is marked for redraw.
///
/// Most code shouldn't call this directly, rather use redraw_later() and
@@ -790,12 +802,6 @@ static void win_update(win_T *wp, Providers *providers)
linenr_T mod_bot = 0;
int save_got_int;
-
- // If we can compute a change in the automatic sizing of the sign column
- // under 'signcolumn=auto:X' and signs currently placed in the buffer, better
- // figuring it out here so we can redraw the entire screen for it.
- buf_signcols(buf);
-
type = wp->w_redr_type;
if (type >= NOT_VALID) {
@@ -817,6 +823,8 @@ static void win_update(win_T *wp, Providers *providers)
return;
}
+ redraw_win_signcol(wp);
+
init_search_hl(wp);
/* Force redraw when width of 'number' or 'relativenumber' column
@@ -1215,19 +1223,40 @@ static void win_update(win_T *wp, Providers *providers)
*/
if (VIsual_mode == Ctrl_V) {
colnr_T fromc, toc;
- int save_ve_flags = ve_flags;
+ unsigned int save_ve_flags = curwin->w_ve_flags;
if (curwin->w_p_lbr) {
- ve_flags = VE_ALL;
+ curwin->w_ve_flags = VE_ALL;
}
getvcols(wp, &VIsual, &curwin->w_cursor, &fromc, &toc);
- ve_flags = save_ve_flags;
toc++;
+ curwin->w_ve_flags = save_ve_flags;
// Highlight to the end of the line, unless 'virtualedit' has
// "block".
- if (curwin->w_curswant == MAXCOL && !(ve_flags & VE_BLOCK)) {
- toc = MAXCOL;
+ if (curwin->w_curswant == MAXCOL) {
+ if (get_ve_flags() & VE_BLOCK) {
+ pos_T pos;
+ int cursor_above = curwin->w_cursor.lnum < VIsual.lnum;
+
+ // Need to find the longest line.
+ toc = 0;
+ pos.coladd = 0;
+ for (pos.lnum = curwin->w_cursor.lnum;
+ cursor_above ? pos.lnum <= VIsual.lnum : pos.lnum >= VIsual.lnum;
+ pos.lnum += cursor_above ? 1 : -1) {
+ colnr_T t;
+
+ pos.col = STRLEN(ml_get_buf(wp->w_buffer, pos.lnum, false));
+ getvvcol(wp, &pos, NULL, NULL, &t);
+ if (toc < t) {
+ toc = t;
+ }
+ }
+ toc++;
+ } else {
+ toc = MAXCOL;
+ }
}
if (fromc != wp->w_old_cursor_fcol
@@ -1825,7 +1854,7 @@ static void win_draw_end(win_T *wp, int c1, int c2, bool draw_margin, int row, i
win_hl_attr(wp, HLF_FC));
}
// draw the sign column
- int count = win_signcol_count(wp);
+ int count = wp->w_scwidth;
if (count > 0) {
n = win_fill_end(wp, ' ', ' ', n, win_signcol_width(wp) * count, row,
endrow, win_hl_attr(wp, HLF_SC));
@@ -1960,7 +1989,7 @@ static size_t fill_foldcolumn(char_u *p, win_T *wp, foldinfo_T foldinfo, linenr_
level = foldinfo.fi_level;
// If the column is too narrow, we start at the lowest level that
- // fits and use numbers to indicated the depth.
+ // fits and use numbers to indicate the depth.
first_level = level - fdc - closed + 1;
if (first_level < 1) {
first_level = 1;
@@ -2771,10 +2800,9 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc
draw_state = WL_SIGN;
/* Show the sign column when there are any signs in this
* buffer or when using Netbeans. */
- int count = win_signcol_count(wp);
- if (count > 0) {
+ if (wp->w_scwidth > 0) {
get_sign_display_info(false, wp, lnum, sattrs, row,
- startrow, filler_lines, filler_todo, count,
+ startrow, filler_lines, filler_todo,
&c_extra, &c_final, extra, sizeof(extra),
&p_extra, &n_extra,
&char_attr, &draw_state, &sign_idx);
@@ -2792,10 +2820,9 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc
// in 'lnum', then display the sign instead of the line
// number.
if (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u'
- && num_signs > 0) {
- int count = win_signcol_count(wp);
+ && num_signs > 0 && sign_get_attr(SIGN_TEXT, sattrs, 0, 1)) {
get_sign_display_info(true, wp, lnum, sattrs, row,
- startrow, filler_lines, filler_todo, count,
+ startrow, filler_lines, filler_todo,
&c_extra, &c_final, extra, sizeof(extra),
&p_extra, &n_extra,
&char_attr, &draw_state, &sign_idx);
@@ -3720,10 +3747,13 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc
tab_len += n_extra - tab_len;
}
- // if n_extra > 0, it gives the number of chars
+ // If n_extra > 0, it gives the number of chars
// to use for a tab, else we need to calculate the width
- // for a tab
+ // for a tab.
int len = (tab_len * utf_char2len(wp->w_p_lcs_chars.tab2));
+ if (wp->w_p_lcs_chars.tab3) {
+ len += utf_char2len(wp->w_p_lcs_chars.tab3);
+ }
if (n_extra > 0) {
len += n_extra - tab_len;
}
@@ -3740,13 +3770,11 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc
}
int lcs = wp->w_p_lcs_chars.tab2;
- // if tab3 is given, need to change the char
- // for tab
+ // if tab3 is given, use it for the last char
if (wp->w_p_lcs_chars.tab3 && i == tab_len - 1) {
lcs = wp->w_p_lcs_chars.tab3;
}
- utf_char2bytes(lcs, p);
- p += utf_char2len(lcs);
+ p += utf_char2bytes(lcs, p);
n_extra += utf_char2len(lcs) - (saved_nextra > 0 ? 1 : 0);
}
p_extra = p_extra_free;
@@ -3771,7 +3799,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc
// Make sure, the highlighting for the tab char will be
// correctly set further below (effectively reverts the
- // FIX_FOR_BOGSUCOLS macro.
+ // FIX_FOR_BOGSUCOLS macro).
if (n_extra == tab_len + vc_saved && wp->w_p_list
&& wp->w_p_lcs_chars.tab1) {
tab_len += vc_saved;
@@ -4130,7 +4158,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc
if (((wp->w_p_cuc
&& (int)wp->w_virtcol >= VCOL_HLC - eol_hl_off
&& (int)wp->w_virtcol <
- grid->Columns * (row - startrow + 1) + v
+ (long)grid->Columns * (row - startrow + 1) + v
&& lnum != wp->w_cursor.lnum)
|| draw_color_col || line_attr_lowprio || line_attr
|| diff_hlf != (hlf_T)0 || has_virttext)) {
@@ -4232,6 +4260,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc
// Show "extends" character from 'listchars' if beyond the line end and
// 'list' is set.
if (wp->w_p_lcs_chars.ext != NUL
+ && draw_state == WL_LINE
&& wp->w_p_list
&& !wp->w_p_wrap
&& filler_todo <= 0
@@ -4294,7 +4323,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc
// Store the character.
//
if (wp->w_p_rl && utf_char2cells(mb_c) > 1) {
- // A double-wide character is: put first halve in left cell.
+ // A double-wide character is: put first half in left cell.
off--;
col--;
}
@@ -4427,7 +4456,8 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc
*/
if ((wp->w_p_rl ? (col < 0) : (col >= grid->Columns))
&& foldinfo.fi_lines == 0
- && (*ptr != NUL
+ && (draw_state != WL_LINE
+ || *ptr != NUL
|| filler_todo > 0
|| (wp->w_p_list && wp->w_p_lcs_chars.eol != NUL
&& p_extra != at_end_str)
@@ -4637,8 +4667,8 @@ void screen_adjust_grid(ScreenGrid **grid, int *row_off, int *col_off)
static bool use_cursor_line_sign(win_T *wp, linenr_T lnum)
{
return wp->w_p_cul
- && lnum == wp->w_cursor.lnum
- && (wp->w_p_culopt_flags & CULOPT_NBR);
+ && lnum == wp->w_cursor.lnum
+ && (wp->w_p_culopt_flags & CULOPT_NBR);
}
// Get information needed to display the sign in line 'lnum' in window 'wp'.
@@ -4650,10 +4680,11 @@ static bool use_cursor_line_sign(win_T *wp, linenr_T lnum)
// @param[in, out] sign_idxp Index of the displayed sign
static void get_sign_display_info(bool nrcol, win_T *wp, linenr_T lnum, sign_attrs_T sattrs[],
int row, int startrow, int filler_lines, int filler_todo,
- int count, int *c_extrap, int *c_finalp, char_u *extra,
+ int *c_extrap, int *c_finalp, char_u *extra,
size_t extra_size, char_u **pp_extra, int *n_extrap,
int *char_attrp, int *draw_statep, int *sign_idxp)
{
+ int count = wp->w_scwidth;
// Draw cells with the sign value or blank.
*c_extrap = ' ';
*c_finalp = NUL;
@@ -4834,9 +4865,9 @@ static void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol,
end_dirty = col + char_cells;
// When writing a single-width character over a double-width
// character and at the end of the redrawn text, need to clear out
- // the right halve of the old character.
- // Also required when writing the right halve of a double-width
- // char over the left halve of an existing one
+ // the right half of the old character.
+ // Also required when writing the right half of a double-width
+ // char over the left half of an existing one
if (col + char_cells == endcol
&& ((char_cells == 1
&& grid_off2cells(grid, off_to, max_off_to) > 1)
@@ -5887,8 +5918,8 @@ void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, int col
}
off = grid->line_offset[row] + col;
- /* When drawing over the right halve of a double-wide char clear out the
- * left halve. Only needed in a terminal. */
+ // When drawing over the right half of a double-wide char clear out the
+ // left half. Only needed in a terminal.
if (grid != &default_grid && col == 0 && grid_invalid_row(grid, row)) {
// redraw the previous cell, make it empty
put_dirty_first = -1;
@@ -5950,9 +5981,9 @@ void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, int col
if (need_redraw) {
// When at the end of the text and overwriting a two-cell
// character with a one-cell character, need to clear the next
- // cell. Also when overwriting the left halve of a two-cell char
- // with the right halve of a two-cell char. Do this only once
- // (utf8_off2cells() may return 2 on the right halve).
+ // cell. Also when overwriting the left half of a two-cell char
+ // with the right half of a two-cell char. Do this only once
+ // (utf8_off2cells() may return 2 on the right half).
if (clear_next_cell) {
clear_next_cell = false;
} else if ((len < 0 ? ptr[mbyte_blen] == NUL
@@ -6351,9 +6382,9 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, int
}
for (int row = start_row; row < end_row; row++) {
- // When drawing over the right halve of a double-wide char clear
- // out the left halve. When drawing over the left halve of a
- // double wide-char clear out the right halve. Only needed in a
+ // When drawing over the right half of a double-wide char clear
+ // out the left half. When drawing over the left half of a
+ // double wide-char clear out the right half. Only needed in a
// terminal.
if (start_col > 0 && grid_fix_col(grid, start_col, row) != start_col) {
grid_puts_len(grid, (char_u *)" ", 1, row, start_col - 1, 0);
@@ -6762,7 +6793,7 @@ void grid_clear_line(ScreenGrid *grid, unsigned off, int width, bool valid)
void grid_invalidate(ScreenGrid *grid)
{
- (void)memset(grid->attrs, -1, grid->Rows * grid->Columns * sizeof(sattr_T));
+ (void)memset(grid->attrs, -1, sizeof(sattr_T) * grid->Rows * grid->Columns);
}
bool grid_invalid_row(ScreenGrid *grid, int row)
@@ -6891,8 +6922,6 @@ void grid_ins_lines(ScreenGrid *grid, int row, int line_count, int end, int col,
if (!grid->throttled) {
ui_call_grid_scroll(grid->handle, row, end, col, col+width, -line_count, 0);
}
-
- return;
}
/// delete lines on the screen and move lines up.
@@ -6943,8 +6972,6 @@ void grid_del_lines(ScreenGrid *grid, int row, int line_count, int end, int col,
if (!grid->throttled) {
ui_call_grid_scroll(grid->handle, row, end, col, col+width, line_count, 0);
}
-
- return;
}
@@ -7326,7 +7353,7 @@ void draw_tabline(void)
if (room > 0) {
// Get buffer name in NameBuff[]
get_trans_bufname(cwp->w_buffer);
- (void)shorten_dir(NameBuff);
+ shorten_dir(NameBuff);
len = vim_strsize(NameBuff);
p = NameBuff;
while (len > room) {
diff --git a/src/nvim/search.c b/src/nvim/search.c
index 906c9a6f47..682fa417a9 100644
--- a/src/nvim/search.c
+++ b/src/nvim/search.c
@@ -26,6 +26,7 @@
#include "nvim/func_attr.h"
#include "nvim/getchar.h"
#include "nvim/indent.h"
+#include "nvim/indent_c.h"
#include "nvim/main.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
@@ -2313,12 +2314,9 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel)
return (pos_T *)NULL; // never found it
}
-/*
- * Check if line[] contains a / / comment.
- * Return MAXCOL if not, otherwise return the column.
- * TODO: skip strings.
- */
-static int check_linecomment(const char_u *line)
+/// Check if line[] contains a / / comment.
+/// @returns MAXCOL if not, otherwise return the column.
+int check_linecomment(const char_u *line)
{
const char_u *p = line; // scan from start
// skip Lispish one-line comments
@@ -2338,7 +2336,8 @@ static int check_linecomment(const char_u *line)
in_str = true;
}
} else if (!in_str && ((p - line) < 2
- || (*(p - 1) != '\\' && *(p - 2) != '#'))) {
+ || (*(p - 1) != '\\' && *(p - 2) != '#'))
+ && !is_pos_in_string(line, (colnr_T)(p - line))) {
break; // found!
}
p++;
@@ -2348,9 +2347,11 @@ static int check_linecomment(const char_u *line)
}
} else {
while ((p = vim_strchr(p, '/')) != NULL) {
- // accept a double /, unless it's preceded with * and followed by *,
- // because * / / * is an end and start of a C comment
- if (p[1] == '/' && (p == line || p[-1] != '*' || p[2] != '*')) {
+ // Accept a double /, unless it's preceded with * and followed by *,
+ // because * / / * is an end and start of a C comment.
+ // Only accept the position if it is not inside a string.
+ if (p[1] == '/' && (p == line || p[-1] != '*' || p[2] != '*')
+ && !is_pos_in_string(line, (colnr_T)(p - line))) {
break;
}
++p;
@@ -4763,6 +4764,536 @@ the_end:
restore_last_search_pattern();
}
+/// Fuzzy string matching
+///
+/// Ported from the lib_fts library authored by Forrest Smith.
+/// https://github.com/forrestthewoods/lib_fts/tree/master/code
+///
+/// The following blog describes the fuzzy matching algorithm:
+/// https://www.forrestthewoods.com/blog/reverse_engineering_sublime_texts_fuzzy_match/
+///
+/// Each matching string is assigned a score. The following factors are checked:
+/// - Matched letter
+/// - Unmatched letter
+/// - Consecutively matched letters
+/// - Proximity to start
+/// - Letter following a separator (space, underscore)
+/// - Uppercase letter following lowercase (aka CamelCase)
+///
+/// Matched letters are good. Unmatched letters are bad. Matching near the start
+/// is good. Matching the first letter in the middle of a phrase is good.
+/// Matching the uppercase letters in camel case entries is good.
+///
+/// The score assigned for each factor is explained below.
+/// File paths are different from file names. File extensions may be ignorable.
+/// Single words care about consecutive matches but not separators or camel
+/// case.
+/// Score starts at 100
+/// Matched letter: +0 points
+/// Unmatched letter: -1 point
+/// Consecutive match bonus: +15 points
+/// First letter bonus: +15 points
+/// Separator bonus: +30 points
+/// Camel case bonus: +30 points
+/// Unmatched leading letter: -5 points (max: -15)
+///
+/// There is some nuance to this. Scores don’t have an intrinsic meaning. The
+/// score range isn’t 0 to 100. It’s roughly [50, 150]. Longer words have a
+/// lower minimum score due to unmatched letter penalty. Longer search patterns
+/// have a higher maximum score due to match bonuses.
+///
+/// Separator and camel case bonus is worth a LOT. Consecutive matches are worth
+/// quite a bit.
+///
+/// There is a penalty if you DON’T match the first three letters. Which
+/// effectively rewards matching near the start. However there’s no difference
+/// in matching between the middle and end.
+///
+/// There is not an explicit bonus for an exact match. Unmatched letters receive
+/// a penalty. So shorter strings and closer matches are worth more.
+typedef struct {
+ int idx; ///< used for stable sort
+ listitem_T *item;
+ int score;
+ list_T *lmatchpos;
+} fuzzyItem_T;
+
+/// bonus for adjacent matches; this is higher than SEPARATOR_BONUS so that
+/// matching a whole word is preferred.
+#define SEQUENTIAL_BONUS 40
+/// bonus if match occurs after a path separator
+#define PATH_SEPARATOR_BONUS 30
+/// bonus if match occurs after a word separator
+#define WORD_SEPARATOR_BONUS 25
+/// bonus if match is uppercase and prev is lower
+#define CAMEL_BONUS 30
+/// bonus if the first letter is matched
+#define FIRST_LETTER_BONUS 15
+/// penalty applied for every letter in str before the first match
+#define LEADING_LETTER_PENALTY -5
+/// maximum penalty for leading letters
+#define MAX_LEADING_LETTER_PENALTY -15
+/// penalty for every letter that doesn't match
+#define UNMATCHED_LETTER_PENALTY -1
+/// penalty for gap in matching positions (-2 * k)
+#define GAP_PENALTY -2
+/// Score for a string that doesn't fuzzy match the pattern
+#define SCORE_NONE -9999
+
+#define FUZZY_MATCH_RECURSION_LIMIT 10
+
+/// Compute a score for a fuzzy matched string. The matching character locations
+/// are in 'matches'.
+static int fuzzy_match_compute_score(const char_u *const str, const int strSz,
+ const uint32_t *const matches, const int numMatches)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ assert(numMatches > 0); // suppress clang "result of operation is garbage"
+ // Initialize score
+ int score = 100;
+
+ // Apply leading letter penalty
+ int penalty = LEADING_LETTER_PENALTY * matches[0];
+ if (penalty < MAX_LEADING_LETTER_PENALTY) {
+ penalty = MAX_LEADING_LETTER_PENALTY;
+ }
+ score += penalty;
+
+ // Apply unmatched penalty
+ const int unmatched = strSz - numMatches;
+ score += UNMATCHED_LETTER_PENALTY * unmatched;
+
+ // Apply ordering bonuses
+ for (int i = 0; i < numMatches; i++) {
+ const uint32_t currIdx = matches[i];
+
+ if (i > 0) {
+ const uint32_t prevIdx = matches[i - 1];
+
+ // Sequential
+ if (currIdx == prevIdx + 1) {
+ score += SEQUENTIAL_BONUS;
+ } else {
+ score += GAP_PENALTY * (currIdx - prevIdx);
+ }
+ }
+
+ // Check for bonuses based on neighbor character value
+ if (currIdx > 0) {
+ // Camel case
+ const char_u *p = str;
+ int neighbor;
+
+ for (uint32_t sidx = 0; sidx < currIdx; sidx++) {
+ neighbor = utf_ptr2char(p);
+ MB_PTR_ADV(p);
+ }
+ const int curr = utf_ptr2char(p);
+
+ if (mb_islower(neighbor) && mb_isupper(curr)) {
+ score += CAMEL_BONUS;
+ }
+
+ // Bonus if the match follows a separator character
+ if (neighbor == '/' || neighbor == '\\') {
+ score += PATH_SEPARATOR_BONUS;
+ } else if (neighbor == ' ' || neighbor == '_') {
+ score += WORD_SEPARATOR_BONUS;
+ }
+ } else {
+ // First letter
+ score += FIRST_LETTER_BONUS;
+ }
+ }
+ return score;
+}
+
+/// Perform a recursive search for fuzzy matching 'fuzpat' in 'str'.
+/// @return the number of matching characters.
+static int fuzzy_match_recursive(const char_u *fuzpat, const char_u *str, uint32_t strIdx,
+ int *const outScore, const char_u *const strBegin,
+ const int strLen, const uint32_t *const srcMatches,
+ uint32_t *const matches, const int maxMatches, int nextMatch,
+ int *const recursionCount)
+ FUNC_ATTR_NONNULL_ARG(1, 2, 4, 5, 8, 11) FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ // Recursion params
+ bool recursiveMatch = false;
+ uint32_t bestRecursiveMatches[MAX_FUZZY_MATCHES];
+ int bestRecursiveScore = 0;
+
+ // Count recursions
+ (*recursionCount)++;
+ if (*recursionCount >= FUZZY_MATCH_RECURSION_LIMIT) {
+ return 0;
+ }
+
+ // Detect end of strings
+ if (*fuzpat == NUL || *str == NUL) {
+ return 0;
+ }
+
+ // Loop through fuzpat and str looking for a match
+ bool first_match = true;
+ while (*fuzpat != NUL && *str != NUL) {
+ const int c1 = utf_ptr2char(fuzpat);
+ const int c2 = utf_ptr2char(str);
+
+ // Found match
+ if (mb_tolower(c1) == mb_tolower(c2)) {
+ // Supplied matches buffer was too short
+ if (nextMatch >= maxMatches) {
+ return 0;
+ }
+
+ // "Copy-on-Write" srcMatches into matches
+ if (first_match && srcMatches != NULL) {
+ memcpy(matches, srcMatches, nextMatch * sizeof(srcMatches[0]));
+ first_match = false;
+ }
+
+ // Recursive call that "skips" this match
+ uint32_t recursiveMatches[MAX_FUZZY_MATCHES];
+ int recursiveScore = 0;
+ const char_u *const next_char = str + utfc_ptr2len(str);
+ if (fuzzy_match_recursive(fuzpat, next_char, strIdx + 1, &recursiveScore, strBegin, strLen,
+ matches, recursiveMatches,
+ sizeof(recursiveMatches) / sizeof(recursiveMatches[0]), nextMatch,
+ recursionCount)) {
+ // Pick best recursive score
+ if (!recursiveMatch || recursiveScore > bestRecursiveScore) {
+ memcpy(bestRecursiveMatches, recursiveMatches,
+ MAX_FUZZY_MATCHES * sizeof(recursiveMatches[0]));
+ bestRecursiveScore = recursiveScore;
+ }
+ recursiveMatch = true;
+ }
+
+ // Advance
+ matches[nextMatch++] = strIdx;
+ MB_PTR_ADV(fuzpat);
+ }
+ MB_PTR_ADV(str);
+ strIdx++;
+ }
+
+ // Determine if full fuzpat was matched
+ const bool matched = *fuzpat == NUL;
+
+ // Calculate score
+ if (matched) {
+ *outScore = fuzzy_match_compute_score(strBegin, strLen, matches, nextMatch);
+ }
+
+ // Return best result
+ if (recursiveMatch && (!matched || bestRecursiveScore > *outScore)) {
+ // Recursive score is better than "this"
+ memcpy(matches, bestRecursiveMatches, maxMatches * sizeof(matches[0]));
+ *outScore = bestRecursiveScore;
+ return nextMatch;
+ } else if (matched) {
+ return nextMatch; // "this" score is better than recursive
+ }
+
+ return 0; // no match
+}
+
+/// fuzzy_match()
+///
+/// Performs exhaustive search via recursion to find all possible matches and
+/// match with highest score.
+/// Scores values have no intrinsic meaning. Possible score range is not
+/// normalized and varies with pattern.
+/// Recursion is limited internally (default=10) to prevent degenerate cases
+/// (pat_arg="aaaaaa" str="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").
+/// Uses char_u for match indices. Therefore patterns are limited to
+/// MAX_FUZZY_MATCHES characters.
+///
+/// @return true if 'pat_arg' matches 'str'. Also returns the match score in
+/// 'outScore' and the matching character positions in 'matches'.
+bool fuzzy_match(char_u *const str, const char_u *const pat_arg, const bool matchseq,
+ int *const outScore, uint32_t *const matches, const int maxMatches)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ const int len = mb_charlen(str);
+ bool complete = false;
+ int numMatches = 0;
+
+ *outScore = 0;
+
+ char_u *const save_pat = vim_strsave(pat_arg);
+ char_u *pat = save_pat;
+ char_u *p = pat;
+
+ // Try matching each word in 'pat_arg' in 'str'
+ while (true) {
+ if (matchseq) {
+ complete = true;
+ } else {
+ // Extract one word from the pattern (separated by space)
+ p = skipwhite(p);
+ if (*p == NUL) {
+ break;
+ }
+ pat = p;
+ while (*p != NUL && !ascii_iswhite(utf_ptr2char(p))) {
+ MB_PTR_ADV(p);
+ }
+ if (*p == NUL) { // processed all the words
+ complete = true;
+ }
+ *p = NUL;
+ }
+
+ int score = 0;
+ int recursionCount = 0;
+ const int matchCount
+ = fuzzy_match_recursive(pat, str, 0, &score, str, len, NULL, matches + numMatches,
+ maxMatches - numMatches, 0, &recursionCount);
+ if (matchCount == 0) {
+ numMatches = 0;
+ break;
+ }
+
+ // Accumulate the match score and the number of matches
+ *outScore += score;
+ numMatches += matchCount;
+
+ if (complete) {
+ break;
+ }
+
+ // try matching the next word
+ p++;
+ }
+
+ xfree(save_pat);
+ return numMatches != 0;
+}
+
+/// Sort the fuzzy matches in the descending order of the match score.
+/// For items with same score, retain the order using the index (stable sort)
+static int fuzzy_match_item_compare(const void *const s1, const void *const s2)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ const int v1 = ((const fuzzyItem_T *)s1)->score;
+ const int v2 = ((const fuzzyItem_T *)s2)->score;
+ const int idx1 = ((const fuzzyItem_T *)s1)->idx;
+ const int idx2 = ((const fuzzyItem_T *)s2)->idx;
+
+ return v1 == v2 ? (idx1 - idx2) : v1 > v2 ? -1 : 1;
+}
+
+/// Fuzzy search the string 'str' in a list of 'items' and return the matching
+/// strings in 'fmatchlist'.
+/// If 'matchseq' is true, then for multi-word search strings, match all the
+/// words in sequence.
+/// If 'items' is a list of strings, then search for 'str' in the list.
+/// If 'items' is a list of dicts, then either use 'key' to lookup the string
+/// for each item or use 'item_cb' Funcref function to get the string.
+/// If 'retmatchpos' is true, then return a list of positions where 'str'
+/// matches for each item.
+static void fuzzy_match_in_list(list_T *const items, char_u *const str, const bool matchseq,
+ const char_u *const key, Callback *const item_cb,
+ const bool retmatchpos, list_T *const fmatchlist)
+ FUNC_ATTR_NONNULL_ARG(2, 5, 7)
+{
+ const long len = tv_list_len(items);
+ if (len == 0) {
+ return;
+ }
+
+ fuzzyItem_T *const ptrs = xcalloc(len, sizeof(fuzzyItem_T));
+ long i = 0;
+ bool found_match = false;
+ uint32_t matches[MAX_FUZZY_MATCHES];
+
+ // For all the string items in items, get the fuzzy matching score
+ TV_LIST_ITER(items, li, {
+ ptrs[i].idx = i;
+ ptrs[i].item = li;
+ ptrs[i].score = SCORE_NONE;
+ char_u *itemstr = NULL;
+ typval_T rettv;
+ rettv.v_type = VAR_UNKNOWN;
+ const typval_T *const tv = TV_LIST_ITEM_TV(li);
+ if (tv->v_type == VAR_STRING) { // list of strings
+ itemstr = tv->vval.v_string;
+ } else if (tv->v_type == VAR_DICT && (key != NULL || item_cb->type != kCallbackNone)) {
+ // For a dict, either use the specified key to lookup the string or
+ // use the specified callback function to get the string.
+ if (key != NULL) {
+ itemstr = (char_u *)tv_dict_get_string(tv->vval.v_dict, (const char *)key, false);
+ } else {
+ typval_T argv[2];
+
+ // Invoke the supplied callback (if any) to get the dict item
+ tv->vval.v_dict->dv_refcount++;
+ argv[0].v_type = VAR_DICT;
+ argv[0].vval.v_dict = tv->vval.v_dict;
+ argv[1].v_type = VAR_UNKNOWN;
+ if (callback_call(item_cb, 1, argv, &rettv)) {
+ if (rettv.v_type == VAR_STRING) {
+ itemstr = rettv.vval.v_string;
+ }
+ }
+ tv_dict_unref(tv->vval.v_dict);
+ }
+ }
+
+ int score;
+ if (itemstr != NULL && fuzzy_match(itemstr, str, matchseq, &score, matches,
+ sizeof(matches) / sizeof(matches[0]))) {
+ // Copy the list of matching positions in itemstr to a list, if
+ // 'retmatchpos' is set.
+ if (retmatchpos) {
+ ptrs[i].lmatchpos = tv_list_alloc(kListLenMayKnow);
+ int j = 0;
+ const char_u *p = str;
+ while (*p != NUL) {
+ if (!ascii_iswhite(utf_ptr2char(p))) {
+ tv_list_append_number(ptrs[i].lmatchpos, matches[j]);
+ j++;
+ }
+ MB_PTR_ADV(p);
+ }
+ }
+ ptrs[i].score = score;
+ found_match = true;
+ }
+ i++;
+ tv_clear(&rettv);
+ });
+
+ if (found_match) {
+ // Sort the list by the descending order of the match score
+ qsort(ptrs, len, sizeof(fuzzyItem_T), fuzzy_match_item_compare);
+
+ // For matchfuzzy(), return a list of matched strings.
+ // ['str1', 'str2', 'str3']
+ // For matchfuzzypos(), return a list with three items.
+ // The first item is a list of matched strings. The second item
+ // is a list of lists where each list item is a list of matched
+ // character positions. The third item is a list of matching scores.
+ // [['str1', 'str2', 'str3'], [[1, 3], [1, 3], [1, 3]]]
+ list_T *l;
+ if (retmatchpos) {
+ const listitem_T *const li = tv_list_find(fmatchlist, 0);
+ assert(li != NULL && TV_LIST_ITEM_TV(li)->vval.v_list != NULL);
+ l = TV_LIST_ITEM_TV(li)->vval.v_list;
+ } else {
+ l = fmatchlist;
+ }
+
+ // Copy the matching strings with a valid score to the return list
+ for (i = 0; i < len; i++) {
+ if (ptrs[i].score == SCORE_NONE) {
+ break;
+ }
+ tv_list_append_tv(l, TV_LIST_ITEM_TV(ptrs[i].item));
+ }
+
+ // next copy the list of matching positions
+ if (retmatchpos) {
+ const listitem_T *li = tv_list_find(fmatchlist, -2);
+ assert(li != NULL && TV_LIST_ITEM_TV(li)->vval.v_list != NULL);
+ l = TV_LIST_ITEM_TV(li)->vval.v_list;
+ for (i = 0; i < len; i++) {
+ if (ptrs[i].score == SCORE_NONE) {
+ break;
+ }
+ tv_list_append_list(l, ptrs[i].lmatchpos);
+ }
+
+ // copy the matching scores
+ li = tv_list_find(fmatchlist, -1);
+ assert(li != NULL && TV_LIST_ITEM_TV(li)->vval.v_list != NULL);
+ l = TV_LIST_ITEM_TV(li)->vval.v_list;
+ for (i = 0; i < len; i++) {
+ if (ptrs[i].score == SCORE_NONE) {
+ break;
+ }
+ tv_list_append_number(l, ptrs[i].score);
+ }
+ }
+ }
+ xfree(ptrs);
+}
+
+/// Do fuzzy matching. Returns the list of matched strings in 'rettv'.
+/// If 'retmatchpos' is true, also returns the matching character positions.
+static void do_fuzzymatch(const typval_T *const argvars, typval_T *const rettv,
+ const bool retmatchpos)
+ FUNC_ATTR_NONNULL_ALL
+{
+ // validate and get the arguments
+ if (argvars[0].v_type != VAR_LIST || argvars[0].vval.v_list == NULL) {
+ semsg(_(e_listarg), retmatchpos ? "matchfuzzypos()" : "matchfuzzy()");
+ return;
+ }
+ if (argvars[1].v_type != VAR_STRING || argvars[1].vval.v_string == NULL) {
+ semsg(_(e_invarg2), tv_get_string(&argvars[1]));
+ return;
+ }
+
+ Callback cb = CALLBACK_NONE;
+ const char_u *key = NULL;
+ bool matchseq = false;
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ if (argvars[2].v_type != VAR_DICT || argvars[2].vval.v_dict == NULL) {
+ emsg(_(e_dictreq));
+ return;
+ }
+
+ // To search a dict, either a callback function or a key can be
+ // specified.
+ dict_T *const d = argvars[2].vval.v_dict;
+ const dictitem_T *const di = tv_dict_find(d, "key", -1);
+ if (di != NULL) {
+ if (di->di_tv.v_type != VAR_STRING || di->di_tv.vval.v_string == NULL
+ || *di->di_tv.vval.v_string == NUL) {
+ semsg(_(e_invarg2), tv_get_string(&di->di_tv));
+ return;
+ }
+ key = (const char_u *)tv_get_string(&di->di_tv);
+ } else if (!tv_dict_get_callback(d, "text_cb", -1, &cb)) {
+ semsg(_(e_invargval), "text_cb");
+ return;
+ }
+ if (tv_dict_find(d, "matchseq", -1) != NULL) {
+ matchseq = true;
+ }
+ }
+
+ // get the fuzzy matches
+ tv_list_alloc_ret(rettv, retmatchpos ? 3 : kListLenUnknown);
+ if (retmatchpos) {
+ // For matchfuzzypos(), a list with three items are returned. First
+ // item is a list of matching strings, the second item is a list of
+ // lists with matching positions within each string and the third item
+ // is the list of scores of the matches.
+ tv_list_append_list(rettv->vval.v_list, tv_list_alloc(kListLenUnknown));
+ tv_list_append_list(rettv->vval.v_list, tv_list_alloc(kListLenUnknown));
+ tv_list_append_list(rettv->vval.v_list, tv_list_alloc(kListLenUnknown));
+ }
+
+ fuzzy_match_in_list(argvars[0].vval.v_list, (char_u *)tv_get_string(&argvars[1]), matchseq, key,
+ &cb, retmatchpos, rettv->vval.v_list);
+ callback_free(&cb);
+}
+
+/// "matchfuzzy()" function
+void f_matchfuzzy(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ do_fuzzymatch(argvars, rettv, false);
+}
+
+/// "matchfuzzypos()" function
+void f_matchfuzzypos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ do_fuzzymatch(argvars, rettv, true);
+}
+
/// Find identifiers or defines in included files.
/// If p_ic && (compl_cont_status & CONT_SOL) then ptr must be in lowercase.
///
diff --git a/src/nvim/search.h b/src/nvim/search.h
index 15b8d41f39..53059cc1ea 100644
--- a/src/nvim/search.h
+++ b/src/nvim/search.h
@@ -55,6 +55,9 @@
#define SEARCH_STAT_DEF_MAX_COUNT 99
#define SEARCH_STAT_BUF_LEN 12
+/// Maximum number of characters that can be fuzzy matched
+#define MAX_FUZZY_MATCHES 256
+
/// Structure containing offset definition for the last search pattern
///
/// @note Only offset for the last search pattern is used, not for the last
diff --git a/src/nvim/shada.c b/src/nvim/shada.c
index e75a244031..b909888783 100644
--- a/src/nvim/shada.c
+++ b/src/nvim/shada.c
@@ -831,7 +831,7 @@ static int shada_read_file(const char *const file, const int flags)
ShaDaReadDef sd_reader;
const int of_ret = open_shada_file_for_reading(fname, &sd_reader);
- if (p_verbose > 0) {
+ if (p_verbose > 1) {
verbose_enter();
smsg(_("Reading ShaDa file \"%s\"%s%s%s%s"),
fname,
@@ -3036,7 +3036,7 @@ shada_write_file_nomerge: {}
return FAIL;
}
- if (p_verbose > 0) {
+ if (p_verbose > 1) {
verbose_enter();
smsg(_("Writing ShaDa file \"%s\""), fname);
verbose_leave();
diff --git a/src/nvim/sign.c b/src/nvim/sign.c
index a308df07d1..8b41781c98 100644
--- a/src/nvim/sign.c
+++ b/src/nvim/sign.c
@@ -1501,28 +1501,28 @@ static void sign_getinfo(sign_T *sp, dict_T *retdict)
if (p == NULL) {
p = "NONE";
}
- tv_dict_add_str(retdict, S_LEN("linehl"), (char *)p);
+ tv_dict_add_str(retdict, S_LEN("linehl"), p);
}
if (sp->sn_text_hl > 0) {
p = get_highlight_name_ext(NULL, sp->sn_text_hl - 1, false);
if (p == NULL) {
p = "NONE";
}
- tv_dict_add_str(retdict, S_LEN("texthl"), (char *)p);
+ tv_dict_add_str(retdict, S_LEN("texthl"), p);
}
if (sp->sn_cul_hl > 0) {
p = get_highlight_name_ext(NULL, sp->sn_cul_hl - 1, false);
if (p == NULL) {
p = "NONE";
}
- tv_dict_add_str(retdict, S_LEN("culhl"), (char *)p);
+ tv_dict_add_str(retdict, S_LEN("culhl"), p);
}
if (sp->sn_num_hl > 0) {
p = get_highlight_name_ext(NULL, sp->sn_num_hl - 1, false);
if (p == NULL) {
p = "NONE";
}
- tv_dict_add_str(retdict, S_LEN("numhl"), (char *)p);
+ tv_dict_add_str(retdict, S_LEN("numhl"), p);
}
}
diff --git a/src/nvim/spell.c b/src/nvim/spell.c
index bd31e98faa..1296d410f6 100644
--- a/src/nvim/spell.c
+++ b/src/nvim/spell.c
@@ -219,7 +219,7 @@ typedef struct {
#define SCORE_THRES3 100 // word count threshold for COMMON3
// When trying changed soundfold words it becomes slow when trying more than
-// two changes. With less then two changes it's slightly faster but we miss a
+// two changes. With less than two changes it's slightly faster but we miss a
// few good suggestions. In rare cases we need to try three of four changes.
#define SCORE_SFMAX1 200 // maximum score for first try
#define SCORE_SFMAX2 300 // maximum score for second try
@@ -1628,7 +1628,7 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att
}
// For spell checking: concatenate the start of the following line "line" into
-// "buf", blanking-out special characters. Copy less then "maxlen" bytes.
+// "buf", blanking-out special characters. Copy less than "maxlen" bytes.
// Keep the blanks at the start of the next line, this is used in win_line()
// to skip those bytes if the word was OK.
void spell_cat_line(char_u *buf, char_u *line, int maxlen)
@@ -4082,7 +4082,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so
// char, e.g., "thes," -> "these".
p = fword + sp->ts_fidx;
MB_PTR_BACK(fword, p);
- if (!spell_iswordp(p, curwin)) {
+ if (!spell_iswordp(p, curwin) && *preword != NUL) {
p = preword + STRLEN(preword);
MB_PTR_BACK(preword, p);
if (spell_iswordp(p, curwin)) {
@@ -6106,7 +6106,7 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res)
for (; ((ws = smp[n].sm_lead_w)[0] & 0xff) == (c & 0xff)
&& ws[0] != NUL; ++n) {
// Quickly skip entries that don't match the word. Most
- // entries are less then three chars, optimize for that.
+ // entries are less than three chars, optimize for that.
if (c != ws[0]) {
continue;
}
@@ -7057,7 +7057,7 @@ void spell_dump_compl(char_u *pat, int ic, Direction *dir, int dumpflags_arg)
arridx[depth] = idxs[n];
curi[depth] = 1;
- // Check if this characters matches with the pattern.
+ // Check if this character matches with the pattern.
// If not skip the whole tree below it.
// Always ignore case here, dump_word() will check
// proper case later. This isn't exactly right when
diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c
index 42bb3c61a5..d7b220b3f6 100644
--- a/src/nvim/spellfile.c
+++ b/src/nvim/spellfile.c
@@ -4395,7 +4395,7 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname)
//
// The table with character flags and the table for case folding.
// This makes sure the same characters are recognized as word characters
- // when generating an when using a spell file.
+ // when generating and when using a spell file.
// Skip this for ASCII, the table may conflict with the one used for
// 'encoding'.
// Also skip this for an .add.spl file, the main spell file must contain
@@ -5122,7 +5122,7 @@ static int sug_filltable(spellinfo_T *spin, wordnode_T *node, int startwordnr, g
wordnr++;
// Remove extra NUL entries, we no longer need them. We don't
- // bother freeing the nodes, the won't be reused anyway.
+ // bother freeing the nodes, they won't be reused anyway.
while (p->wn_sibling != NULL && p->wn_sibling->wn_byte == NUL) {
p->wn_sibling = p->wn_sibling->wn_sibling;
}
@@ -5654,7 +5654,7 @@ void spell_add_word(char_u *word, int len, SpellAddType what, int idx, bool undo
// If the .add file is edited somewhere, reload it.
if (buf != NULL) {
- buf_reload(buf, buf->b_orig_mode);
+ buf_reload(buf, buf->b_orig_mode, false);
}
redraw_all_later(SOME_VALID);
diff --git a/src/nvim/state.c b/src/nvim/state.c
index 68bc76660d..f9a3aaab7f 100644
--- a/src/nvim/state.c
+++ b/src/nvim/state.c
@@ -12,6 +12,7 @@
#include "nvim/lib/kvec.h"
#include "nvim/log.h"
#include "nvim/main.h"
+#include "nvim/option.h"
#include "nvim/option_defs.h"
#include "nvim/os/input.h"
#include "nvim/state.h"
@@ -38,10 +39,16 @@ void state_enter(VimState *s)
int key;
getkey:
- if (char_avail() || using_script() || input_available()) {
- // Don't block for events if there's a character already available for
- // processing. Characters can come from mappings, scripts and other
- // sources, so this scenario is very common.
+ // Expand mappings first by calling vpeekc() directly.
+ // - If vpeekc() returns non-NUL, there is a character already available for processing, so
+ // don't block for events. vgetc() may still block, in case of an incomplete UTF-8 sequence.
+ // - If vpeekc() returns NUL, vgetc() will block, and there are three cases:
+ // - There is no input available.
+ // - All of available input maps to an empty string.
+ // - There is an incomplete mapping.
+ // A blocking wait for a character should only be done in the third case, which is the only
+ // case of the three where typebuf.tb_len > 0 after vpeekc() returns NUL.
+ if (vpeekc() != NUL || typebuf.tb_len > 0) {
key = safe_vgetc();
} else if (!multiqueue_empty(main_loop.events)) {
// Event was made available after the last multiqueue_process_events call
@@ -54,9 +61,11 @@ getkey:
// mapping engine.
(void)os_inchar(NULL, 0, -1, 0, main_loop.events);
// If an event was put into the queue, we send K_EVENT directly.
- key = !multiqueue_empty(main_loop.events)
- ? K_EVENT
- : safe_vgetc();
+ if (!multiqueue_empty(main_loop.events)) {
+ key = K_EVENT;
+ } else {
+ goto getkey;
+ }
}
if (key == K_EVENT) {
@@ -107,15 +116,17 @@ void state_handle_k_event(void)
/// Return true if in the current mode we need to use virtual.
bool virtual_active(void)
{
+ unsigned int cur_ve_flags = get_ve_flags();
+
// While an operator is being executed we return "virtual_op", because
// VIsual_active has already been reset, thus we can't check for "block"
// being used.
if (virtual_op != kNone) {
return virtual_op;
}
- return ve_flags == VE_ALL
- || ((ve_flags & VE_BLOCK) && VIsual_active && VIsual_mode == Ctrl_V)
- || ((ve_flags & VE_INSERT) && (State & INSERT));
+ return cur_ve_flags == VE_ALL
+ || ((cur_ve_flags & VE_BLOCK) && VIsual_active && VIsual_mode == Ctrl_V)
+ || ((cur_ve_flags & VE_INSERT) && (State & INSERT));
}
/// VISUAL, SELECTMODE and OP_PENDING State are never set, they are equal to
@@ -180,7 +191,7 @@ char *get_mode(void)
buf[1] = 'x';
}
}
- } else if (State & CMDLINE) {
+ } else if ((State & CMDLINE) || exmode_active) {
buf[0] = 'c';
if (exmode_active) {
buf[1] = 'v';
diff --git a/src/nvim/strings.c b/src/nvim/strings.c
index e2a8108c45..291138ef23 100644
--- a/src/nvim/strings.c
+++ b/src/nvim/strings.c
@@ -1001,22 +1001,20 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
- str_arg);
}
if (fmt_spec == 'S') {
- if (min_field_width != 0) {
- min_field_width += (strlen(str_arg)
- - mb_string2cells((char_u *)str_arg));
- }
- if (precision) {
- char_u *p1;
- size_t i = 0;
-
- for (p1 = (char_u *)str_arg; *p1;
- p1 += utfc_ptr2len(p1)) {
- i += (size_t)utf_ptr2cells(p1);
- if (i > precision) {
- break;
- }
+ char_u *p1;
+ size_t i;
+
+ for (i = 0, p1 = (char_u *)str_arg; *p1; p1 += utfc_ptr2len(p1)) {
+ size_t cell = (size_t)utf_ptr2cells(p1);
+ if (precision_specified && i + cell > precision) {
+ break;
}
- str_arg_l = (size_t)(p1 - (char_u *)str_arg);
+ i += cell;
+ }
+
+ str_arg_l = (size_t)(p1 - (char_u *)str_arg);
+ if (min_field_width != 0) {
+ min_field_width += str_arg_l - i;
}
}
break;
diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c
index a9447165c2..db10b71d38 100644
--- a/src/nvim/syntax.c
+++ b/src/nvim/syntax.c
@@ -27,6 +27,7 @@
#include "nvim/highlight.h"
#include "nvim/indent_c.h"
#include "nvim/keymap.h"
+#include "nvim/lua/executor.h"
#include "nvim/macros.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
@@ -3112,9 +3113,9 @@ static void syn_cmd_conceal(exarg_T *eap, int syncing)
next = skiptowhite(arg);
if (*arg == NUL) {
if (curwin->w_s->b_syn_conceal) {
- msg(_("syntax conceal on"));
+ msg("syntax conceal on");
} else {
- msg(_("syntax conceal off"));
+ msg("syntax conceal off");
}
} else if (STRNICMP(arg, "on", 2) == 0 && next - arg == 2) {
curwin->w_s->b_syn_conceal = true;
@@ -3141,9 +3142,9 @@ static void syn_cmd_case(exarg_T *eap, int syncing)
next = skiptowhite(arg);
if (*arg == NUL) {
if (curwin->w_s->b_syn_ic) {
- msg(_("syntax case ignore"));
+ msg("syntax case ignore");
} else {
- msg(_("syntax case match"));
+ msg("syntax case match");
}
} else if (STRNICMP(arg, "match", 5) == 0 && next - arg == 5) {
curwin->w_s->b_syn_ic = false;
@@ -3168,9 +3169,9 @@ static void syn_cmd_foldlevel(exarg_T *eap, int syncing)
if (*arg == NUL) {
switch (curwin->w_s->b_syn_foldlevel) {
case SYNFLD_START:
- msg(_("syntax foldlevel start")); break;
+ msg("syntax foldlevel start"); break;
case SYNFLD_MINIMUM:
- msg(_("syntax foldlevel minimum")); break;
+ msg("syntax foldlevel minimum"); break;
default:
break;
}
@@ -3209,11 +3210,11 @@ static void syn_cmd_spell(exarg_T *eap, int syncing)
next = skiptowhite(arg);
if (*arg == NUL) {
if (curwin->w_s->b_syn_spell == SYNSPL_TOP) {
- msg(_("syntax spell toplevel"));
+ msg("syntax spell toplevel");
} else if (curwin->w_s->b_syn_spell == SYNSPL_NOTOP) {
- msg(_("syntax spell notoplevel"));
+ msg("syntax spell notoplevel");
} else {
- msg(_("syntax spell default"));
+ msg("syntax spell default");
}
} else if (STRNICMP(arg, "toplevel", 8) == 0 && next - arg == 8) {
curwin->w_s->b_syn_spell = SYNSPL_TOP;
@@ -3245,7 +3246,7 @@ static void syn_cmd_iskeyword(exarg_T *eap, int syncing)
if (*arg == NUL) {
msg_puts("\n");
if (curwin->w_s->b_syn_isk != empty_option) {
- msg_puts(_("syntax iskeyword "));
+ msg_puts("syntax iskeyword ");
msg_outtrans(curwin->w_s->b_syn_isk);
} else {
msg_outtrans((char_u *)_("syntax iskeyword not set"));
@@ -6714,6 +6715,100 @@ int lookup_color(const int idx, const bool foreground, TriState *const boldp)
return color;
}
+void set_hl_group(int id, HlAttrs attrs, Dict(highlight) *dict, int link_id)
+{
+ int idx = id - 1; // Index is ID minus one.
+
+ bool is_default = attrs.rgb_ae_attr & HL_DEFAULT;
+
+ // Return if "default" was used and the group already has settings
+ if (is_default && hl_has_settings(idx, true)) {
+ return;
+ }
+
+ HlGroup *g = &HL_TABLE()[idx];
+
+ if (link_id > 0) {
+ g->sg_cleared = false;
+ g->sg_link = link_id;
+ g->sg_script_ctx = current_sctx;
+ g->sg_script_ctx.sc_lnum += sourcing_lnum;
+ g->sg_set |= SG_LINK;
+ if (is_default) {
+ g->sg_deflink = link_id;
+ g->sg_deflink_sctx = current_sctx;
+ g->sg_deflink_sctx.sc_lnum += sourcing_lnum;
+ }
+ return;
+ }
+
+ g->sg_cleared = false;
+ g->sg_link = 0;
+ g->sg_gui = attrs.rgb_ae_attr;
+
+ g->sg_rgb_fg = attrs.rgb_fg_color;
+ g->sg_rgb_bg = attrs.rgb_bg_color;
+ g->sg_rgb_sp = attrs.rgb_sp_color;
+
+ struct {
+ char **dest; RgbValue val; Object name;
+ } cattrs[] = {
+ { &g->sg_rgb_fg_name, g->sg_rgb_fg, HAS_KEY(dict->fg) ? dict->fg : dict->foreground },
+ { &g->sg_rgb_bg_name, g->sg_rgb_bg, HAS_KEY(dict->bg) ? dict->bg : dict->background },
+ { &g->sg_rgb_sp_name, g->sg_rgb_sp, HAS_KEY(dict->sp) ? dict->sp : dict->special },
+ { NULL, -1, NIL },
+ };
+
+ char hex_name[8];
+ char *name;
+
+ for (int j = 0; cattrs[j].dest; j++) {
+ if (cattrs[j].val < 0) {
+ XFREE_CLEAR(*cattrs[j].dest);
+ continue;
+ }
+
+ if (cattrs[j].name.type == kObjectTypeString && cattrs[j].name.data.string.size) {
+ name = cattrs[j].name.data.string.data;
+ } else {
+ snprintf(hex_name, sizeof(hex_name), "#%06x", cattrs[j].val);
+ name = hex_name;
+ }
+
+ if (!*cattrs[j].dest
+ || STRCMP(*cattrs[j].dest, name) != 0) {
+ xfree(*cattrs[j].dest);
+ *cattrs[j].dest = xstrdup(name);
+ }
+ }
+
+ g->sg_cterm = attrs.cterm_ae_attr;
+ g->sg_cterm_bg = attrs.cterm_bg_color;
+ g->sg_cterm_fg = attrs.cterm_fg_color;
+ g->sg_cterm_bold = g->sg_cterm & HL_BOLD;
+ g->sg_blend = attrs.hl_blend;
+
+ g->sg_script_ctx = current_sctx;
+ g->sg_script_ctx.sc_lnum += sourcing_lnum;
+
+ // 'Normal' is special
+ if (STRCMP(g->sg_name_u, "NORMAL") == 0) {
+ cterm_normal_fg_color = g->sg_cterm_fg;
+ cterm_normal_bg_color = g->sg_cterm_bg;
+ normal_fg = g->sg_rgb_fg;
+ normal_bg = g->sg_rgb_bg;
+ normal_sp = g->sg_rgb_sp;
+ ui_default_colors_set();
+ } else {
+ g->sg_attr = hl_get_syn_attr(0, id, attrs);
+
+ // a cursor style uses this syn_id, make sure its attribute is updated.
+ if (cursor_mode_uses_syn_id(id)) {
+ ui_mode_info_set();
+ }
+ }
+}
+
/// Handle ":highlight" command
///
@@ -6825,6 +6920,7 @@ void do_highlight(const char *line, const bool forceit, const bool init)
hlgroup->sg_deflink = to_id;
hlgroup->sg_deflink_sctx = current_sctx;
hlgroup->sg_deflink_sctx.sc_lnum += sourcing_lnum;
+ nlua_set_sctx(&hlgroup->sg_deflink_sctx);
}
}
@@ -6845,6 +6941,7 @@ void do_highlight(const char *line, const bool forceit, const bool init)
hlgroup->sg_link = to_id;
hlgroup->sg_script_ctx = current_sctx;
hlgroup->sg_script_ctx.sc_lnum += sourcing_lnum;
+ nlua_set_sctx(&hlgroup->sg_script_ctx);
hlgroup->sg_cleared = false;
redraw_all_later(SOME_VALID);
@@ -7225,6 +7322,7 @@ void do_highlight(const char *line, const bool forceit, const bool init)
}
HL_TABLE()[idx].sg_script_ctx = current_sctx;
HL_TABLE()[idx].sg_script_ctx.sc_lnum += sourcing_lnum;
+ nlua_set_sctx(&HL_TABLE()[idx].sg_script_ctx);
}
xfree(key);
xfree(arg);
@@ -8765,6 +8863,22 @@ RgbValue name_to_color(const char *name)
return -1;
}
+int name_to_ctermcolor(const char *name)
+{
+ int i;
+ int off = TOUPPER_ASC(*name);
+ for (i = ARRAY_SIZE(color_names); --i >= 0;) {
+ if (off == color_names[i][0]
+ && STRICMP(name+1, color_names[i]+1) == 0) {
+ break;
+ }
+ }
+ if (i < 0) {
+ return -1;
+ }
+ TriState bold = kNone;
+ return lookup_color(i, false, &bold);
+}
/**************************************
* End of Highlighting stuff *
diff --git a/src/nvim/tag.c b/src/nvim/tag.c
index a10a2a0c32..32d72218c8 100644
--- a/src/nvim/tag.c
+++ b/src/nvim/tag.c
@@ -2960,7 +2960,7 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help)
} else {
RedrawingDisabled--;
if (postponed_split) { // close the window
- win_close(curwin, false);
+ win_close(curwin, false, false);
postponed_split = 0;
}
}
@@ -3408,7 +3408,7 @@ static void tagstack_push_items(win_T *wp, list_T *l)
if ((di = tv_dict_find(itemdict, "from", -1)) == NULL) {
continue;
}
- if (list2fpos(&di->di_tv, &mark, &fnum, NULL) != OK) {
+ if (list2fpos(&di->di_tv, &mark, &fnum, NULL, false) != OK) {
continue;
}
if ((tagname = (char_u *)tv_dict_get_string(itemdict, "tagname", true))
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index 6add541ad4..1c26e46a21 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -135,7 +135,6 @@ struct terminal {
int row, col;
bool visible;
} cursor;
- int pressed_button; // which mouse button is pressed
bool pending_resize; // pending width/height
bool color_set[16];
@@ -173,6 +172,11 @@ void terminal_teardown(void)
pmap_init(ptr_t, &invalidated_terminals);
}
+static void term_output_callback(const char *s, size_t len, void *user_data)
+{
+ terminal_send((Terminal *)user_data, (char *)s, len);
+}
+
// public API {{{
Terminal *terminal_open(buf_T *buf, TerminalOptions opts)
@@ -196,6 +200,7 @@ Terminal *terminal_open(buf_T *buf, TerminalOptions opts)
vterm_screen_set_callbacks(rv->vts, &vterm_screen_callbacks, rv);
vterm_screen_set_damage_merge(rv->vts, VTERM_DAMAGE_SCROLL);
vterm_screen_reset(rv->vts, 1);
+ vterm_output_set_callback(rv->vt, term_output_callback, rv);
// force a initial refresh of the screen to ensure the buffer will always
// have as many lines as screen rows when refresh_scrollback is called
rv->invalid_start = 0;
@@ -529,6 +534,10 @@ static int terminal_execute(VimState *state, int key)
do_cmdline(NULL, getcmdkeycmd, NULL, 0);
break;
+ case K_LUA:
+ map_execute_lua();
+ break;
+
case Ctrl_N:
if (s->got_bsl) {
return 0;
@@ -633,7 +642,6 @@ void terminal_paste(long count, char_u **y_array, size_t y_size)
return;
}
vterm_keyboard_start_paste(curbuf->terminal->vt);
- terminal_flush_output(curbuf->terminal);
size_t buff_len = STRLEN(y_array[0]);
char_u *buff = xmalloc(buff_len);
for (int i = 0; i < count; i++) { // -V756
@@ -664,14 +672,6 @@ void terminal_paste(long count, char_u **y_array, size_t y_size)
}
xfree(buff);
vterm_keyboard_end_paste(curbuf->terminal->vt);
- terminal_flush_output(curbuf->terminal);
-}
-
-void terminal_flush_output(Terminal *term)
-{
- size_t len = vterm_output_read(term->vt, term->textbuf,
- sizeof(term->textbuf));
- terminal_send(term, term->textbuf, len);
}
void terminal_send_key(Terminal *term, int c)
@@ -690,8 +690,6 @@ void terminal_send_key(Terminal *term, int c)
} else {
vterm_keyboard_unichar(term->vt, (uint32_t)c, mod);
}
-
- terminal_flush_output(term);
}
void terminal_receive(Terminal *term, char *data, size_t len)
@@ -1209,21 +1207,12 @@ static VTermKey convert_key(int key, VTermModifier *statep)
}
}
-static void mouse_action(Terminal *term, int button, int row, int col, bool drag, VTermModifier mod)
+static void mouse_action(Terminal *term, int button, int row, int col, bool pressed,
+ VTermModifier mod)
{
- if (term->pressed_button && (term->pressed_button != button || !drag)) {
- // release the previous button
- vterm_mouse_button(term->vt, term->pressed_button, 0, mod);
- term->pressed_button = 0;
- }
-
- // move the mouse
vterm_mouse_move(term->vt, row, col, mod);
-
- if (!term->pressed_button) {
- // press the button if not already pressed
- vterm_mouse_button(term->vt, button, 1, mod);
- term->pressed_button = button;
+ if (button) {
+ vterm_mouse_button(term->vt, button, pressed, mod);
}
}
@@ -1242,35 +1231,35 @@ static bool send_mouse_event(Terminal *term, int c)
// event in the terminal window and mouse events was enabled by the
// program. translate and forward the event
int button;
- bool drag = false;
+ bool pressed = false;
switch (c) {
case K_LEFTDRAG:
- drag = true; FALLTHROUGH;
case K_LEFTMOUSE:
+ pressed = true; FALLTHROUGH;
+ case K_LEFTRELEASE:
button = 1; break;
case K_MOUSEMOVE:
- drag = true; button = 0; break;
+ button = 0; break;
case K_MIDDLEDRAG:
- drag = true; FALLTHROUGH;
case K_MIDDLEMOUSE:
+ pressed = true; FALLTHROUGH;
+ case K_MIDDLERELEASE:
button = 2; break;
case K_RIGHTDRAG:
- drag = true; FALLTHROUGH;
case K_RIGHTMOUSE:
+ pressed = true; FALLTHROUGH;
+ case K_RIGHTRELEASE:
button = 3; break;
case K_MOUSEDOWN:
- button = 4; break;
+ pressed = true; button = 4; break;
case K_MOUSEUP:
- button = 5; break;
+ pressed = true; button = 5; break;
default:
return false;
}
- mouse_action(term, button, row, col - offset, drag, 0);
- size_t len = vterm_output_read(term->vt, term->textbuf,
- sizeof(term->textbuf));
- terminal_send(term, term->textbuf, len);
+ mouse_action(term, button, row, col - offset, pressed, 0);
return false;
}
@@ -1302,7 +1291,7 @@ static bool send_mouse_event(Terminal *term, int c)
}
end:
- ins_char_typebuf(c);
+ ins_char_typebuf(c, mod_mask);
return true;
}
diff --git a/src/nvim/testdir/check.vim b/src/nvim/testdir/check.vim
index 14bab33a2f..883f036fe1 100644
--- a/src/nvim/testdir/check.vim
+++ b/src/nvim/testdir/check.vim
@@ -12,9 +12,9 @@ endfunc
" Command to check for the absence of a feature.
command -nargs=1 CheckNotFeature call CheckNotFeature(<f-args>)
func CheckNotFeature(name)
- if !has(a:name, 1)
- throw 'Checking for non-existent feature ' .. a:name
- endif
+ " if !has(a:name, 1)
+ " throw 'Checking for non-existent feature ' .. a:name
+ " endif
if has(a:name)
throw 'Skipped: ' .. a:name .. ' feature present'
endif
@@ -63,6 +63,15 @@ func CheckUnix()
endif
endfunc
+" Command to check for not running on a BSD system.
+" TODO: using this checks should not be needed
+command CheckNotBSD call CheckNotBSD()
+func CheckNotBSD()
+ if has('bsd')
+ throw 'Skipped: does not work on BSD'
+ endif
+endfunc
+
" Command to check that making screendumps is supported.
" Caller must source screendump.vim
command CheckScreendump call CheckScreendump()
@@ -104,6 +113,14 @@ func CheckNotGui()
endif
endfunc
+" Command to check that test is not running as root
+command CheckNotRoot call CheckNotRoot()
+func CheckNotRoot()
+ if IsRoot()
+ throw 'Skipped: cannot run test as root'
+ endif
+endfunc
+
" Command to check that the current language is English
command CheckEnglish call CheckEnglish()
func CheckEnglish()
diff --git a/src/nvim/testdir/runnvim.sh b/src/nvim/testdir/runnvim.sh
index 25cb8437b4..72d88f9f93 100755
--- a/src/nvim/testdir/runnvim.sh
+++ b/src/nvim/testdir/runnvim.sh
@@ -65,9 +65,6 @@ main() {(
fi
fi
fi
- if test "$FAILED" = 1 ; then
- ci_fold start "$NVIM_TEST_CURRENT_SUITE/$test_name"
- fi
valgrind_check .
if test -n "$LOG_DIR" ; then
check_sanitizer "$LOG_DIR"
@@ -78,9 +75,6 @@ main() {(
fi
rm -f "$tlog"
if test "$FAILED" = 1 ; then
- ci_fold end "$NVIM_TEST_CURRENT_SUITE/$test_name"
- fi
- if test "$FAILED" = 1 ; then
echo "Test $test_name failed, see output above and summary for more details" >> test.log
# When Neovim crashed/aborted it might not have created messages.
# test.log itself is used as an indicator to exit non-zero in the Makefile.
diff --git a/src/nvim/testdir/setup.vim b/src/nvim/testdir/setup.vim
index fdae0697c3..15e3b31498 100644
--- a/src/nvim/testdir/setup.vim
+++ b/src/nvim/testdir/setup.vim
@@ -10,6 +10,7 @@ let s:did_load = 1
set backspace=
set directory^=.
set fillchars=vert:\|,fold:-
+set fsync
set laststatus=1
set listchars=eol:$
set joinspaces
diff --git a/src/nvim/testdir/shared.vim b/src/nvim/testdir/shared.vim
index f456ff4250..c2809844ac 100644
--- a/src/nvim/testdir/shared.vim
+++ b/src/nvim/testdir/shared.vim
@@ -343,6 +343,15 @@ func RunVimPiped(before, after, arguments, pipecmd)
return 1
endfunc
+func IsRoot()
+ if !has('unix')
+ return v:false
+ elseif $USER == 'root' || system('id -un') =~ '\<root\>'
+ return v:true
+ endif
+ return v:false
+endfunc
+
" Get all messages but drop the maintainer entry.
func GetMessages()
redir => result
diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim
index cc767a9bcf..5a3d1d56bb 100644
--- a/src/nvim/testdir/test_alot.vim
+++ b/src/nvim/testdir/test_alot.vim
@@ -27,6 +27,7 @@ source test_join.vim
source test_jumps.vim
source test_fileformat.vim
source test_filetype.vim
+source test_filetype_lua.vim
source test_lambda.vim
source test_menu.vim
source test_messages.vim
@@ -38,7 +39,8 @@ source test_put.vim
source test_rename.vim
source test_scroll_opt.vim
source test_shift.vim
-source test_sort.vim
+" Test fails on windows CI when using the MSVC compiler.
+" source test_sort.vim
source test_sha256.vim
source test_suspend.vim
source test_syn_attr.vim
diff --git a/src/nvim/testdir/test_alot_utf8.vim b/src/nvim/testdir/test_alot_utf8.vim
index 70f14320a6..77f5ede4c8 100644
--- a/src/nvim/testdir/test_alot_utf8.vim
+++ b/src/nvim/testdir/test_alot_utf8.vim
@@ -6,7 +6,6 @@
source test_charsearch_utf8.vim
source test_expr_utf8.vim
-source test_matchadd_conceal_utf8.vim
source test_mksession_utf8.vim
source test_regexp_utf8.vim
source test_source_utf8.vim
diff --git a/src/nvim/testdir/test_assert.vim b/src/nvim/testdir/test_assert.vim
index 52f243aaea..28c5948142 100644
--- a/src/nvim/testdir/test_assert.vim
+++ b/src/nvim/testdir/test_assert.vim
@@ -1,5 +1,55 @@
" Test that the methods used for testing work.
+func Test_assert_false()
+ call assert_equal(0, assert_false(0))
+ call assert_equal(0, assert_false(v:false))
+ call assert_equal(0, v:false->assert_false())
+
+ call assert_equal(1, assert_false(123))
+ call assert_match("Expected False but got 123", v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(1, 123->assert_false())
+ call assert_match("Expected False but got 123", v:errors[0])
+ call remove(v:errors, 0)
+endfunc
+
+func Test_assert_true()
+ call assert_equal(0, assert_true(1))
+ call assert_equal(0, assert_true(123))
+ call assert_equal(0, assert_true(v:true))
+ call assert_equal(0, v:true->assert_true())
+
+ call assert_equal(1, assert_true(0))
+ call assert_match("Expected True but got 0", v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(1, 0->assert_true())
+ call assert_match("Expected True but got 0", v:errors[0])
+ call remove(v:errors, 0)
+endfunc
+
+func Test_assert_equal()
+ let s = 'foo'
+ call assert_equal(0, assert_equal('foo', s))
+ let n = 4
+ call assert_equal(0, assert_equal(4, n))
+ let l = [1, 2, 3]
+ call assert_equal(0, assert_equal([1, 2, 3], l))
+ call assert_equal(v:_null_list, v:_null_list)
+ call assert_equal(v:_null_list, [])
+ call assert_equal([], v:_null_list)
+
+ let s = 'foo'
+ call assert_equal(1, assert_equal('bar', s))
+ call assert_match("Expected 'bar' but got 'foo'", v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal('XxxxxxxxxxxxxxxxxxxxxxX', 'XyyyyyyyyyyyyyyyyyyyyyyyyyX')
+ call assert_match("Expected 'X\\\\\\[x occurs 21 times]X' but got 'X\\\\\\[y occurs 25 times]X'", v:errors[0])
+ call remove(v:errors, 0)
+endfunc
+
func Test_assert_equalfile()
call assert_equal(1, assert_equalfile('abcabc', 'xyzxyz'))
call assert_match("E485: Can't read file abcabc", v:errors[0])
@@ -46,17 +96,129 @@ func Test_assert_equalfile()
call delete('Xtwo')
endfunc
+func Test_assert_notequal()
+ let n = 4
+ call assert_equal(0, assert_notequal('foo', n))
+ let s = 'foo'
+ call assert_equal(0, assert_notequal([1, 2, 3], s))
+
+ call assert_equal(1, assert_notequal('foo', s))
+ call assert_match("Expected not equal to 'foo'", v:errors[0])
+ call remove(v:errors, 0)
+endfunc
+
+func Test_assert_report()
+ call assert_equal(1, assert_report('something is wrong'))
+ call assert_match('something is wrong', v:errors[0])
+ call remove(v:errors, 0)
+ call assert_equal(1, 'also wrong'->assert_report())
+ call assert_match('also wrong', v:errors[0])
+ call remove(v:errors, 0)
+endfunc
+
+func Test_assert_exception()
+ try
+ nocommand
+ catch
+ call assert_equal(0, assert_exception('E492:'))
+ endtry
+
+ try
+ nocommand
+ catch
+ try
+ " illegal argument, get NULL for error
+ call assert_equal(1, assert_exception([]))
+ catch
+ call assert_equal(0, assert_exception('E730:'))
+ endtry
+ endtry
+endfunc
+
+func Test_wrong_error_type()
+ let save_verrors = v:errors
+ let v:['errors'] = {'foo': 3}
+ call assert_equal('yes', 'no')
+ let verrors = v:errors
+ let v:errors = save_verrors
+ call assert_equal(type([]), type(verrors))
+endfunc
+
+func Test_match()
+ call assert_equal(0, assert_match('^f.*b.*r$', 'foobar'))
+
+ call assert_equal(1, assert_match('bar.*foo', 'foobar'))
+ call assert_match("Pattern 'bar.*foo' does not match 'foobar'", v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(1, assert_match('bar.*foo', 'foobar', 'wrong'))
+ call assert_match('wrong', v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(1, 'foobar'->assert_match('bar.*foo', 'wrong'))
+ call assert_match('wrong', v:errors[0])
+ call remove(v:errors, 0)
+endfunc
+
+func Test_notmatch()
+ call assert_equal(0, assert_notmatch('foo', 'bar'))
+ call assert_equal(0, assert_notmatch('^foobar$', 'foobars'))
+
+ call assert_equal(1, assert_notmatch('foo', 'foobar'))
+ call assert_match("Pattern 'foo' does match 'foobar'", v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(1, 'foobar'->assert_notmatch('foo'))
+ call assert_match("Pattern 'foo' does match 'foobar'", v:errors[0])
+ call remove(v:errors, 0)
+endfunc
+
+func Test_assert_fail_fails()
+ call assert_equal(1, assert_fails('xxx', 'E12345'))
+ call assert_match("Expected 'E12345' but got 'E492:", v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(1, assert_fails('xxx', 'E9876', 'stupid'))
+ call assert_match("stupid: Expected 'E9876' but got 'E492:", v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(1, assert_fails('echo', '', 'echo command'))
+ call assert_match("command did not fail: echo command", v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(1, 'echo'->assert_fails('', 'echo command'))
+ call assert_match("command did not fail: echo command", v:errors[0])
+ call remove(v:errors, 0)
+endfunc
+
func Test_assert_fails_in_try_block()
try
call assert_equal(0, assert_fails('throw "error"'))
endtry
endfunc
+func Test_assert_beeps()
+ new
+ call assert_equal(0, assert_beeps('normal h'))
+
+ call assert_equal(1, assert_beeps('normal 0'))
+ call assert_match("command did not beep: normal 0", v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(0, 'normal h'->assert_beeps())
+ call assert_equal(1, 'normal 0'->assert_beeps())
+ call assert_match("command did not beep: normal 0", v:errors[0])
+ call remove(v:errors, 0)
+
+ bwipe
+endfunc
+
func Test_assert_inrange()
call assert_equal(0, assert_inrange(7, 7, 7))
call assert_equal(0, assert_inrange(5, 7, 5))
call assert_equal(0, assert_inrange(5, 7, 6))
call assert_equal(0, assert_inrange(5, 7, 7))
+
call assert_equal(1, assert_inrange(5, 7, 4))
call assert_match("Expected range 5 - 7, but got 4", v:errors[0])
call remove(v:errors, 0)
@@ -64,6 +226,12 @@ func Test_assert_inrange()
call assert_match("Expected range 5 - 7, but got 8", v:errors[0])
call remove(v:errors, 0)
+ call assert_equal(0, 5->assert_inrange(5, 7))
+ call assert_equal(0, 7->assert_inrange(5, 7))
+ call assert_equal(1, 8->assert_inrange(5, 7))
+ call assert_match("Expected range 5 - 7, but got 8", v:errors[0])
+ call remove(v:errors, 0)
+
call assert_fails('call assert_inrange(1, 1)', 'E119:')
if has('float')
@@ -83,6 +251,12 @@ func Test_assert_inrange()
endif
endfunc
+func Test_assert_with_msg()
+ call assert_equal('foo', 'bar', 'testing')
+ call assert_match("testing: Expected 'foo' but got 'bar'", v:errors[0])
+ call remove(v:errors, 0)
+endfunc
+
" Must be last.
func Test_zz_quit_detected()
" Verify that if a test function ends Vim the test script detects this.
diff --git a/src/nvim/testdir/test_autochdir.vim b/src/nvim/testdir/test_autochdir.vim
index 9ad727241e..53ed4617f7 100644
--- a/src/nvim/testdir/test_autochdir.vim
+++ b/src/nvim/testdir/test_autochdir.vim
@@ -64,4 +64,14 @@ func Test_verbose_pwd()
call delete('Xautodir', 'rf')
endfunc
+func Test_multibyte()
+ " using an invalid character should not cause a crash
+ set wic
+ " Except on Windows, E472 is also thrown last, but v8.1.1183 isn't ported yet
+ " call assert_fails('tc *', has('win32') ? 'E480:' : 'E344:')
+ call assert_fails('tc *', has('win32') ? 'E480:' : 'E472:')
+ set nowic
+endfunc
+
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim
index 45285b69a1..c39546b9ea 100644
--- a/src/nvim/testdir/test_autocmd.vim
+++ b/src/nvim/testdir/test_autocmd.vim
@@ -4,7 +4,7 @@ source shared.vim
source check.vim
source term_util.vim
-func! s:cleanup_buffers() abort
+func s:cleanup_buffers() abort
for bnr in range(1, bufnr('$'))
if bufloaded(bnr) && bufnr('%') != bnr
execute 'bd! ' . bnr
@@ -502,7 +502,7 @@ func Test_autocmd_bufwipe_in_SessLoadPost()
[CODE]
call writefile(content, 'Xvimrc')
- call system(v:progpath. ' --headless -i NONE -u Xvimrc --noplugins -S Session.vim -c cq')
+ call system(GetVimCommand('Xvimrc') .. ' --headless --noplugins -S Session.vim -c cq')
let errors = join(readfile('Xerrors'))
call assert_match('E814', errors)
@@ -562,7 +562,7 @@ func Test_autocmd_bufwipe_in_SessLoadPost2()
[CODE]
call writefile(content, 'Xvimrc')
- call system(v:progpath. ' --headless -i NONE -u Xvimrc --noplugins -S Session.vim -c cq')
+ call system(GetVimCommand('Xvimrc') .. ' --headless --noplugins -S Session.vim -c cq')
let errors = join(readfile('Xerrors'))
" This probably only ever matches on unix.
call assert_notmatch('Caught deadly signal SEGV', errors)
@@ -1506,7 +1506,7 @@ func Test_bufunload_all()
call writefile(content, 'Xtest')
call delete('Xout')
- call system(v:progpath. ' -u NORC -i NONE -N -S Xtest')
+ call system(GetVimCommandClean() .. ' -N --headless -S Xtest')
call assert_true(filereadable('Xout'))
call delete('Xxx1')
@@ -1827,6 +1827,14 @@ func Test_autocommand_all_events()
call assert_fails('au * x bwipe', 'E1155:')
endfunc
+func Test_autocmd_user()
+ au User MyEvent let s:res = [expand("<afile>"), expand("<amatch>")]
+ doautocmd User MyEvent
+ call assert_equal(['MyEvent', 'MyEvent'], s:res)
+ au! User
+ unlet s:res
+endfunc
+
function s:Before_test_dirchanged()
augroup test_dirchanged
autocmd!
@@ -1850,14 +1858,16 @@ endfunc
function Test_dirchanged_global()
call s:Before_test_dirchanged()
+ autocmd test_dirchanged DirChangedPre global call add(s:li, expand("<amatch>") .. " pre cd " .. v:event.directory)
autocmd test_dirchanged DirChanged global call add(s:li, "cd:")
autocmd test_dirchanged DirChanged global call add(s:li, expand("<afile>"))
call chdir(s:dir_foo)
- call assert_equal(["cd:", s:dir_foo], s:li)
+ let expected = ["global pre cd " .. s:dir_foo, "cd:", s:dir_foo]
+ call assert_equal(expected, s:li)
call chdir(s:dir_foo)
- call assert_equal(["cd:", s:dir_foo], s:li)
+ call assert_equal(expected, s:li)
exe 'lcd ' .. fnameescape(s:dir_bar)
- call assert_equal(["cd:", s:dir_foo], s:li)
+ call assert_equal(expected, s:li)
call s:After_test_dirchanged()
endfunc
@@ -1879,6 +1889,7 @@ function Test_dirchanged_auto()
CheckOption autochdir
call s:Before_test_dirchanged()
call test_autochdir()
+ autocmd test_dirchanged DirChangedPre auto call add(s:li, "pre cd " .. v:event.directory)
autocmd test_dirchanged DirChanged auto call add(s:li, "auto:")
autocmd test_dirchanged DirChanged auto call add(s:li, expand("<afile>"))
set acd
@@ -1886,7 +1897,8 @@ function Test_dirchanged_auto()
call assert_equal([], s:li)
exe 'edit ' . s:dir_foo . '/Xfile'
call assert_equal(s:dir_foo, getcwd())
- call assert_equal(["auto:", s:dir_foo], s:li)
+ let expected = ["pre cd " .. s:dir_foo, "auto:", s:dir_foo]
+ call assert_equal(expected, s:li)
set noacd
bwipe!
call s:After_test_dirchanged()
@@ -2380,6 +2392,40 @@ func Test_autocmd_was_using_freed_memory()
pclose
endfunc
+func Test_BufWrite_lockmarks()
+ edit! Xtest
+ call setline(1, ['a', 'b', 'c', 'd'])
+
+ " :lockmarks preserves the marks
+ call SetChangeMarks(2, 3)
+ lockmarks write
+ call assert_equal([2, 3], [line("'["), line("']")])
+
+ " *WritePre autocmds get the correct line range, but lockmarks preserves the
+ " original values for the user
+ augroup lockmarks
+ au!
+ au BufWritePre,FilterWritePre * call assert_equal([1, 4], [line("'["), line("']")])
+ au FileWritePre * call assert_equal([3, 4], [line("'["), line("']")])
+ augroup END
+
+ lockmarks write
+ call assert_equal([2, 3], [line("'["), line("']")])
+
+ if executable('cat')
+ lockmarks %!cat
+ call assert_equal([2, 3], [line("'["), line("']")])
+ endif
+
+ lockmarks 3,4write Xtest2
+ call assert_equal([2, 3], [line("'["), line("']")])
+
+ au! lockmarks
+ augroup! lockmarks
+ call delete('Xtest')
+ call delete('Xtest2')
+endfunc
+
" FileChangedShell tested in test_filechanged.vim
func LogACmd()
@@ -2509,6 +2555,16 @@ func Test_close_autocmd_tab()
%bwipe!
endfunc
+func Test_Visual_doautoall_redraw()
+ call setline(1, ['a', 'b'])
+ new
+ wincmd p
+ call feedkeys("G\<C-V>", 'txn')
+ autocmd User Explode ++once redraw
+ doautoall User Explode
+ %bwipe!
+endfunc
+
func Test_autocmd_closes_window()
au BufNew,BufWinLeave * e %e
file yyy
diff --git a/src/nvim/testdir/test_blockedit.vim b/src/nvim/testdir/test_blockedit.vim
index 38978ef689..7b56b1554f 100644
--- a/src/nvim/testdir/test_blockedit.vim
+++ b/src/nvim/testdir/test_blockedit.vim
@@ -81,4 +81,52 @@ func Test_blockinsert_delete()
bwipe!
endfunc
+func Test_blockappend_eol_cursor()
+ new
+ " Test 1 Move 1 char left
+ call setline(1, ['aaa', 'bbb', 'ccc'])
+ exe "norm! gg$\<c-v>2jA\<left>x\<esc>"
+ call assert_equal(['aaxa', 'bbxb', 'ccxc'], getline(1, '$'))
+ " Test 2 Move 2 chars left
+ sil %d
+ call setline(1, ['aaa', 'bbb', 'ccc'])
+ exe "norm! gg$\<c-v>2jA\<left>\<left>x\<esc>"
+ call assert_equal(['axaa', 'bxbb', 'cxcc'], getline(1, '$'))
+ " Test 3 Move 3 chars left (outside of the visual selection)
+ sil %d
+ call setline(1, ['aaa', 'bbb', 'ccc'])
+ exe "norm! ggl$\<c-v>2jA\<left>\<left>\<left>x\<esc>"
+ call assert_equal(['xaaa', 'bbb', 'ccc'], getline(1, '$'))
+ bw!
+endfunc
+
+func Test_blockappend_eol_cursor2()
+ new
+ " Test 1 Move 1 char left
+ call setline(1, ['aaaaa', 'bbb', 'ccccc'])
+ exe "norm! gg\<c-v>$2jA\<left>x\<esc>"
+ call assert_equal(['aaaaxa', 'bbbx', 'ccccxc'], getline(1, '$'))
+ " Test 2 Move 2 chars left
+ sil %d
+ call setline(1, ['aaaaa', 'bbb', 'ccccc'])
+ exe "norm! gg\<c-v>$2jA\<left>\<left>x\<esc>"
+ call assert_equal(['aaaxaa', 'bbbx', 'cccxcc'], getline(1, '$'))
+ " Test 3 Move 3 chars left (to the beginning of the visual selection)
+ sil %d
+ call setline(1, ['aaaaa', 'bbb', 'ccccc'])
+ exe "norm! gg\<c-v>$2jA\<left>\<left>\<left>x\<esc>"
+ call assert_equal(['aaxaaa', 'bbxb', 'ccxccc'], getline(1, '$'))
+ " Test 4 Move 3 chars left (outside of the visual selection)
+ sil %d
+ call setline(1, ['aaaaa', 'bbb', 'ccccc'])
+ exe "norm! ggl\<c-v>$2jA\<left>\<left>\<left>x\<esc>"
+ call assert_equal(['aaxaaa', 'bbxb', 'ccxccc'], getline(1, '$'))
+ " Test 5 Move 4 chars left (outside of the visual selection)
+ sil %d
+ call setline(1, ['aaaaa', 'bbb', 'ccccc'])
+ exe "norm! ggl\<c-v>$2jA\<left>\<left>\<left>\<left>x\<esc>"
+ call assert_equal(['axaaaa', 'bxbb', 'cxcccc'], getline(1, '$'))
+ bw!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_breakindent.vim b/src/nvim/testdir/test_breakindent.vim
index 8d592f21ea..438edb0257 100644
--- a/src/nvim/testdir/test_breakindent.vim
+++ b/src/nvim/testdir/test_breakindent.vim
@@ -1,7 +1,7 @@
" Test for breakindent
"
" Note: if you get strange failures when adding new tests, it might be that
-" while the test is run, the breakindent cacheing gets in its way.
+" while the test is run, the breakindent caching gets in its way.
" It helps to change the tabstop setting and force a redraw (e.g. see
" Test_breakindent08())
if !exists('+breakindent')
@@ -20,7 +20,7 @@ func s:screen_lines2(lnums, lnume, width) abort
return ScreenLines([a:lnums, a:lnume], a:width)
endfunc
-func! s:compare_lines(expect, actual)
+func s:compare_lines(expect, actual)
call assert_equal(join(a:expect, "\n"), join(a:actual, "\n"))
endfunc
diff --git a/src/nvim/testdir/test_buffer.vim b/src/nvim/testdir/test_buffer.vim
index 40111fdf06..a31cdbb49a 100644
--- a/src/nvim/testdir/test_buffer.vim
+++ b/src/nvim/testdir/test_buffer.vim
@@ -1,5 +1,7 @@
" Tests for Vim buffer
+source check.vim
+
func Test_buffer_error()
new foo1
new foo2
@@ -30,4 +32,33 @@ func Test_balt()
call assert_equal('OtherBuffer', bufname())
endfunc
+" Test for buffer match URL(scheme) check
+" scheme is alpha and inner hyphen only.
+func Test_buffer_scheme()
+ CheckMSWindows
+
+ set noshellslash
+ %bwipe!
+ let bufnames = [
+ \ #{id: 'b0', name: 'test://xyz/foo/b0' , match: 1},
+ \ #{id: 'b1', name: 'test+abc://xyz/foo/b1', match: 0},
+ \ #{id: 'b2', name: 'test_abc://xyz/foo/b2', match: 0},
+ \ #{id: 'b3', name: 'test-abc://xyz/foo/b3', match: 1},
+ \ #{id: 'b4', name: '-test://xyz/foo/b4' , match: 0},
+ \ #{id: 'b5', name: 'test-://xyz/foo/b5' , match: 0},
+ \]
+ for buf in bufnames
+ new `=buf.name`
+ if buf.match
+ call assert_equal(buf.name, getbufinfo(buf.id)[0].name)
+ else
+ " slashes will have become backslashes
+ call assert_notequal(buf.name, getbufinfo(buf.id)[0].name)
+ endif
+ bwipe
+ endfor
+
+ set shellslash&
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_cd.vim b/src/nvim/testdir/test_cd.vim
index 76a2620be0..c364babd65 100644
--- a/src/nvim/testdir/test_cd.vim
+++ b/src/nvim/testdir/test_cd.vim
@@ -44,6 +44,15 @@ func Test_cd_minus()
cd -
call assert_equal(path, getcwd())
+ " Test for :cd - after a failed :cd
+ " v8.2.1183 is not ported yet
+ " call assert_fails('cd /nonexistent', 'E344:')
+ call assert_fails('cd /nonexistent', 'E472:')
+ call assert_equal(path, getcwd())
+ cd -
+ call assert_equal(path_dotdot, getcwd())
+ cd -
+
" Test for :cd - without a previous directory
let lines =<< trim [SCRIPT]
call assert_fails('cd -', 'E186:')
diff --git a/src/nvim/testdir/test_cdo.vim b/src/nvim/testdir/test_cdo.vim
new file mode 100644
index 0000000000..aa2e4f1a8c
--- /dev/null
+++ b/src/nvim/testdir/test_cdo.vim
@@ -0,0 +1,205 @@
+" Tests for the :cdo, :cfdo, :ldo and :lfdo commands
+
+if !has('quickfix')
+ throw 'Skipped: quickfix feature missing'
+endif
+
+" Create the files used by the tests
+function SetUp()
+ call writefile(["Line1", "Line2", "Line3"], 'Xtestfile1')
+ call writefile(["Line1", "Line2", "Line3"], 'Xtestfile2')
+ call writefile(["Line1", "Line2", "Line3"], 'Xtestfile3')
+endfunction
+
+" Remove the files used by the tests
+function TearDown()
+ call delete('Xtestfile1')
+ call delete('Xtestfile2')
+ call delete('Xtestfile3')
+endfunction
+
+" Returns the current line in '<filename> <linenum>L <column>C' format
+function GetRuler()
+ return expand('%') . ' ' . line('.') . 'L' . ' ' . col('.') . 'C'
+endfunction
+
+" Tests for the :cdo and :ldo commands
+function XdoTests(cchar)
+ enew
+
+ " Shortcuts for calling the cdo and ldo commands
+ let Xdo = a:cchar . 'do'
+ let Xgetexpr = a:cchar . 'getexpr'
+ let Xprev = a:cchar. 'prev'
+ let XdoCmd = Xdo . ' call add(l, GetRuler())'
+
+ " Try with an empty list
+ let l = []
+ exe XdoCmd
+ call assert_equal([], l)
+
+ " Populate the list and then try
+ exe Xgetexpr . " ['non-error 1', 'Xtestfile1:1:3:Line1', 'non-error 2', 'Xtestfile2:2:2:Line2', 'non-error 3', 'Xtestfile3:3:1:Line3']"
+
+ let l = []
+ exe XdoCmd
+ call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 3L 1C'], l)
+
+ " Run command only on selected error lines
+ let l = []
+ enew
+ exe "2,3" . XdoCmd
+ call assert_equal(['Xtestfile2 2L 2C', 'Xtestfile3 3L 1C'], l)
+
+ " Boundary condition tests
+ let l = []
+ enew
+ exe "1,1" . XdoCmd
+ call assert_equal(['Xtestfile1 1L 3C'], l)
+
+ let l = []
+ enew
+ exe "3" . XdoCmd
+ call assert_equal(['Xtestfile3 3L 1C'], l)
+
+ " Range test commands
+ let l = []
+ enew
+ exe "%" . XdoCmd
+ call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 3L 1C'], l)
+
+ let l = []
+ enew
+ exe "1,$" . XdoCmd
+ call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 3L 1C'], l)
+
+ let l = []
+ enew
+ exe Xprev
+ exe "." . XdoCmd
+ call assert_equal(['Xtestfile2 2L 2C'], l)
+
+ let l = []
+ enew
+ exe "+" . XdoCmd
+ call assert_equal(['Xtestfile3 3L 1C'], l)
+
+ " Invalid error lines test
+ let l = []
+ enew
+ exe "silent! 27" . XdoCmd
+ exe "silent! 4,5" . XdoCmd
+ call assert_equal([], l)
+
+ " Run commands from an unsaved buffer
+ let v:errmsg=''
+ let l = []
+ enew
+ setlocal modified
+ exe "silent! 2,2" . XdoCmd
+ if v:errmsg !~# 'No write since last change'
+ call add(v:errors, 'Unsaved file change test failed')
+ endif
+
+ " If the executed command fails, then the operation should be aborted
+ enew!
+ let subst_count = 0
+ exe "silent!" . Xdo . " s/Line/xLine/ | let subst_count += 1"
+ if subst_count != 1 || getline('.') != 'xLine1'
+ call add(v:errors, 'Abort command on error test failed')
+ endif
+
+ let l = []
+ exe "2,2" . Xdo . "! call add(l, GetRuler())"
+ call assert_equal(['Xtestfile2 2L 2C'], l)
+
+ " List with no valid error entries
+ let l = []
+ edit! +2 Xtestfile1
+ exe Xgetexpr . " ['non-error 1', 'non-error 2', 'non-error 3']"
+ exe XdoCmd
+ call assert_equal([], l)
+ exe "silent! 2" . XdoCmd
+ call assert_equal([], l)
+ let v:errmsg=''
+ exe "%" . XdoCmd
+ exe "1,$" . XdoCmd
+ exe "." . XdoCmd
+ call assert_equal('', v:errmsg)
+
+ " List with only one valid entry
+ let l = []
+ exe Xgetexpr . " ['Xtestfile3:3:1:Line3']"
+ exe XdoCmd
+ call assert_equal(['Xtestfile3 3L 1C'], l)
+
+endfunction
+
+" Tests for the :cfdo and :lfdo commands
+function XfdoTests(cchar)
+ enew
+
+ " Shortcuts for calling the cfdo and lfdo commands
+ let Xfdo = a:cchar . 'fdo'
+ let Xgetexpr = a:cchar . 'getexpr'
+ let XfdoCmd = Xfdo . ' call add(l, GetRuler())'
+ let Xpfile = a:cchar. 'pfile'
+
+ " Clear the quickfix/location list
+ exe Xgetexpr . " []"
+
+ " Try with an empty list
+ let l = []
+ exe XfdoCmd
+ call assert_equal([], l)
+
+ " Populate the list and then try
+ exe Xgetexpr . " ['non-error 1', 'Xtestfile1:1:3:Line1', 'Xtestfile1:2:1:Line2', 'non-error 2', 'Xtestfile2:2:2:Line2', 'non-error 3', 'Xtestfile3:2:3:Line2', 'Xtestfile3:3:1:Line3']"
+
+ let l = []
+ exe XfdoCmd
+ call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 2L 3C'], l)
+
+ " Run command only on selected error lines
+ let l = []
+ exe "2,3" . XfdoCmd
+ call assert_equal(['Xtestfile2 2L 2C', 'Xtestfile3 2L 3C'], l)
+
+ " Boundary condition tests
+ let l = []
+ exe "3" . XfdoCmd
+ call assert_equal(['Xtestfile3 2L 3C'], l)
+
+ " Range test commands
+ let l = []
+ exe "%" . XfdoCmd
+ call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 2L 3C'], l)
+
+ let l = []
+ exe "1,$" . XfdoCmd
+ call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 2L 3C'], l)
+
+ let l = []
+ exe Xpfile
+ exe "." . XfdoCmd
+ call assert_equal(['Xtestfile2 2L 2C'], l)
+
+ " List with only one valid entry
+ let l = []
+ exe Xgetexpr . " ['Xtestfile2:2:5:Line2']"
+ exe XfdoCmd
+ call assert_equal(['Xtestfile2 2L 5C'], l)
+
+endfunction
+
+" Tests for cdo and cfdo
+function Test_cdo()
+ call XdoTests('c')
+ call XfdoTests('c')
+endfunction
+
+" Tests for ldo and lfdo
+function Test_ldo()
+ call XdoTests('l')
+ call XfdoTests('l')
+endfunction
diff --git a/src/nvim/testdir/test_charsearch_utf8.vim b/src/nvim/testdir/test_charsearch_utf8.vim
index 09341a90b0..82a807ac5b 100644
--- a/src/nvim/testdir/test_charsearch_utf8.vim
+++ b/src/nvim/testdir/test_charsearch_utf8.vim
@@ -1,7 +1,7 @@
" Tests for related f{char} and t{char} using utf-8.
" Test for t,f,F,T movement commands
-function! Test_search_cmds()
+func Test_search_cmds()
new!
call setline(1, "・最初から最後まで最強のVimは最高")
1
diff --git a/src/nvim/testdir/test_cindent.vim b/src/nvim/testdir/test_cindent.vim
index 562867f548..4b702bf2b8 100644
--- a/src/nvim/testdir/test_cindent.vim
+++ b/src/nvim/testdir/test_cindent.vim
@@ -1,6 +1,5 @@
" Test for cinoptions and cindent
"
-" TODO: rewrite test3.in into this new style test
func Test_cino_hash()
" Test that curbuf->b_ind_hash_comment is correctly reset
@@ -128,12 +127,5187 @@ func Test_cindent_func()
bwipe!
endfunc
+func Test_cindent_1()
+ new
+ setl cindent ts=4 sw=4
+ setl cino& sts&
+
+ let code =<< trim [CODE]
+ /* start of AUTO matically checked vim: set ts=4 : */
+ {
+ if (test)
+ cmd1;
+ cmd2;
+ }
+
+ {
+ if (test)
+ cmd1;
+ else
+ cmd2;
+ }
+
+ {
+ if (test)
+ {
+ cmd1;
+ cmd2;
+ }
+ }
+
+ {
+ if (test)
+ {
+ cmd1;
+ else
+ }
+ }
+
+ {
+ while (this)
+ if (test)
+ cmd1;
+ cmd2;
+ }
+
+ {
+ while (this)
+ if (test)
+ cmd1;
+ else
+ cmd2;
+ }
+
+ {
+ if (test)
+ {
+ cmd;
+ }
+
+ if (test)
+ cmd;
+ }
+
+ {
+ if (test) {
+ cmd;
+ }
+
+ if (test) cmd;
+ }
+
+ {
+ cmd1;
+ for (blah)
+ while (this)
+ if (test)
+ cmd2;
+ cmd3;
+ }
+
+ {
+ cmd1;
+ for (blah)
+ while (this)
+ if (test)
+ cmd2;
+ cmd3;
+
+ if (test)
+ {
+ cmd1;
+ cmd2;
+ cmd3;
+ }
+ }
+
+
+ /* Test for 'cindent' do/while mixed with if/else: */
+
+ {
+ do
+ if (asdf)
+ asdfasd;
+ while (cond);
+
+ do
+ if (asdf)
+ while (asdf)
+ asdf;
+ while (asdf);
+ }
+
+ /* Test for 'cindent' with two ) on a continuation line */
+ {
+ if (asdfasdf;asldkfj asdlkfj as;ldkfj sal;d
+ aal;sdkjf ( ;asldfkja;sldfk
+ al;sdjfka ;slkdf ) sa;ldkjfsa dlk;)
+ line up here;
+ }
+
+
+ /* C++ tests: */
+
+ // foo() these three lines should remain in column 0
+ // {
+ // }
+
+ /* Test for continuation and unterminated lines: */
+ {
+ i = 99 + 14325 +
+ 21345 +
+ 21345 +
+ 21345 + ( 21345 +
+ 21345) +
+ 2345 +
+ 1234;
+ c = 1;
+ }
+
+ /*
+ testje for indent with empty line
+
+ here */
+
+ {
+ if (testing &&
+ not a joke ||
+ line up here)
+ hay;
+ if (testing &&
+ (not a joke || testing
+ )line up here)
+ hay;
+ if (testing &&
+ (not a joke || testing
+ line up here))
+ hay;
+ }
+
+
+ {
+ switch (c)
+ {
+ case xx:
+ do
+ if (asdf)
+ do
+ asdfasdf;
+ while (asdf);
+ else
+ asdfasdf;
+ while (cond);
+ case yy:
+ case xx:
+ case zz:
+ testing;
+ }
+ }
+
+ {
+ if (cond) {
+ foo;
+ }
+ else
+ {
+ bar;
+ }
+ }
+
+ {
+ if (alskdfj ;alsdkfjal;skdjf (;sadlkfsa ;dlkf j;alksdfj ;alskdjf
+ alsdkfj (asldk;fj
+ awith cino=(0 ;lf this one goes to below the paren with ==
+ ;laksjfd ;lsakdjf ;alskdf asd)
+ asdfasdf;)))
+ asdfasdf;
+ }
+
+ int
+ func(a, b)
+ int a;
+ int c;
+ {
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3)
+ )
+ }
+
+ {
+ while (asd)
+ {
+ if (asdf)
+ if (test)
+ if (that)
+ {
+ if (asdf)
+ do
+ cdasd;
+ while (as
+ df);
+ }
+ else
+ if (asdf)
+ asdf;
+ else
+ asdf;
+ asdf;
+ }
+ }
+
+ {
+ s = "/*"; b = ';'
+ s = "/*"; b = ';';
+ a = b;
+ }
+
+ {
+ switch (a)
+ {
+ case a:
+ switch (t)
+ {
+ case 1:
+ cmd;
+ break;
+ case 2:
+ cmd;
+ break;
+ }
+ cmd;
+ break;
+ case b:
+ {
+ int i;
+ cmd;
+ }
+ break;
+ case c: {
+ int i;
+ cmd;
+ }
+ case d: if (cond &&
+ test) { /* this line doesn't work right */
+ int i;
+ cmd;
+ }
+ break;
+ }
+ }
+
+ {
+ if (!(vim_strchr(p_cpo, CPO_BUFOPTGLOB) != NULL && entering) &&
+ (bp_to->b_p_initialized ||
+ (!entering && vim_strchr(p_cpo, CPO_BUFOPT) != NULL)))
+ return;
+ label :
+ asdf = asdf ?
+ asdf : asdf;
+ asdf = asdf ?
+ asdf: asdf;
+ }
+
+ /* Special Comments : This function has the added complexity (compared */
+ /* : to addtolist) of having to check for a detail */
+ /* : texture and add that to the list first. */
+
+ char *(array[100]) = {
+ "testje",
+ "foo",
+ "bar",
+ }
+
+ enum soppie
+ {
+ yes = 0,
+ no,
+ maybe
+ };
+
+ typedef enum soppie
+ {
+ yes = 0,
+ no,
+ maybe
+ };
+
+ static enum
+ {
+ yes = 0,
+ no,
+ maybe
+ } soppie;
+
+ public static enum
+ {
+ yes = 0,
+ no,
+ maybe
+ } soppie;
+
+ static private enum
+ {
+ yes = 0,
+ no,
+ maybe
+ } soppie;
+
+ {
+ int a,
+ b;
+ }
+
+ {
+ struct Type
+ {
+ int i;
+ char *str;
+ } var[] =
+ {
+ 0, "zero",
+ 1, "one",
+ 2, "two",
+ 3, "three"
+ };
+
+ float matrix[3][3] =
+ {
+ {
+ 0,
+ 1,
+ 2
+ },
+ {
+ 3,
+ 4,
+ 5
+ },
+ {
+ 6,
+ 7,
+ 8
+ }
+ };
+ }
+
+ {
+ /* blah ( blah */
+ /* where does this go? */
+
+ /* blah ( blah */
+ cmd;
+
+ func(arg1,
+ /* comment */
+ arg2);
+ a;
+ {
+ b;
+ {
+ c; /* Hey, NOW it indents?! */
+ }
+ }
+
+ {
+ func(arg1,
+ arg2,
+ arg3);
+ /* Hey, what am I doing here? Is this coz of the ","? */
+ }
+ }
+
+ main ()
+ {
+ if (cond)
+ {
+ a = b;
+ }
+ if (cond) {
+ a = c;
+ }
+ if (cond)
+ a = d;
+ return;
+ }
+
+ {
+ case 2: if (asdf &&
+ asdfasdf)
+ aasdf;
+ a = 9;
+ case 3: if (asdf)
+ aasdf;
+ a = 9;
+ case 4: x = 1;
+ y = 2;
+
+ label: if (asdf)
+ here;
+
+ label: if (asdf &&
+ asdfasdf)
+ {
+ }
+
+ label: if (asdf &&
+ asdfasdf) {
+ there;
+ }
+
+ label: if (asdf &&
+ asdfasdf)
+ there;
+ }
+
+ {
+ /*
+ hello with ":set comments= cino=c5"
+ */
+
+ /*
+ hello with ":set comments= cino="
+ */
+ }
+
+
+ {
+ if (a < b) {
+ a = a + 1;
+ } else
+ a = a + 2;
+
+ if (a)
+ do {
+ testing;
+ } while (asdfasdf);
+ a = b + 1;
+ asdfasdf
+ }
+
+ {
+ for ( int i = 0;
+ i < 10; i++ )
+ {
+ }
+ i = 0;
+ }
+
+ class bob
+ {
+ int foo() {return 1;}
+ int bar;
+ }
+
+ main()
+ {
+ while(1)
+ if (foo)
+ {
+ bar;
+ }
+ else {
+ asdf;
+ }
+ misplacedline;
+ }
+
+ {
+ if (clipboard.state == SELECT_DONE
+ && ((row == clipboard.start.lnum
+ && col >= clipboard.start.col)
+ || row > clipboard.start.lnum))
+ }
+
+ {
+ if (1) {i += 4;}
+ where_am_i;
+ return 0;
+ }
+
+ {
+ {
+ } // sdf(asdf
+ if (asdf)
+ asd;
+ }
+
+ {
+ label1:
+ label2:
+ }
+
+ {
+ int fooRet = foo(pBar1, false /*fKB*/,
+ true /*fPTB*/, 3 /*nT*/, false /*fDF*/);
+ f() {
+ for ( i = 0;
+ i < m;
+ /* c */ i++ ) {
+ a = b;
+ }
+ }
+ }
+
+ {
+ f1(/*comment*/);
+ f2();
+ }
+
+ {
+ do {
+ if (foo) {
+ } else
+ ;
+ } while (foo);
+ foo(); // was wrong
+ }
+
+ int x; // no extra indent because of the ;
+ void func()
+ {
+ }
+
+ char *tab[] = {"aaa",
+ "};", /* }; */ NULL}
+ int indented;
+ {}
+
+ char *a[] = {"aaa", "bbb",
+ "ccc", NULL};
+ // here
+
+ char *tab[] = {"aaa",
+ "xx", /* xx */}; /* asdf */
+ int not_indented;
+
+ {
+ do {
+ switch (bla)
+ {
+ case 1: if (foo)
+ bar;
+ }
+ } while (boo);
+ wrong;
+ }
+
+ int foo,
+ bar;
+ int foo;
+
+ #if defined(foo) \
+ && defined(bar)
+ char * xx = "asdf\
+ foo\
+ bor";
+ int x;
+
+ char *foo = "asdf\
+ asdf\
+ asdf",
+ *bar;
+
+ void f()
+ {
+ #if defined(foo) \
+ && defined(bar)
+ char *foo = "asdf\
+ asdf\
+ asdf",
+ *bar;
+ {
+ int i;
+ char *foo = "asdf\
+ asdf\
+ asdf",
+ *bar;
+ }
+ #endif
+ }
+ #endif
+
+ int y; // comment
+ // comment
+
+ // comment
+
+ {
+ Constructor(int a,
+ int b ) : BaseClass(a)
+ {
+ }
+ }
+
+ void foo()
+ {
+ char one,
+ two;
+ struct bla piet,
+ jan;
+ enum foo kees,
+ jannie;
+ static unsigned sdf,
+ krap;
+ unsigned int piet,
+ jan;
+ int
+ kees,
+ jan;
+ }
+
+ {
+ t(int f,
+ int d); // )
+ d();
+ }
+
+ Constructor::Constructor(int a,
+ int b
+ ) :
+ BaseClass(a,
+ b,
+ c),
+ mMember(b),
+ {
+ }
+
+ Constructor::Constructor(int a,
+ int b ) :
+ BaseClass(a)
+ {
+ }
+
+ Constructor::Constructor(int a,
+ int b ) /*x*/ : /*x*/ BaseClass(a),
+ member(b)
+ {
+ }
+
+ A::A(int a, int b)
+ : aa(a),
+ bb(b),
+ cc(c)
+ {
+ }
+
+ class CAbc :
+ public BaseClass1,
+ protected BaseClass2
+ {
+ int Test() { return FALSE; }
+ int Test1() { return TRUE; }
+
+ CAbc(int a, int b ) :
+ BaseClass(a)
+ {
+ switch(xxx)
+ {
+ case abc:
+ asdf();
+ break;
+
+ case 999:
+ baer();
+ break;
+ }
+ }
+
+ public: // <-- this was incorrectly indented before!!
+ void testfall();
+ protected:
+ void testfall();
+ };
+
+ class CAbc : public BaseClass1,
+ protected BaseClass2
+ {
+ };
+
+ static struct
+ {
+ int a;
+ int b;
+ } variable[COUNT] =
+ {
+ {
+ 123,
+ 456
+ },
+ {
+ 123,
+ 456
+ }
+ };
+
+ static struct
+ {
+ int a;
+ int b;
+ } variable[COUNT] =
+ {
+ { 123, 456 },
+ { 123, 456 }
+ };
+
+ void asdf() /* ind_maxparen may cause trouble here */
+ {
+ if ((0
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1)) break;
+ }
+
+ foo()
+ {
+ a = cond ? foo() : asdf
+ + asdf;
+
+ a = cond ?
+ foo() : asdf
+ + asdf;
+ }
+
+ int main(void)
+ {
+ if (a)
+ if (b)
+ 2;
+ else 3;
+ next_line_of_code();
+ }
+
+ barry()
+ {
+ Foo::Foo (int one,
+ int two)
+ : something(4)
+ {}
+ }
+
+ barry()
+ {
+ Foo::Foo (int one, int two)
+ : something(4)
+ {}
+ }
+
+ Constructor::Constructor(int a,
+ int b
+ ) :
+ BaseClass(a,
+ b,
+ c),
+ mMember(b)
+ {
+ }
+ int main ()
+ {
+ if (lala)
+ do
+ ++(*lolo);
+ while (lili
+ && lele);
+ lulu;
+ }
+
+ int main ()
+ {
+ switch (c)
+ {
+ case 'c': if (cond)
+ {
+ }
+ }
+ }
+
+ main()
+ {
+ (void) MyFancyFuasdfadsfnction(
+ argument);
+ }
+
+ main()
+ {
+ char foo[] = "/*";
+ /* as
+ df */
+ hello
+ }
+
+ /* valid namespaces with normal indent */
+ namespace
+ {
+ {
+ 111111111111;
+ }
+ }
+ namespace /* test */
+ {
+ 11111111111111111;
+ }
+ namespace // test
+ {
+ 111111111111111111;
+ }
+ namespace
+ {
+ 111111111111111111;
+ }
+ namespace test
+ {
+ 111111111111111111;
+ }
+ namespace{
+ 111111111111111111;
+ }
+ namespace test{
+ 111111111111111111;
+ }
+ namespace {
+ 111111111111111111;
+ }
+ namespace test {
+ 111111111111111111;
+ namespace test2 {
+ 22222222222222222;
+ }
+ }
+ inline namespace {
+ 111111111111111111;
+ }
+ inline /* test */ namespace {
+ 111111111111111111;
+ }
+ inline/* test */namespace {
+ 111111111111111111;
+ }
+
+ /* invalid namespaces use block indent */
+ namespace test test2 {
+ 111111111111111111111;
+ }
+ namespace11111111111 {
+ 111111111111;
+ }
+ namespace() {
+ 1111111111111;
+ }
+ namespace()
+ {
+ 111111111111111111;
+ }
+ namespace test test2
+ {
+ 1111111111111111111;
+ }
+ namespace111111111
+ {
+ 111111111111111111;
+ }
+ inlinenamespace {
+ 111111111111111111;
+ }
+
+ void getstring() {
+ /* Raw strings */
+ const char* s = R"(
+ test {
+ # comment
+ field: 123
+ }
+ )";
+ }
+
+ void getstring() {
+ const char* s = R"foo(
+ test {
+ # comment
+ field: 123
+ }
+ )foo";
+ }
+
+ {
+ int a[4] = {
+ [0] = 0,
+ [1] = 1,
+ [2] = 2,
+ [3] = 3,
+ };
+ }
+
+ {
+ a = b[2]
+ + 3;
+ }
+
+ {
+ if (1)
+ /* aaaaa
+ * bbbbb
+ */
+ a = 1;
+ }
+
+ void func()
+ {
+ switch (foo)
+ {
+ case (bar):
+ if (baz())
+ quux();
+ break;
+ case (shmoo):
+ if (!bar)
+ {
+ }
+ case (foo1):
+ switch (bar)
+ {
+ case baz:
+ baz_f();
+ break;
+ }
+ break;
+ default:
+ baz();
+ baz();
+ break;
+ }
+ }
+
+ /* end of AUTO */
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ call search('start of AUTO')
+ exe "normal =/end of AUTO\<CR>"
+
+ let expected =<< trim [CODE]
+ /* start of AUTO matically checked vim: set ts=4 : */
+ {
+ if (test)
+ cmd1;
+ cmd2;
+ }
+
+ {
+ if (test)
+ cmd1;
+ else
+ cmd2;
+ }
+
+ {
+ if (test)
+ {
+ cmd1;
+ cmd2;
+ }
+ }
+
+ {
+ if (test)
+ {
+ cmd1;
+ else
+ }
+ }
+
+ {
+ while (this)
+ if (test)
+ cmd1;
+ cmd2;
+ }
+
+ {
+ while (this)
+ if (test)
+ cmd1;
+ else
+ cmd2;
+ }
+
+ {
+ if (test)
+ {
+ cmd;
+ }
+
+ if (test)
+ cmd;
+ }
+
+ {
+ if (test) {
+ cmd;
+ }
+
+ if (test) cmd;
+ }
+
+ {
+ cmd1;
+ for (blah)
+ while (this)
+ if (test)
+ cmd2;
+ cmd3;
+ }
+
+ {
+ cmd1;
+ for (blah)
+ while (this)
+ if (test)
+ cmd2;
+ cmd3;
+
+ if (test)
+ {
+ cmd1;
+ cmd2;
+ cmd3;
+ }
+ }
+
+
+ /* Test for 'cindent' do/while mixed with if/else: */
+
+ {
+ do
+ if (asdf)
+ asdfasd;
+ while (cond);
+
+ do
+ if (asdf)
+ while (asdf)
+ asdf;
+ while (asdf);
+ }
+
+ /* Test for 'cindent' with two ) on a continuation line */
+ {
+ if (asdfasdf;asldkfj asdlkfj as;ldkfj sal;d
+ aal;sdkjf ( ;asldfkja;sldfk
+ al;sdjfka ;slkdf ) sa;ldkjfsa dlk;)
+ line up here;
+ }
+
+
+ /* C++ tests: */
+
+ // foo() these three lines should remain in column 0
+ // {
+ // }
+
+ /* Test for continuation and unterminated lines: */
+ {
+ i = 99 + 14325 +
+ 21345 +
+ 21345 +
+ 21345 + ( 21345 +
+ 21345) +
+ 2345 +
+ 1234;
+ c = 1;
+ }
+
+ /*
+ testje for indent with empty line
+
+ here */
+
+ {
+ if (testing &&
+ not a joke ||
+ line up here)
+ hay;
+ if (testing &&
+ (not a joke || testing
+ )line up here)
+ hay;
+ if (testing &&
+ (not a joke || testing
+ line up here))
+ hay;
+ }
+
+
+ {
+ switch (c)
+ {
+ case xx:
+ do
+ if (asdf)
+ do
+ asdfasdf;
+ while (asdf);
+ else
+ asdfasdf;
+ while (cond);
+ case yy:
+ case xx:
+ case zz:
+ testing;
+ }
+ }
+
+ {
+ if (cond) {
+ foo;
+ }
+ else
+ {
+ bar;
+ }
+ }
+
+ {
+ if (alskdfj ;alsdkfjal;skdjf (;sadlkfsa ;dlkf j;alksdfj ;alskdjf
+ alsdkfj (asldk;fj
+ awith cino=(0 ;lf this one goes to below the paren with ==
+ ;laksjfd ;lsakdjf ;alskdf asd)
+ asdfasdf;)))
+ asdfasdf;
+ }
+
+ int
+ func(a, b)
+ int a;
+ int c;
+ {
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3)
+ )
+ }
+
+ {
+ while (asd)
+ {
+ if (asdf)
+ if (test)
+ if (that)
+ {
+ if (asdf)
+ do
+ cdasd;
+ while (as
+ df);
+ }
+ else
+ if (asdf)
+ asdf;
+ else
+ asdf;
+ asdf;
+ }
+ }
+
+ {
+ s = "/*"; b = ';'
+ s = "/*"; b = ';';
+ a = b;
+ }
+
+ {
+ switch (a)
+ {
+ case a:
+ switch (t)
+ {
+ case 1:
+ cmd;
+ break;
+ case 2:
+ cmd;
+ break;
+ }
+ cmd;
+ break;
+ case b:
+ {
+ int i;
+ cmd;
+ }
+ break;
+ case c: {
+ int i;
+ cmd;
+ }
+ case d: if (cond &&
+ test) { /* this line doesn't work right */
+ int i;
+ cmd;
+ }
+ break;
+ }
+ }
+
+ {
+ if (!(vim_strchr(p_cpo, CPO_BUFOPTGLOB) != NULL && entering) &&
+ (bp_to->b_p_initialized ||
+ (!entering && vim_strchr(p_cpo, CPO_BUFOPT) != NULL)))
+ return;
+ label :
+ asdf = asdf ?
+ asdf : asdf;
+ asdf = asdf ?
+ asdf: asdf;
+ }
+
+ /* Special Comments : This function has the added complexity (compared */
+ /* : to addtolist) of having to check for a detail */
+ /* : texture and add that to the list first. */
+
+ char *(array[100]) = {
+ "testje",
+ "foo",
+ "bar",
+ }
+
+ enum soppie
+ {
+ yes = 0,
+ no,
+ maybe
+ };
+
+ typedef enum soppie
+ {
+ yes = 0,
+ no,
+ maybe
+ };
+
+ static enum
+ {
+ yes = 0,
+ no,
+ maybe
+ } soppie;
+
+ public static enum
+ {
+ yes = 0,
+ no,
+ maybe
+ } soppie;
+
+ static private enum
+ {
+ yes = 0,
+ no,
+ maybe
+ } soppie;
+
+ {
+ int a,
+ b;
+ }
+
+ {
+ struct Type
+ {
+ int i;
+ char *str;
+ } var[] =
+ {
+ 0, "zero",
+ 1, "one",
+ 2, "two",
+ 3, "three"
+ };
+
+ float matrix[3][3] =
+ {
+ {
+ 0,
+ 1,
+ 2
+ },
+ {
+ 3,
+ 4,
+ 5
+ },
+ {
+ 6,
+ 7,
+ 8
+ }
+ };
+ }
+
+ {
+ /* blah ( blah */
+ /* where does this go? */
+
+ /* blah ( blah */
+ cmd;
+
+ func(arg1,
+ /* comment */
+ arg2);
+ a;
+ {
+ b;
+ {
+ c; /* Hey, NOW it indents?! */
+ }
+ }
+
+ {
+ func(arg1,
+ arg2,
+ arg3);
+ /* Hey, what am I doing here? Is this coz of the ","? */
+ }
+ }
+
+ main ()
+ {
+ if (cond)
+ {
+ a = b;
+ }
+ if (cond) {
+ a = c;
+ }
+ if (cond)
+ a = d;
+ return;
+ }
+
+ {
+ case 2: if (asdf &&
+ asdfasdf)
+ aasdf;
+ a = 9;
+ case 3: if (asdf)
+ aasdf;
+ a = 9;
+ case 4: x = 1;
+ y = 2;
+
+ label: if (asdf)
+ here;
+
+ label: if (asdf &&
+ asdfasdf)
+ {
+ }
+
+ label: if (asdf &&
+ asdfasdf) {
+ there;
+ }
+
+ label: if (asdf &&
+ asdfasdf)
+ there;
+ }
+
+ {
+ /*
+ hello with ":set comments= cino=c5"
+ */
+
+ /*
+ hello with ":set comments= cino="
+ */
+ }
+
+
+ {
+ if (a < b) {
+ a = a + 1;
+ } else
+ a = a + 2;
+
+ if (a)
+ do {
+ testing;
+ } while (asdfasdf);
+ a = b + 1;
+ asdfasdf
+ }
+
+ {
+ for ( int i = 0;
+ i < 10; i++ )
+ {
+ }
+ i = 0;
+ }
+
+ class bob
+ {
+ int foo() {return 1;}
+ int bar;
+ }
+
+ main()
+ {
+ while(1)
+ if (foo)
+ {
+ bar;
+ }
+ else {
+ asdf;
+ }
+ misplacedline;
+ }
+
+ {
+ if (clipboard.state == SELECT_DONE
+ && ((row == clipboard.start.lnum
+ && col >= clipboard.start.col)
+ || row > clipboard.start.lnum))
+ }
+
+ {
+ if (1) {i += 4;}
+ where_am_i;
+ return 0;
+ }
+
+ {
+ {
+ } // sdf(asdf
+ if (asdf)
+ asd;
+ }
+
+ {
+ label1:
+ label2:
+ }
+
+ {
+ int fooRet = foo(pBar1, false /*fKB*/,
+ true /*fPTB*/, 3 /*nT*/, false /*fDF*/);
+ f() {
+ for ( i = 0;
+ i < m;
+ /* c */ i++ ) {
+ a = b;
+ }
+ }
+ }
+
+ {
+ f1(/*comment*/);
+ f2();
+ }
+
+ {
+ do {
+ if (foo) {
+ } else
+ ;
+ } while (foo);
+ foo(); // was wrong
+ }
+
+ int x; // no extra indent because of the ;
+ void func()
+ {
+ }
+
+ char *tab[] = {"aaa",
+ "};", /* }; */ NULL}
+ int indented;
+ {}
+
+ char *a[] = {"aaa", "bbb",
+ "ccc", NULL};
+ // here
+
+ char *tab[] = {"aaa",
+ "xx", /* xx */}; /* asdf */
+ int not_indented;
+
+ {
+ do {
+ switch (bla)
+ {
+ case 1: if (foo)
+ bar;
+ }
+ } while (boo);
+ wrong;
+ }
+
+ int foo,
+ bar;
+ int foo;
+
+ #if defined(foo) \
+ && defined(bar)
+ char * xx = "asdf\
+ foo\
+ bor";
+ int x;
+
+ char *foo = "asdf\
+ asdf\
+ asdf",
+ *bar;
+
+ void f()
+ {
+ #if defined(foo) \
+ && defined(bar)
+ char *foo = "asdf\
+ asdf\
+ asdf",
+ *bar;
+ {
+ int i;
+ char *foo = "asdf\
+ asdf\
+ asdf",
+ *bar;
+ }
+ #endif
+ }
+ #endif
+
+ int y; // comment
+ // comment
+
+ // comment
+
+ {
+ Constructor(int a,
+ int b ) : BaseClass(a)
+ {
+ }
+ }
+
+ void foo()
+ {
+ char one,
+ two;
+ struct bla piet,
+ jan;
+ enum foo kees,
+ jannie;
+ static unsigned sdf,
+ krap;
+ unsigned int piet,
+ jan;
+ int
+ kees,
+ jan;
+ }
+
+ {
+ t(int f,
+ int d); // )
+ d();
+ }
+
+ Constructor::Constructor(int a,
+ int b
+ ) :
+ BaseClass(a,
+ b,
+ c),
+ mMember(b),
+ {
+ }
+
+ Constructor::Constructor(int a,
+ int b ) :
+ BaseClass(a)
+ {
+ }
+
+ Constructor::Constructor(int a,
+ int b ) /*x*/ : /*x*/ BaseClass(a),
+ member(b)
+ {
+ }
+
+ A::A(int a, int b)
+ : aa(a),
+ bb(b),
+ cc(c)
+ {
+ }
+
+ class CAbc :
+ public BaseClass1,
+ protected BaseClass2
+ {
+ int Test() { return FALSE; }
+ int Test1() { return TRUE; }
+
+ CAbc(int a, int b ) :
+ BaseClass(a)
+ {
+ switch(xxx)
+ {
+ case abc:
+ asdf();
+ break;
+
+ case 999:
+ baer();
+ break;
+ }
+ }
+
+ public: // <-- this was incorrectly indented before!!
+ void testfall();
+ protected:
+ void testfall();
+ };
+
+ class CAbc : public BaseClass1,
+ protected BaseClass2
+ {
+ };
+
+ static struct
+ {
+ int a;
+ int b;
+ } variable[COUNT] =
+ {
+ {
+ 123,
+ 456
+ },
+ {
+ 123,
+ 456
+ }
+ };
+
+ static struct
+ {
+ int a;
+ int b;
+ } variable[COUNT] =
+ {
+ { 123, 456 },
+ { 123, 456 }
+ };
+
+ void asdf() /* ind_maxparen may cause trouble here */
+ {
+ if ((0
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1)) break;
+ }
+
+ foo()
+ {
+ a = cond ? foo() : asdf
+ + asdf;
+
+ a = cond ?
+ foo() : asdf
+ + asdf;
+ }
+
+ int main(void)
+ {
+ if (a)
+ if (b)
+ 2;
+ else 3;
+ next_line_of_code();
+ }
+
+ barry()
+ {
+ Foo::Foo (int one,
+ int two)
+ : something(4)
+ {}
+ }
+
+ barry()
+ {
+ Foo::Foo (int one, int two)
+ : something(4)
+ {}
+ }
+
+ Constructor::Constructor(int a,
+ int b
+ ) :
+ BaseClass(a,
+ b,
+ c),
+ mMember(b)
+ {
+ }
+ int main ()
+ {
+ if (lala)
+ do
+ ++(*lolo);
+ while (lili
+ && lele);
+ lulu;
+ }
+
+ int main ()
+ {
+ switch (c)
+ {
+ case 'c': if (cond)
+ {
+ }
+ }
+ }
+
+ main()
+ {
+ (void) MyFancyFuasdfadsfnction(
+ argument);
+ }
+
+ main()
+ {
+ char foo[] = "/*";
+ /* as
+ df */
+ hello
+ }
+
+ /* valid namespaces with normal indent */
+ namespace
+ {
+ {
+ 111111111111;
+ }
+ }
+ namespace /* test */
+ {
+ 11111111111111111;
+ }
+ namespace // test
+ {
+ 111111111111111111;
+ }
+ namespace
+ {
+ 111111111111111111;
+ }
+ namespace test
+ {
+ 111111111111111111;
+ }
+ namespace{
+ 111111111111111111;
+ }
+ namespace test{
+ 111111111111111111;
+ }
+ namespace {
+ 111111111111111111;
+ }
+ namespace test {
+ 111111111111111111;
+ namespace test2 {
+ 22222222222222222;
+ }
+ }
+ inline namespace {
+ 111111111111111111;
+ }
+ inline /* test */ namespace {
+ 111111111111111111;
+ }
+ inline/* test */namespace {
+ 111111111111111111;
+ }
+
+ /* invalid namespaces use block indent */
+ namespace test test2 {
+ 111111111111111111111;
+ }
+ namespace11111111111 {
+ 111111111111;
+ }
+ namespace() {
+ 1111111111111;
+ }
+ namespace()
+ {
+ 111111111111111111;
+ }
+ namespace test test2
+ {
+ 1111111111111111111;
+ }
+ namespace111111111
+ {
+ 111111111111111111;
+ }
+ inlinenamespace {
+ 111111111111111111;
+ }
+
+ void getstring() {
+ /* Raw strings */
+ const char* s = R"(
+ test {
+ # comment
+ field: 123
+ }
+ )";
+ }
+
+ void getstring() {
+ const char* s = R"foo(
+ test {
+ # comment
+ field: 123
+ }
+ )foo";
+ }
+
+ {
+ int a[4] = {
+ [0] = 0,
+ [1] = 1,
+ [2] = 2,
+ [3] = 3,
+ };
+ }
+
+ {
+ a = b[2]
+ + 3;
+ }
+
+ {
+ if (1)
+ /* aaaaa
+ * bbbbb
+ */
+ a = 1;
+ }
+
+ void func()
+ {
+ switch (foo)
+ {
+ case (bar):
+ if (baz())
+ quux();
+ break;
+ case (shmoo):
+ if (!bar)
+ {
+ }
+ case (foo1):
+ switch (bar)
+ {
+ case baz:
+ baz_f();
+ break;
+ }
+ break;
+ default:
+ baz();
+ baz();
+ break;
+ }
+ }
+
+ /* end of AUTO */
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_2()
+ new
+ setl cindent ts=4 sw=4
+ setl tw=0 noai fo=croq
+ let &wm = &columns - 20
+
+ let code =<< trim [CODE]
+ {
+
+ /* this is
+ * a real serious important big
+ * comment
+ */
+ /* insert " about life, the universe, and the rest" after "serious" */
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ call search('serious', 'e')
+ normal a about life, the universe, and the rest
+
+ let expected =<< trim [CODE]
+ {
+
+ /* this is
+ * a real serious
+ * about life, the
+ * universe, and the
+ * rest important big
+ * comment
+ */
+ /* insert " about life, the universe, and the rest" after "serious" */
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ set wm&
+ enew! | close
+endfunc
+
+func Test_cindent_3()
+ new
+ setl nocindent ts=4 sw=4
+
+ let code =<< trim [CODE]
+ {
+ /*
+ * Testing for comments, without 'cin' set
+ */
+
+ /*
+ * what happens here?
+ */
+
+ /*
+ the end of the comment, try inserting a line below */
+
+ /* how about
+ this one */
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ call search('comments')
+ normal joabout life
+ call search('happens')
+ normal jothere
+ call search('below')
+ normal oline
+ call search('this')
+ normal Ohello
+
+ let expected =<< trim [CODE]
+ {
+ /*
+ * Testing for comments, without 'cin' set
+ */
+ about life
+
+ /*
+ * what happens here?
+ */
+ there
+
+ /*
+ the end of the comment, try inserting a line below */
+ line
+
+ /* how about
+ hello
+ this one */
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_4()
+ new
+ setl cindent ts=4 sw=4
+
+ let code =<< trim [CODE]
+ {
+ var = this + that + vec[0] * vec[0]
+ + vec[1] * vec[1]
+ + vec2[2] * vec[2];
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ call search('vec2')
+ normal ==
+
+ let expected =<< trim [CODE]
+ {
+ var = this + that + vec[0] * vec[0]
+ + vec[1] * vec[1]
+ + vec2[2] * vec[2];
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_5()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=}4
+
+ let code =<< trim [CODE]
+ {
+ asdf asdflkajds f;
+ if (tes & ting) {
+ asdf asdf asdf ;
+ asdfa sdf asdf;
+ }
+ testing1;
+ if (tes & ting)
+ {
+ asdf asdf asdf ;
+ asdfa sdf asdf;
+ }
+ testing2;
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ call search('testing1')
+ exe "normal k2==/testing2\<CR>"
+ normal k2==
+
+ let expected =<< trim [CODE]
+ {
+ asdf asdflkajds f;
+ if (tes & ting) {
+ asdf asdf asdf ;
+ asdfa sdf asdf;
+ }
+ testing1;
+ if (tes & ting)
+ {
+ asdf asdf asdf ;
+ asdfa sdf asdf;
+ }
+ testing2;
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_6()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=(0,)20
+
+ let code =<< trim [CODE]
+ main ( int first_par, /*
+ * Comment for
+ * first par
+ */
+ int second_par /*
+ * Comment for
+ * second par
+ */
+ )
+ {
+ func( first_par, /*
+ * Comment for
+ * first par
+ */
+ second_par /*
+ * Comment for
+ * second par
+ */
+ );
+
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ call search('main')
+ normal =][
+
+ let expected =<< trim [CODE]
+ main ( int first_par, /*
+ * Comment for
+ * first par
+ */
+ int second_par /*
+ * Comment for
+ * second par
+ */
+ )
+ {
+ func( first_par, /*
+ * Comment for
+ * first par
+ */
+ second_par /*
+ * Comment for
+ * second par
+ */
+ );
+
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_7()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=es,n0s
+
+ let code =<< trim [CODE]
+ main(void)
+ {
+ /* Make sure that cino=X0s is not parsed like cino=Xs. */
+ if (cond)
+ foo();
+ else
+ {
+ bar();
+ }
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ call search('main')
+ normal =][
+
+ let expected =<< trim [CODE]
+ main(void)
+ {
+ /* Make sure that cino=X0s is not parsed like cino=Xs. */
+ if (cond)
+ foo();
+ else
+ {
+ bar();
+ }
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_8()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=
+
+ let code =<< trim [CODE]
+
+ {
+ do
+ {
+ if ()
+ {
+ if ()
+ asdf;
+ else
+ asdf;
+ }
+ } while ();
+ cmd; /* this should go under the } */
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+
+ {
+ do
+ {
+ if ()
+ {
+ if ()
+ asdf;
+ else
+ asdf;
+ }
+ } while ();
+ cmd; /* this should go under the } */
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_9()
+ new
+ setl cindent ts=4 sw=4
+
+ let code =<< trim [CODE]
+
+ void f()
+ {
+ if ( k() ) {
+ l();
+
+ } else { /* Start (two words) end */
+ m();
+ }
+
+ n();
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+
+ void f()
+ {
+ if ( k() ) {
+ l();
+
+ } else { /* Start (two words) end */
+ m();
+ }
+
+ n();
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_10()
+ new
+ setl cindent ts=4 sw=4
+ setl cino={s,e-s
+
+ let code =<< trim [CODE]
+
+ void f()
+ {
+ if ( k() )
+ {
+ l();
+ } else { /* Start (two words) end */
+ m();
+ }
+ n(); /* should be under the if () */
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+
+ void f()
+ {
+ if ( k() )
+ {
+ l();
+ } else { /* Start (two words) end */
+ m();
+ }
+ n(); /* should be under the if () */
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_11()
+ new
+ setl cindent ts=4 sw=4
+ setl cino={s,fs
+
+ let code =<< trim [CODE]
+ void bar(void)
+ {
+ static array[2][2] =
+ {
+ { 1, 2 },
+ { 3, 4 },
+ }
+
+ while (a)
+ {
+ foo(&a);
+ }
+
+ {
+ int a;
+ {
+ a = a + 1;
+ }
+ }
+ b = a;
+ }
+
+ void func(void)
+ {
+ a = 1;
+ {
+ b = 2;
+ }
+ c = 3;
+ d = 4;
+ }
+ /* foo */
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ exe "normal ]]=/ foo\<CR>"
+
+ let expected =<< trim [CODE]
+ void bar(void)
+ {
+ static array[2][2] =
+ {
+ { 1, 2 },
+ { 3, 4 },
+ }
+
+ while (a)
+ {
+ foo(&a);
+ }
+
+ {
+ int a;
+ {
+ a = a + 1;
+ }
+ }
+ b = a;
+ }
+
+ void func(void)
+ {
+ a = 1;
+ {
+ b = 2;
+ }
+ c = 3;
+ d = 4;
+ }
+ /* foo */
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_12()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=
+
+ let code =<< trim [CODE]
+ a()
+ {
+ do {
+ a = a +
+ a;
+ } while ( a ); /* add text under this line */
+ if ( a )
+ a;
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ call search('while')
+ normal ohere
+
+ let expected =<< trim [CODE]
+ a()
+ {
+ do {
+ a = a +
+ a;
+ } while ( a ); /* add text under this line */
+ here
+ if ( a )
+ a;
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_13()
+ new
+ setl cindent ts=4 sw=4
+ setl cino= com=
+
+ let code =<< trim [CODE]
+ a()
+ {
+ label1:
+ /* hmm */
+ // comment
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ call search('comment')
+ exe "normal olabel2: b();\rlabel3 /* post */:\r/* pre */ label4:\r" .
+ \ "f(/*com*/);\rif (/*com*/)\rcmd();"
+
+ let expected =<< trim [CODE]
+ a()
+ {
+ label1:
+ /* hmm */
+ // comment
+ label2: b();
+ label3 /* post */:
+ /* pre */ label4:
+ f(/*com*/);
+ if (/*com*/)
+ cmd();
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_14()
+ new
+ setl cindent ts=4 sw=4
+ setl comments& comments^=s:/*,m:**,ex:*/
+
+ let code =<< trim [CODE]
+ /*
+ * A simple comment
+ */
+
+ /*
+ ** A different comment
+ */
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ call search('simple')
+ normal =5j
+
+ let expected =<< trim [CODE]
+ /*
+ * A simple comment
+ */
+
+ /*
+ ** A different comment
+ */
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_15()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=c0
+ setl comments& comments-=s1:/* comments^=s0:/*
+
+ let code =<< trim [CODE]
+ void f()
+ {
+
+ /*********
+ A comment.
+ *********/
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void f()
+ {
+
+ /*********
+ A comment.
+ *********/
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_16()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=c0,C1
+ setl comments& comments-=s1:/* comments^=s0:/*
+
+ let code =<< trim [CODE]
+ void f()
+ {
+
+ /*********
+ A comment.
+ *********/
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void f()
+ {
+
+ /*********
+ A comment.
+ *********/
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_17()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=
+
+ let code =<< trim [CODE]
+ void f()
+ {
+ c = c1 &&
+ (
+ c2 ||
+ c3
+ ) && c4;
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void f()
+ {
+ c = c1 &&
+ (
+ c2 ||
+ c3
+ ) && c4;
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_18()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=(s
+
+ let code =<< trim [CODE]
+ void f()
+ {
+ c = c1 &&
+ (
+ c2 ||
+ c3
+ ) && c4;
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void f()
+ {
+ c = c1 &&
+ (
+ c2 ||
+ c3
+ ) && c4;
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_19()
+ new
+ setl cindent ts=4 sw=4
+ set cino=(s,U1
+
+ let code =<< trim [CODE]
+ void f()
+ {
+ c = c1 &&
+ (
+ c2 ||
+ c3
+ ) && c4;
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void f()
+ {
+ c = c1 &&
+ (
+ c2 ||
+ c3
+ ) && c4;
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_20()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=(0
+
+ let code =<< trim [CODE]
+ void f()
+ {
+ if ( c1
+ && ( c2
+ || c3))
+ foo;
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void f()
+ {
+ if ( c1
+ && ( c2
+ || c3))
+ foo;
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_21()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=(0,w1
+
+ let code =<< trim [CODE]
+ void f()
+ {
+ if ( c1
+ && ( c2
+ || c3))
+ foo;
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void f()
+ {
+ if ( c1
+ && ( c2
+ || c3))
+ foo;
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_22()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=(s
+
+ let code =<< trim [CODE]
+ void f()
+ {
+ c = c1 && (
+ c2 ||
+ c3
+ ) && c4;
+ if (
+ c1 && c2
+ )
+ foo;
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void f()
+ {
+ c = c1 && (
+ c2 ||
+ c3
+ ) && c4;
+ if (
+ c1 && c2
+ )
+ foo;
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_23()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=(s,m1
+
+ let code =<< trim [CODE]
+ void f()
+ {
+ c = c1 && (
+ c2 ||
+ c3
+ ) && c4;
+ if (
+ c1 && c2
+ )
+ foo;
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void f()
+ {
+ c = c1 && (
+ c2 ||
+ c3
+ ) && c4;
+ if (
+ c1 && c2
+ )
+ foo;
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_24()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=b1
+
+ let code =<< trim [CODE]
+ void f()
+ {
+ switch (x)
+ {
+ case 1:
+ a = b;
+ break;
+ default:
+ a = 0;
+ break;
+ }
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void f()
+ {
+ switch (x)
+ {
+ case 1:
+ a = b;
+ break;
+ default:
+ a = 0;
+ break;
+ }
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_25()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=(0,W5
+
+ let code =<< trim [CODE]
+ void f()
+ {
+ invokeme(
+ argu,
+ ment);
+ invokeme(
+ argu,
+ ment
+ );
+ invokeme(argu,
+ ment
+ );
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void f()
+ {
+ invokeme(
+ argu,
+ ment);
+ invokeme(
+ argu,
+ ment
+ );
+ invokeme(argu,
+ ment
+ );
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_26()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=/6
+
+ let code =<< trim [CODE]
+ void f()
+ {
+ statement;
+ // comment 1
+ // comment 2
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void f()
+ {
+ statement;
+ // comment 1
+ // comment 2
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_27()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=
+
+ let code =<< trim [CODE]
+ void f()
+ {
+ statement;
+ // comment 1
+ // comment 2
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ exe "normal ]]/comment 1/+1\<CR>=="
+
+ let expected =<< trim [CODE]
+ void f()
+ {
+ statement;
+ // comment 1
+ // comment 2
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_28()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=g0
+
+ let code =<< trim [CODE]
+ class CAbc
+ {
+ int Test() { return FALSE; }
+
+ public: // comment
+ void testfall();
+ protected:
+ void testfall();
+ };
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ class CAbc
+ {
+ int Test() { return FALSE; }
+
+ public: // comment
+ void testfall();
+ protected:
+ void testfall();
+ };
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_29()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=(0,gs,hs
+
+ let code =<< trim [CODE]
+ class Foo : public Bar
+ {
+ public:
+ virtual void method1(void) = 0;
+ virtual void method2(int arg1,
+ int arg2,
+ int arg3) = 0;
+ };
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ class Foo : public Bar
+ {
+ public:
+ virtual void method1(void) = 0;
+ virtual void method2(int arg1,
+ int arg2,
+ int arg3) = 0;
+ };
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_30()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=+20
+
+ let code =<< [CODE]
+ void
+foo()
+{
+ if (a)
+ {
+ } else
+ asdf;
+}
+[CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< [CODE]
+ void
+foo()
+{
+ if (a)
+ {
+ } else
+ asdf;
+}
+
+[CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_31()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=(0,W2s
+
+ let code =<< trim [CODE]
+
+ {
+ averylongfunctionnamelongfunctionnameaverylongfunctionname()->asd(
+ asdasdf,
+ func(asdf,
+ asdfadsf),
+ asdfasdf
+ );
+
+ /* those are ugly, but consequent */
+
+ func()->asd(asdasdf,
+ averylongfunctionname(
+ abc,
+ dec)->averylongfunctionname(
+ asdfadsf,
+ asdfasdf,
+ asdfasdf,
+ ),
+ func(asdfadf,
+ asdfasdf
+ ),
+ asdasdf
+ );
+
+ averylongfunctionnameaverylongfunctionnameavery()->asd(fasdf(
+ abc,
+ dec)->asdfasdfasdf(
+ asdfadsf,
+ asdfasdf,
+ asdfasdf,
+ ),
+ func(asdfadf,
+ asdfasdf),
+ asdasdf
+ );
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+
+ {
+ averylongfunctionnamelongfunctionnameaverylongfunctionname()->asd(
+ asdasdf,
+ func(asdf,
+ asdfadsf),
+ asdfasdf
+ );
+
+ /* those are ugly, but consequent */
+
+ func()->asd(asdasdf,
+ averylongfunctionname(
+ abc,
+ dec)->averylongfunctionname(
+ asdfadsf,
+ asdfasdf,
+ asdfasdf,
+ ),
+ func(asdfadf,
+ asdfasdf
+ ),
+ asdasdf
+ );
+
+ averylongfunctionnameaverylongfunctionnameavery()->asd(fasdf(
+ abc,
+ dec)->asdfasdfasdf(
+ asdfadsf,
+ asdfasdf,
+ asdfasdf,
+ ),
+ func(asdfadf,
+ asdfasdf),
+ asdasdf
+ );
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_32()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=M1
+
+ let code =<< trim [CODE]
+ int main ()
+ {
+ if (cond1 &&
+ cond2
+ )
+ foo;
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ int main ()
+ {
+ if (cond1 &&
+ cond2
+ )
+ foo;
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_33()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=(0,ts
+
+ let code =<< trim [CODE]
+ void func(int a
+ #if defined(FOO)
+ , int b
+ , int c
+ #endif
+ )
+ {
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal 2j=][
+
+ let expected =<< trim [CODE]
+ void func(int a
+ #if defined(FOO)
+ , int b
+ , int c
+ #endif
+ )
+ {
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_34()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=(0
+
+ let code =<< trim [CODE]
+
+ void
+ func(int a
+ #if defined(FOO)
+ , int b
+ , int c
+ #endif
+ )
+ {
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal =][
+
+ let expected =<< trim [CODE]
+
+ void
+ func(int a
+ #if defined(FOO)
+ , int b
+ , int c
+ #endif
+ )
+ {
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_35()
+ new
+ setl cindent ts=4 sw=4
+ setl cino&
+
+ let code =<< trim [CODE]
+ void func(void)
+ {
+ if(x==y)
+ if(y==z)
+ foo=1;
+ else { bar=1;
+ baz=2;
+ }
+ printf("Foo!\n");
+ }
+
+ void func1(void)
+ {
+ char* tab[] = {"foo", "bar",
+ "baz", "quux",
+ "this line used", "to be indented incorrectly"};
+ foo();
+ }
+
+ void func2(void)
+ {
+ int tab[] =
+ {1, 2,
+ 3, 4,
+ 5, 6};
+
+ printf("This line used to be indented incorrectly.\n");
+ }
+
+ int foo[]
+ #ifdef BAR
+
+ = { 1, 2, 3,
+ 4, 5, 6 }
+
+ #endif
+ ;
+ int baz;
+
+ void func3(void)
+ {
+ int tab[] = {
+ 1, 2,
+ 3, 4,
+ 5, 6};
+
+ printf("Don't you dare indent this line incorrectly!\n");
+ }
+
+ void
+ func4(a, b,
+ c)
+ int a;
+ int b;
+ int c;
+ {
+ }
+
+ void
+ func5(
+ int a,
+ int b)
+ {
+ }
+
+ void
+ func6(
+ int a)
+ {
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=7][
+
+ let expected =<< trim [CODE]
+ void func(void)
+ {
+ if(x==y)
+ if(y==z)
+ foo=1;
+ else { bar=1;
+ baz=2;
+ }
+ printf("Foo!\n");
+ }
+
+ void func1(void)
+ {
+ char* tab[] = {"foo", "bar",
+ "baz", "quux",
+ "this line used", "to be indented incorrectly"};
+ foo();
+ }
+
+ void func2(void)
+ {
+ int tab[] =
+ {1, 2,
+ 3, 4,
+ 5, 6};
+
+ printf("This line used to be indented incorrectly.\n");
+ }
+
+ int foo[]
+ #ifdef BAR
+
+ = { 1, 2, 3,
+ 4, 5, 6 }
+
+ #endif
+ ;
+ int baz;
+
+ void func3(void)
+ {
+ int tab[] = {
+ 1, 2,
+ 3, 4,
+ 5, 6};
+
+ printf("Don't you dare indent this line incorrectly!\n");
+ }
+
+ void
+ func4(a, b,
+ c)
+ int a;
+ int b;
+ int c;
+ {
+ }
+
+ void
+ func5(
+ int a,
+ int b)
+ {
+ }
+
+ void
+ func6(
+ int a)
+ {
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_36()
+ new
+ setl cindent ts=4 sw=4
+ setl cino&
+ setl cino+=l1
+
+ let code =<< trim [CODE]
+ void func(void)
+ {
+ int tab[] =
+ {
+ 1, 2, 3,
+ 4, 5, 6};
+
+ printf("Indent this line correctly!\n");
+
+ switch (foo)
+ {
+ case bar:
+ printf("bar");
+ break;
+ case baz: {
+ printf("baz");
+ break;
+ }
+ case quux:
+ printf("But don't break the indentation of this instruction\n");
+ break;
+ }
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void func(void)
+ {
+ int tab[] =
+ {
+ 1, 2, 3,
+ 4, 5, 6};
+
+ printf("Indent this line correctly!\n");
+
+ switch (foo)
+ {
+ case bar:
+ printf("bar");
+ break;
+ case baz: {
+ printf("baz");
+ break;
+ }
+ case quux:
+ printf("But don't break the indentation of this instruction\n");
+ break;
+ }
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_37()
+ new
+ setl cindent ts=4 sw=4
+ setl cino&
+
+ let code =<< trim [CODE]
+ void func(void)
+ {
+ cout << "a"
+ << "b"
+ << ") :"
+ << "c";
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void func(void)
+ {
+ cout << "a"
+ << "b"
+ << ") :"
+ << "c";
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_38()
+ new
+ setl cindent ts=4 sw=4
+ setl com=s1:/*,m:*,ex:*/
+
+ let code =<< trim [CODE]
+ void func(void)
+ {
+ /*
+ * This is a comment.
+ */
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]3jofoo();
+
+ let expected =<< trim [CODE]
+ void func(void)
+ {
+ /*
+ * This is a comment.
+ */
+ foo();
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_39()
+ new
+ setl cindent ts=4 sw=4
+ setl cino&
+
+ let code =<< trim [CODE]
+ void func(void)
+ {
+ for (int i = 0; i < 10; ++i)
+ if (i & 1) {
+ foo(1);
+ } else
+ foo(0);
+ baz();
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void func(void)
+ {
+ for (int i = 0; i < 10; ++i)
+ if (i & 1) {
+ foo(1);
+ } else
+ foo(0);
+ baz();
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_40()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=k2s,(0
+
+ let code =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+
+ if ( c1
+ && ( c2
+ || c3))
+ foo;
+ func( c1
+ && ( c2
+ || c3))
+ foo;
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+
+ if ( c1
+ && ( c2
+ || c3))
+ foo;
+ func( c1
+ && ( c2
+ || c3))
+ foo;
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_41()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=k2s,(s
+
+ let code =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+
+ if ( c1
+ && ( c2
+ || c3))
+ foo;
+ func( c1
+ && ( c2
+ || c3))
+ foo;
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+
+ if ( c1
+ && ( c2
+ || c3))
+ foo;
+ func( c1
+ && ( c2
+ || c3))
+ foo;
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_42()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=k2s,(s,U1
+
+ let code =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+ if (c123456789
+ && (c22345
+ || c3))
+ printf("foo\n");
+
+ c = c1 &&
+ (
+ c2 ||
+ c3
+ ) && c4;
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+ if (c123456789
+ && (c22345
+ || c3))
+ printf("foo\n");
+
+ c = c1 &&
+ (
+ c2 ||
+ c3
+ ) && c4;
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_43()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=k2s,(0,W4
+
+ let code =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+ if (c123456789
+ && (c22345
+ || c3))
+ printf("foo\n");
+
+ if ( c1
+ && ( c2
+ || c3))
+ foo;
+
+ a_long_line(
+ argument,
+ argument);
+ a_short_line(argument,
+ argument);
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+ if (c123456789
+ && (c22345
+ || c3))
+ printf("foo\n");
+
+ if ( c1
+ && ( c2
+ || c3))
+ foo;
+
+ a_long_line(
+ argument,
+ argument);
+ a_short_line(argument,
+ argument);
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_44()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=k2s,u2
+
+ let code =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+ if (c123456789
+ && (c22345
+ || c3))
+ printf("foo\n");
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+ if (c123456789
+ && (c22345
+ || c3))
+ printf("foo\n");
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_45()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=k2s,(0,w1
+
+ let code =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+ if (c123456789
+ && (c22345
+ || c3))
+ printf("foo\n");
+
+ if ( c1
+ && ( c2
+ || c3))
+ foo;
+ func( c1
+ && ( c2
+ || c3))
+ foo;
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+ if (c123456789
+ && (c22345
+ || c3))
+ printf("foo\n");
+
+ if ( c1
+ && ( c2
+ || c3))
+ foo;
+ func( c1
+ && ( c2
+ || c3))
+ foo;
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_46()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=k2,(s
+
+ let code =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal ]]=][
+
+ let expected =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_47()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=N-s
+
+ let code =<< trim [CODE]
+ NAMESPACESTART
+ /* valid namespaces with normal indent */
+ namespace
+ {
+ {
+ 111111111111;
+ }
+ }
+ namespace /* test */
+ {
+ 11111111111111111;
+ }
+ namespace // test
+ {
+ 111111111111111111;
+ }
+ namespace
+ {
+ 111111111111111111;
+ }
+ namespace test
+ {
+ 111111111111111111;
+ }
+ namespace test::cpp17
+ {
+ 111111111111111111;
+ }
+ namespace ::incorrectcpp17
+ {
+ 111111111111111111;
+ }
+ namespace test::incorrectcpp17::
+ {
+ 111111111111111111;
+ }
+ namespace test:incorrectcpp17
+ {
+ 111111111111111111;
+ }
+ namespace test:::incorrectcpp17
+ {
+ 111111111111111111;
+ }
+ namespace{
+ 111111111111111111;
+ }
+ namespace test{
+ 111111111111111111;
+ }
+ namespace {
+ 111111111111111111;
+ }
+ namespace test {
+ 111111111111111111;
+ namespace test2 {
+ 22222222222222222;
+ }
+ }
+ inline namespace {
+ 111111111111111111;
+ }
+ inline /* test */ namespace {
+ 111111111111111111;
+ }
+ inline/* test */namespace {
+ 111111111111111111;
+ }
+
+ /* invalid namespaces use block indent */
+ namespace test test2 {
+ 111111111111111111111;
+ }
+ namespace11111111111 {
+ 111111111111;
+ }
+ namespace() {
+ 1111111111111;
+ }
+ namespace()
+ {
+ 111111111111111111;
+ }
+ namespace test test2
+ {
+ 1111111111111111111;
+ }
+ namespace111111111
+ {
+ 111111111111111111;
+ }
+ inlinenamespace {
+ 111111111111111111;
+ }
+ NAMESPACEEND
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ call search('^NAMESPACESTART')
+ exe "normal =/^NAMESPACEEND\n"
+
+ let expected =<< trim [CODE]
+ NAMESPACESTART
+ /* valid namespaces with normal indent */
+ namespace
+ {
+ {
+ 111111111111;
+ }
+ }
+ namespace /* test */
+ {
+ 11111111111111111;
+ }
+ namespace // test
+ {
+ 111111111111111111;
+ }
+ namespace
+ {
+ 111111111111111111;
+ }
+ namespace test
+ {
+ 111111111111111111;
+ }
+ namespace test::cpp17
+ {
+ 111111111111111111;
+ }
+ namespace ::incorrectcpp17
+ {
+ 111111111111111111;
+ }
+ namespace test::incorrectcpp17::
+ {
+ 111111111111111111;
+ }
+ namespace test:incorrectcpp17
+ {
+ 111111111111111111;
+ }
+ namespace test:::incorrectcpp17
+ {
+ 111111111111111111;
+ }
+ namespace{
+ 111111111111111111;
+ }
+ namespace test{
+ 111111111111111111;
+ }
+ namespace {
+ 111111111111111111;
+ }
+ namespace test {
+ 111111111111111111;
+ namespace test2 {
+ 22222222222222222;
+ }
+ }
+ inline namespace {
+ 111111111111111111;
+ }
+ inline /* test */ namespace {
+ 111111111111111111;
+ }
+ inline/* test */namespace {
+ 111111111111111111;
+ }
+
+ /* invalid namespaces use block indent */
+ namespace test test2 {
+ 111111111111111111111;
+ }
+ namespace11111111111 {
+ 111111111111;
+ }
+ namespace() {
+ 1111111111111;
+ }
+ namespace()
+ {
+ 111111111111111111;
+ }
+ namespace test test2
+ {
+ 1111111111111111111;
+ }
+ namespace111111111
+ {
+ 111111111111111111;
+ }
+ inlinenamespace {
+ 111111111111111111;
+ }
+ NAMESPACEEND
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_48()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=j1,J1
+
+ let code =<< trim [CODE]
+ JSSTART
+ var bar = {
+ foo: {
+ that: this,
+ some: ok,
+ },
+ "bar":{
+ a : 2,
+ b: "123abc",
+ x: 4,
+ "y": 5
+ }
+ }
+ JSEND
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ call search('^JSSTART')
+ exe "normal =/^JSEND\n"
+
+ let expected =<< trim [CODE]
+ JSSTART
+ var bar = {
+ foo: {
+ that: this,
+ some: ok,
+ },
+ "bar":{
+ a : 2,
+ b: "123abc",
+ x: 4,
+ "y": 5
+ }
+ }
+ JSEND
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_49()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=j1,J1
+
+ let code =<< trim [CODE]
+ JSSTART
+ var foo = [
+ 1,
+ 2,
+ 3
+ ];
+ JSEND
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ call search('^JSSTART')
+ exe "normal =/^JSEND\n"
+
+ let expected =<< trim [CODE]
+ JSSTART
+ var foo = [
+ 1,
+ 2,
+ 3
+ ];
+ JSEND
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_50()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=j1,J1
+
+ let code =<< trim [CODE]
+ JSSTART
+ function bar() {
+ var foo = [
+ 1,
+ 2,
+ 3
+ ];
+ }
+ JSEND
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ call search('^JSSTART')
+ exe "normal =/^JSEND\n"
+
+ let expected =<< trim [CODE]
+ JSSTART
+ function bar() {
+ var foo = [
+ 1,
+ 2,
+ 3
+ ];
+ }
+ JSEND
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_51()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=j1,J1
+
+ let code =<< trim [CODE]
+ JSSTART
+ (function($){
+
+ if (cond &&
+ cond) {
+ stmt;
+ }
+ window.something.left =
+ (width - 50 + offset) + "px";
+ var class_name='myclass';
+
+ function private_method() {
+ }
+
+ var public_method={
+ method: function(options,args){
+ private_method();
+ }
+ }
+
+ function init(options) {
+
+ $(this).data(class_name+'_public',$.extend({},{
+ foo: 'bar',
+ bar: 2,
+ foobar: [
+ 1,
+ 2,
+ 3
+ ],
+ callback: function(){
+ return true;
+ }
+ }, options||{}));
+ }
+
+ $.fn[class_name]=function() {
+
+ var _arguments=arguments;
+ return this.each(function(){
+
+ var options=$(this).data(class_name+'_public');
+ if (!options) {
+ init.apply(this,_arguments);
+
+ } else {
+ var method=public_method[_arguments[0]];
+
+ if (typeof(method)!='function') {
+ console.log(class_name+' has no method "'+_arguments[0]+'"');
+ return false;
+ }
+ _arguments[0]=options;
+ method.apply(this,_arguments);
+ }
+ });
+ }
+
+ })(jQuery);
+ JSEND
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ call search('^JSSTART')
+ exe "normal =/^JSEND\n"
+
+ let expected =<< trim [CODE]
+ JSSTART
+ (function($){
+
+ if (cond &&
+ cond) {
+ stmt;
+ }
+ window.something.left =
+ (width - 50 + offset) + "px";
+ var class_name='myclass';
+
+ function private_method() {
+ }
+
+ var public_method={
+ method: function(options,args){
+ private_method();
+ }
+ }
+
+ function init(options) {
+
+ $(this).data(class_name+'_public',$.extend({},{
+ foo: 'bar',
+ bar: 2,
+ foobar: [
+ 1,
+ 2,
+ 3
+ ],
+ callback: function(){
+ return true;
+ }
+ }, options||{}));
+ }
+
+ $.fn[class_name]=function() {
+
+ var _arguments=arguments;
+ return this.each(function(){
+
+ var options=$(this).data(class_name+'_public');
+ if (!options) {
+ init.apply(this,_arguments);
+
+ } else {
+ var method=public_method[_arguments[0]];
+
+ if (typeof(method)!='function') {
+ console.log(class_name+' has no method "'+_arguments[0]+'"');
+ return false;
+ }
+ _arguments[0]=options;
+ method.apply(this,_arguments);
+ }
+ });
+ }
+
+ })(jQuery);
+ JSEND
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_52()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=j1,J1
+
+ let code =<< trim [CODE]
+ JSSTART
+ function init(options) {
+ $(this).data(class_name+'_public',$.extend({},{
+ foo: 'bar',
+ bar: 2,
+ foobar: [
+ 1,
+ 2,
+ 3
+ ],
+ callback: function(){
+ return true;
+ }
+ }, options||{}));
+ }
+ JSEND
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ call search('^JSSTART')
+ exe "normal =/^JSEND\n"
+
+ let expected =<< trim [CODE]
+ JSSTART
+ function init(options) {
+ $(this).data(class_name+'_public',$.extend({},{
+ foo: 'bar',
+ bar: 2,
+ foobar: [
+ 1,
+ 2,
+ 3
+ ],
+ callback: function(){
+ return true;
+ }
+ }, options||{}));
+ }
+ JSEND
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_53()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=j1,J1
+
+ let code =<< trim [CODE]
+ JSSTART
+ (function($){
+ function init(options) {
+ $(this).data(class_name+'_public',$.extend({},{
+ foo: 'bar',
+ bar: 2,
+ foobar: [
+ 1,
+ 2,
+ 3
+ ],
+ callback: function(){
+ return true;
+ }
+ }, options||{}));
+ }
+ })(jQuery);
+ JSEND
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ call search('^JSSTART')
+ exe "normal =/^JSEND\n"
+
+ let expected =<< trim [CODE]
+ JSSTART
+ (function($){
+ function init(options) {
+ $(this).data(class_name+'_public',$.extend({},{
+ foo: 'bar',
+ bar: 2,
+ foobar: [
+ 1,
+ 2,
+ 3
+ ],
+ callback: function(){
+ return true;
+ }
+ }, options||{}));
+ }
+ })(jQuery);
+ JSEND
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_54()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=j1,J1,+2
+
+ let code =<< trim [CODE]
+ JSSTART
+ // Results of JavaScript indent
+ // 1
+ (function(){
+ var a = [
+ 'a',
+ 'b',
+ 'c',
+ 'd',
+ 'e',
+ 'f',
+ 'g',
+ 'h',
+ 'i'
+ ];
+ }())
+
+ // 2
+ (function(){
+ var a = [
+ 0 +
+ 5 *
+ 9 *
+ 'a',
+ 'b',
+ 0 +
+ 5 *
+ 9 *
+ 'c',
+ 'd',
+ 'e',
+ 'f',
+ 'g',
+ 'h',
+ 'i'
+ ];
+ }())
+
+ // 3
+ (function(){
+ var a = [
+ 0 +
+ // comment 1
+ 5 *
+ /* comment 2 */
+ 9 *
+ 'a',
+ 'b',
+ 0 +
+ 5 *
+ 9 *
+ 'c',
+ 'd',
+ 'e',
+ 'f',
+ 'g',
+ 'h',
+ 'i'
+ ];
+ }())
+
+ // 4
+ {
+ var a = [
+ 0,
+ 1
+ ];
+ var b;
+ var c;
+ }
+
+ // 5
+ {
+ var a = [
+ [
+ 0
+ ],
+ 2,
+ 3
+ ];
+ }
+
+ // 6
+ {
+ var a = [
+ [
+ 0,
+ 1
+ ],
+ 2,
+ 3
+ ];
+ }
+
+ // 7
+ {
+ var a = [
+ // [
+ 0,
+ // 1
+ // ],
+ 2,
+ 3
+ ];
+ }
+
+ // 8
+ var x = [
+ (function(){
+ var a,
+ b,
+ c,
+ d,
+ e,
+ f,
+ g,
+ h,
+ i;
+ })
+ ];
+
+ // 9
+ var a = [
+ 0 +
+ 5 *
+ 9 *
+ 'a',
+ 'b',
+ 0 +
+ 5 *
+ 9 *
+ 'c',
+ 'd',
+ 'e',
+ 'f',
+ 'g',
+ 'h',
+ 'i'
+ ];
+
+ // 10
+ var a,
+ b,
+ c,
+ d,
+ e,
+ f,
+ g,
+ h,
+ i;
+ JSEND
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ call search('^JSSTART')
+ exe "normal =/^JSEND\n"
+
+ let expected =<< trim [CODE]
+ JSSTART
+ // Results of JavaScript indent
+ // 1
+ (function(){
+ var a = [
+ 'a',
+ 'b',
+ 'c',
+ 'd',
+ 'e',
+ 'f',
+ 'g',
+ 'h',
+ 'i'
+ ];
+ }())
+
+ // 2
+ (function(){
+ var a = [
+ 0 +
+ 5 *
+ 9 *
+ 'a',
+ 'b',
+ 0 +
+ 5 *
+ 9 *
+ 'c',
+ 'd',
+ 'e',
+ 'f',
+ 'g',
+ 'h',
+ 'i'
+ ];
+ }())
+
+ // 3
+ (function(){
+ var a = [
+ 0 +
+ // comment 1
+ 5 *
+ /* comment 2 */
+ 9 *
+ 'a',
+ 'b',
+ 0 +
+ 5 *
+ 9 *
+ 'c',
+ 'd',
+ 'e',
+ 'f',
+ 'g',
+ 'h',
+ 'i'
+ ];
+ }())
+
+ // 4
+ {
+ var a = [
+ 0,
+ 1
+ ];
+ var b;
+ var c;
+ }
+
+ // 5
+ {
+ var a = [
+ [
+ 0
+ ],
+ 2,
+ 3
+ ];
+ }
+
+ // 6
+ {
+ var a = [
+ [
+ 0,
+ 1
+ ],
+ 2,
+ 3
+ ];
+ }
+
+ // 7
+ {
+ var a = [
+ // [
+ 0,
+ // 1
+ // ],
+ 2,
+ 3
+ ];
+ }
+
+ // 8
+ var x = [
+ (function(){
+ var a,
+ b,
+ c,
+ d,
+ e,
+ f,
+ g,
+ h,
+ i;
+ })
+ ];
+
+ // 9
+ var a = [
+ 0 +
+ 5 *
+ 9 *
+ 'a',
+ 'b',
+ 0 +
+ 5 *
+ 9 *
+ 'c',
+ 'd',
+ 'e',
+ 'f',
+ 'g',
+ 'h',
+ 'i'
+ ];
+
+ // 10
+ var a,
+ b,
+ c,
+ d,
+ e,
+ f,
+ g,
+ h,
+ i;
+ JSEND
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_55()
+ new
+ setl cindent ts=4 sw=4
+ setl cino&
+
+ let code =<< trim [CODE]
+ /* start of define */
+ {
+ }
+ #define AAA \
+ BBB\
+ CCC
+
+ #define CNT \
+ 1 + \
+ 2 + \
+ 4
+ /* end of define */
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ call search('start of define')
+ exe "normal =/end of define\n"
+
+ let expected =<< trim [CODE]
+ /* start of define */
+ {
+ }
+ #define AAA \
+ BBB\
+ CCC
+
+ #define CNT \
+ 1 + \
+ 2 + \
+ 4
+ /* end of define */
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
+func Test_cindent_56()
+ new
+ setl cindent ts=4 sw=4
+ setl cino&
+
+ let code =<< trim [CODE]
+ {
+ a = second/*bug*/*line;
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ call search('a = second')
+ normal ox
+
+ let expected =<< trim [CODE]
+ {
+ a = second/*bug*/*line;
+ x
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
" this was going beyond the end of the line.
func Test_cindent_case()
new
- call setline(1, "case x: // x")
+ call setline(1, 'case x: // x')
set cindent
norm! f:a:
+ call assert_equal('case x:: // x', getline(1))
+
+ set cindent&
bwipe!
endfunc
@@ -173,4 +5347,23 @@ func Test_cindent_pragma()
enew! | close
endfunc
+func Test_backslash_at_end_of_line()
+ new
+ exe "norm v>O'\\\<C-m>-"
+ exe "norm \<C-q>="
+ bwipe!
+endfunc
+
+func Test_find_brace_backwards()
+ " this was looking beyond the end of the line
+ new
+ norm R/*
+ norm o0{
+ norm o//
+ norm V{=
+ call assert_equal(['/*', ' 0{', '//'], getline(1, 3))
+ bwipe!
+endfunc
+
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim
index 1672b0e840..ff4cbe544c 100644
--- a/src/nvim/testdir/test_cmdline.vim
+++ b/src/nvim/testdir/test_cmdline.vim
@@ -500,8 +500,16 @@ func Test_fullcommand()
for [in, want] in items(tests)
call assert_equal(want, fullcommand(in))
endfor
+ call assert_equal('', fullcommand(v:_null_string))
call assert_equal('syntax', 'syn'->fullcommand())
+
+ command -buffer BufferLocalCommand :
+ command GlobalCommand :
+ call assert_equal('GlobalCommand', fullcommand('GlobalCom'))
+ call assert_equal('BufferLocalCommand', fullcommand('BufferL'))
+ delcommand BufferLocalCommand
+ delcommand GlobalCommand
endfunc
func Test_shellcmd_completion()
diff --git a/src/nvim/testdir/test_compiler.vim b/src/nvim/testdir/test_compiler.vim
index aaa2301bca..c0c572ce65 100644
--- a/src/nvim/testdir/test_compiler.vim
+++ b/src/nvim/testdir/test_compiler.vim
@@ -19,6 +19,9 @@ func Test_compiler()
call assert_equal('perl', b:current_compiler)
call assert_fails('let g:current_compiler', 'E121:')
+ let verbose_efm = execute('verbose set efm')
+ call assert_match('Last set from .*[/\\]compiler[/\\]perl.vim ', verbose_efm)
+
call setline(1, ['#!/usr/bin/perl -w', 'use strict;', 'my $foo=1'])
w!
call feedkeys(":make\<CR>\<CR>", 'tx')
diff --git a/src/nvim/testdir/test_conceal.vim b/src/nvim/testdir/test_conceal.vim
index 1306dbe5cf..bffc2f49d3 100644
--- a/src/nvim/testdir/test_conceal.vim
+++ b/src/nvim/testdir/test_conceal.vim
@@ -4,10 +4,10 @@ source check.vim
CheckFeature conceal
source screendump.vim
-" CheckScreendump
func Test_conceal_two_windows()
CheckScreendump
+
let code =<< trim [CODE]
let lines = ["one one one one one", "two |hidden| here", "three |hidden| three"]
call setline(1, lines)
@@ -111,6 +111,7 @@ endfunc
func Test_conceal_with_cursorline()
CheckScreendump
+
" Opens a help window, where 'conceal' is set, switches to the other window
" where 'cursorline' needs to be updated when the cursor moves.
let code =<< trim [CODE]
@@ -139,6 +140,7 @@ endfunc
func Test_conceal_resize_term()
CheckScreendump
+
let code =<< trim [CODE]
call setline(1, '`one` `two` `three` `four` `five`, the backticks should be concealed')
setl cocu=n cole=3
diff --git a/src/nvim/testdir/test_cscope.vim b/src/nvim/testdir/test_cscope.vim
index cc6154af69..faf37485cd 100644
--- a/src/nvim/testdir/test_cscope.vim
+++ b/src/nvim/testdir/test_cscope.vim
@@ -102,7 +102,7 @@ func Test_cscopeWithCscopeConnections()
for cmd in ['cs find f Xmemfile_test.c', 'cs find 7 Xmemfile_test.c']
enew
let a = execute(cmd)
- call assert_true(a =~ '"Xmemfile_test.c" \d\+L, \d\+C')
+ call assert_true(a =~ '"Xmemfile_test.c" \d\+L, \d\+B')
call assert_equal('Xmemfile_test.c', @%)
endfor
@@ -112,7 +112,7 @@ func Test_cscopeWithCscopeConnections()
let a = execute(cmd)
let alines = split(a, '\n', 1)
call assert_equal('', alines[0])
- call assert_true(alines[1] =~ '"Xmemfile_test.c" \d\+L, \d\+C')
+ call assert_true(alines[1] =~ '"Xmemfile_test.c" \d\+L, \d\+B')
call assert_equal('(1 of 1): <<global>> #include <assert.h>', alines[2])
call assert_equal('#include <assert.h>', getline('.'))
endfor
diff --git a/src/nvim/testdir/test_cursor_func.vim b/src/nvim/testdir/test_cursor_func.vim
index e8c4a952ee..57825b4551 100644
--- a/src/nvim/testdir/test_cursor_func.vim
+++ b/src/nvim/testdir/test_cursor_func.vim
@@ -1,4 +1,4 @@
-" Tests for cursor().
+" Tests for cursor() and other functions that get/set the cursor position
func Test_wrong_arguments()
call assert_fails('call cursor(1. 3)', 'E474:')
@@ -24,6 +24,9 @@ func Test_move_cursor()
" below last line goes to last line
call cursor(9, 1)
call assert_equal([4, 1, 0, 1], getcurpos()[1:])
+ " pass string arguments
+ call cursor('3', '3')
+ call assert_equal([3, 3, 0, 3], getcurpos()[1:])
call setline(1, ["\<TAB>"])
call cursor(1, 1, 1)
@@ -72,7 +75,6 @@ func Test_curswant_with_cursorline()
endfunc
func Test_screenpos()
- throw 'skipped: TODO: '
rightbelow new
rightbelow 20vsplit
call setline(1, ["\tsome text", "long wrapping line here", "next line"])
@@ -100,9 +102,10 @@ func Test_screenpos()
bwipe!
call assert_equal({'col': 1, 'row': 1, 'endcol': 1, 'curscol': 1}, screenpos(win_getid(), 1, 1))
- nmenu WinBar.TEST :
- call assert_equal({'col': 1, 'row': 2, 'endcol': 1, 'curscol': 1}, screenpos(win_getid(), 1, 1))
- nunmenu WinBar.TEST
+ " Needs WinBar
+ " nmenu WinBar.TEST :
+ " call assert_equal({'col': 1, 'row': 2, 'endcol': 1, 'curscol': 1}, screenpos(win_getid(), 1, 1))
+ " nunmenu WinBar.TEST
endfunc
func Test_screenpos_number()
@@ -119,3 +122,247 @@ func Test_screenpos_number()
close
bwipe!
endfunc
+
+" Save the visual start character position
+func SaveVisualStartCharPos()
+ call add(g:VisualStartPos, getcharpos('v'))
+ return ''
+endfunc
+
+" Save the current cursor character position in insert mode
+func SaveInsertCurrentCharPos()
+ call add(g:InsertCurrentPos, getcharpos('.'))
+ return ''
+endfunc
+
+" Test for the getcharpos() function
+func Test_getcharpos()
+ call assert_fails('call getcharpos({})', 'E731:')
+ call assert_equal([0, 0, 0, 0], getcharpos(0))
+ new
+ call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678'])
+
+ " Test for '.' and '$'
+ normal 1G
+ call assert_equal([0, 1, 1, 0], getcharpos('.'))
+ call assert_equal([0, 4, 1, 0], getcharpos('$'))
+ normal 2G6l
+ call assert_equal([0, 2, 7, 0], getcharpos('.'))
+ normal 3G$
+ call assert_equal([0, 3, 1, 0], getcharpos('.'))
+ normal 4G$
+ call assert_equal([0, 4, 9, 0], getcharpos('.'))
+
+ " Test for a mark
+ normal 2G7lmmgg
+ call assert_equal([0, 2, 8, 0], getcharpos("'m"))
+ delmarks m
+ call assert_equal([0, 0, 0, 0], getcharpos("'m"))
+
+ " Test for the visual start column
+ vnoremap <expr> <F3> SaveVisualStartCharPos()
+ let g:VisualStartPos = []
+ exe "normal 2G6lv$\<F3>ohh\<F3>o\<F3>"
+ call assert_equal([[0, 2, 7, 0], [0, 2, 10, 0], [0, 2, 5, 0]], g:VisualStartPos)
+ call assert_equal([0, 2, 9, 0], getcharpos('v'))
+ let g:VisualStartPos = []
+ exe "normal 3Gv$\<F3>o\<F3>"
+ call assert_equal([[0, 3, 1, 0], [0, 3, 2, 0]], g:VisualStartPos)
+ let g:VisualStartPos = []
+ exe "normal 1Gv$\<F3>o\<F3>"
+ call assert_equal([[0, 1, 1, 0], [0, 1, 1, 0]], g:VisualStartPos)
+ vunmap <F3>
+
+ " Test for getting the position in insert mode with the cursor after the
+ " last character in a line
+ inoremap <expr> <F3> SaveInsertCurrentCharPos()
+ let g:InsertCurrentPos = []
+ exe "normal 1GA\<F3>"
+ exe "normal 2GA\<F3>"
+ exe "normal 3GA\<F3>"
+ exe "normal 4GA\<F3>"
+ exe "normal 2G6li\<F3>"
+ call assert_equal([[0, 1, 1, 0], [0, 2, 10, 0], [0, 3, 2, 0], [0, 4, 10, 0],
+ \ [0, 2, 7, 0]], g:InsertCurrentPos)
+ iunmap <F3>
+
+ %bw!
+endfunc
+
+" Test for the setcharpos() function
+func Test_setcharpos()
+ call assert_equal(-1, setcharpos('.', v:_null_list))
+ new
+ call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678'])
+ call setcharpos('.', [0, 1, 1, 0])
+ call assert_equal([1, 1], [line('.'), col('.')])
+ call setcharpos('.', [0, 2, 7, 0])
+ call assert_equal([2, 9], [line('.'), col('.')])
+ call setcharpos('.', [0, 3, 4, 0])
+ call assert_equal([3, 1], [line('.'), col('.')])
+ call setcharpos('.', [0, 3, 1, 0])
+ call assert_equal([3, 1], [line('.'), col('.')])
+ call setcharpos('.', [0, 4, 0, 0])
+ call assert_equal([4, 1], [line('.'), col('.')])
+ call setcharpos('.', [0, 4, 20, 0])
+ call assert_equal([4, 9], [line('.'), col('.')])
+
+ " Test for mark
+ delmarks m
+ call setcharpos("'m", [0, 2, 9, 0])
+ normal `m
+ call assert_equal([2, 11], [line('.'), col('.')])
+ " unload the buffer and try to set the mark
+ let bnr = bufnr()
+ enew!
+ call assert_equal(-1, setcharpos("'m", [bnr, 2, 2, 0]))
+
+ %bw!
+ call assert_equal(-1, setcharpos('.', [10, 3, 1, 0]))
+endfunc
+
+func SaveVisualStartCharCol()
+ call add(g:VisualStartCol, charcol('v'))
+ return ''
+endfunc
+
+func SaveInsertCurrentCharCol()
+ call add(g:InsertCurrentCol, charcol('.'))
+ return ''
+endfunc
+
+" Test for the charcol() function
+func Test_charcol()
+ call assert_fails('call charcol({})', 'E731:')
+ call assert_equal(0, charcol(0))
+ new
+ call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678'])
+
+ " Test for '.' and '$'
+ normal 1G
+ call assert_equal(1, charcol('.'))
+ call assert_equal(1, charcol('$'))
+ normal 2G6l
+ call assert_equal(7, charcol('.'))
+ call assert_equal(10, charcol('$'))
+ normal 3G$
+ call assert_equal(1, charcol('.'))
+ call assert_equal(2, charcol('$'))
+ normal 4G$
+ call assert_equal(9, charcol('.'))
+ call assert_equal(10, charcol('$'))
+
+ " Test for [lnum, '$']
+ call assert_equal(1, charcol([1, '$']))
+ call assert_equal(10, charcol([2, '$']))
+ call assert_equal(2, charcol([3, '$']))
+ call assert_equal(0, charcol([5, '$']))
+
+ " Test for a mark
+ normal 2G7lmmgg
+ call assert_equal(8, charcol("'m"))
+ delmarks m
+ call assert_equal(0, charcol("'m"))
+
+ " Test for the visual start column
+ vnoremap <expr> <F3> SaveVisualStartCharCol()
+ let g:VisualStartCol = []
+ exe "normal 2G6lv$\<F3>ohh\<F3>o\<F3>"
+ call assert_equal([7, 10, 5], g:VisualStartCol)
+ call assert_equal(9, charcol('v'))
+ let g:VisualStartCol = []
+ exe "normal 3Gv$\<F3>o\<F3>"
+ call assert_equal([1, 2], g:VisualStartCol)
+ let g:VisualStartCol = []
+ exe "normal 1Gv$\<F3>o\<F3>"
+ call assert_equal([1, 1], g:VisualStartCol)
+ vunmap <F3>
+
+ " Test for getting the column number in insert mode with the cursor after
+ " the last character in a line
+ inoremap <expr> <F3> SaveInsertCurrentCharCol()
+ let g:InsertCurrentCol = []
+ exe "normal 1GA\<F3>"
+ exe "normal 2GA\<F3>"
+ exe "normal 3GA\<F3>"
+ exe "normal 4GA\<F3>"
+ exe "normal 2G6li\<F3>"
+ call assert_equal([1, 10, 2, 10, 7], g:InsertCurrentCol)
+ iunmap <F3>
+
+ %bw!
+endfunc
+
+func SaveInsertCursorCharPos()
+ call add(g:InsertCursorPos, getcursorcharpos('.'))
+ return ''
+endfunc
+
+" Test for getcursorcharpos()
+func Test_getcursorcharpos()
+ call assert_equal(getcursorcharpos(), getcursorcharpos(0))
+ call assert_equal([0, 0, 0, 0, 0], getcursorcharpos(-1))
+ call assert_equal([0, 0, 0, 0, 0], getcursorcharpos(1999))
+
+ new
+ call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678'])
+ normal 1G9l
+ call assert_equal([0, 1, 1, 0, 1], getcursorcharpos())
+ normal 2G9l
+ call assert_equal([0, 2, 9, 0, 14], getcursorcharpos())
+ normal 3G9l
+ call assert_equal([0, 3, 1, 0, 1], getcursorcharpos())
+ normal 4G9l
+ call assert_equal([0, 4, 9, 0, 9], getcursorcharpos())
+
+ " Test for getting the cursor position in insert mode with the cursor after
+ " the last character in a line
+ inoremap <expr> <F3> SaveInsertCursorCharPos()
+ let g:InsertCursorPos = []
+ exe "normal 1GA\<F3>"
+ exe "normal 2GA\<F3>"
+ exe "normal 3GA\<F3>"
+ exe "normal 4GA\<F3>"
+ exe "normal 2G6li\<F3>"
+ call assert_equal([[0, 1, 1, 0, 1], [0, 2, 10, 0, 15], [0, 3, 2, 0, 2],
+ \ [0, 4, 10, 0, 10], [0, 2, 7, 0, 12]], g:InsertCursorPos)
+ iunmap <F3>
+
+ let winid = win_getid()
+ normal 2G5l
+ wincmd w
+ call assert_equal([0, 2, 6, 0, 11], getcursorcharpos(winid))
+ %bw!
+endfunc
+
+" Test for setcursorcharpos()
+func Test_setcursorcharpos()
+ call assert_fails('call setcursorcharpos(v:_null_list)', 'E474:')
+ call assert_fails('call setcursorcharpos([1])', 'E474:')
+ call assert_fails('call setcursorcharpos([1, 1, 1, 1, 1])', 'E474:')
+ new
+ call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678'])
+ normal G
+ call setcursorcharpos([1, 1])
+ call assert_equal([1, 1], [line('.'), col('.')])
+ call setcursorcharpos([2, 7, 0])
+ call assert_equal([2, 9], [line('.'), col('.')])
+ call setcursorcharpos(3, 4)
+ call assert_equal([3, 1], [line('.'), col('.')])
+ call setcursorcharpos([3, 1])
+ call assert_equal([3, 1], [line('.'), col('.')])
+ call setcursorcharpos([4, 0, 0, 0])
+ call assert_equal([4, 1], [line('.'), col('.')])
+ call setcursorcharpos([4, 20])
+ call assert_equal([4, 9], [line('.'), col('.')])
+ normal 1G
+ call setcursorcharpos([100, 100, 100, 100])
+ call assert_equal([4, 9], [line('.'), col('.')])
+ normal 1G
+ call setcursorcharpos('$', 1)
+ call assert_equal([4, 1], [line('.'), col('.')])
+
+ %bw!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim
index 3a0c615cf6..482d39056f 100644
--- a/src/nvim/testdir/test_diffmode.vim
+++ b/src/nvim/testdir/test_diffmode.vim
@@ -1146,6 +1146,35 @@ func Test_diff_followwrap()
bwipe!
endfunc
+func Test_diff_maintains_change_mark()
+ func DiffMaintainsChangeMark()
+ enew!
+ call setline(1, ['a', 'b', 'c', 'd'])
+ diffthis
+ new
+ call setline(1, ['a', 'b', 'c', 'e'])
+ " Set '[ and '] marks
+ 2,3yank
+ call assert_equal([2, 3], [line("'["), line("']")])
+ " Verify they aren't affected by the implicit diff
+ diffthis
+ call assert_equal([2, 3], [line("'["), line("']")])
+ " Verify they aren't affected by an explicit diff
+ diffupdate
+ call assert_equal([2, 3], [line("'["), line("']")])
+ bwipe!
+ bwipe!
+ endfunc
+
+ set diffopt-=internal
+ call DiffMaintainsChangeMark()
+ set diffopt+=internal
+ call DiffMaintainsChangeMark()
+
+ set diffopt&
+ delfunc DiffMaintainsChangeMark
+endfunc
+
func Test_diff_rnu()
CheckScreendump
diff --git a/src/nvim/testdir/test_digraph.vim b/src/nvim/testdir/test_digraph.vim
index d23748a3e3..5965ee48ef 100644
--- a/src/nvim/testdir/test_digraph.vim
+++ b/src/nvim/testdir/test_digraph.vim
@@ -81,7 +81,7 @@ func Test_digraphs()
call Put_Dig(".e")
call Put_Dig("a.") " not defined
call assert_equal(['ḃ', 'ė', '.'], getline(line('.')-2,line('.')))
- " Diaresis
+ " Diaeresis
call Put_Dig("a:")
call Put_Dig(":u")
call Put_Dig("b:") " not defined
@@ -288,7 +288,7 @@ func Test_digraphs_option()
call Put_Dig_BS(".","e")
call Put_Dig_BS("a",".") " not defined
call assert_equal(['ḃ', 'ė', '.'], getline(line('.')-2,line('.')))
- " Diaresis
+ " Diaeresis
call Put_Dig_BS("a",":")
call Put_Dig_BS(":","u")
call Put_Dig_BS("b",":") " not defined
diff --git a/src/nvim/testdir/test_display.vim b/src/nvim/testdir/test_display.vim
index c2a9683f7c..9f74d0a38a 100644
--- a/src/nvim/testdir/test_display.vim
+++ b/src/nvim/testdir/test_display.vim
@@ -41,7 +41,7 @@ func Test_display_foldcolumn()
quit!
endfunc
-func! Test_display_foldtext_mbyte()
+func Test_display_foldtext_mbyte()
CheckFeature folding
call NewWindow(10, 40)
diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim
index fc4e80f0d6..360b3aaaa0 100644
--- a/src/nvim/testdir/test_edit.vim
+++ b/src/nvim/testdir/test_edit.vim
@@ -213,7 +213,7 @@ func Test_edit_07()
bw!
endfunc
-func! Test_edit_08()
+func Test_edit_08()
throw 'skipped: moved to test/functional/legacy/edit_spec.lua'
" reset insertmode from i_ctrl-r_=
let g:bufnr = bufnr('%')
@@ -417,7 +417,7 @@ func Test_edit_13()
bwipe!
endfunc
-func! Test_edit_CR()
+func Test_edit_CR()
" Test for <CR> in insert mode
" basically only in quickfix mode ist tested, the rest
" has been taken care of by other tests
@@ -450,7 +450,7 @@ func! Test_edit_CR()
call delete('Xqflist.txt')
endfunc
-func! Test_edit_CTRL_()
+func Test_edit_CTRL_()
" disabled for Windows builds, why?
if !has("rightleft") || has("win32")
return
@@ -590,7 +590,7 @@ func Test_edit_CTRL_K()
call feedkeys("A\<c-x>\<c-k>\<down>\<down>\<down>\<down>\<cr>\<esc>", 'tnix')
call assert_equal(['AA'], getline(1, '$'))
- " press an unexecpted key after dictionary completion
+ " press an unexpected key after dictionary completion
%d
call setline(1, 'A')
call cursor(1, 1)
@@ -734,7 +734,7 @@ func Test_edit_CTRL_O()
bw!
endfunc
-func! Test_edit_CTRL_R()
+func Test_edit_CTRL_R()
" Insert Register
new
" call test_override("ALL", 1)
@@ -1006,16 +1006,14 @@ func Test_edit_DROP()
endfunc
func Test_edit_CTRL_V()
- if has("ebcdic")
- return
- endif
new
call setline(1, ['abc'])
call cursor(2, 1)
+
" force some redraws
set showmode showcmd
- "call test_override_char_avail(1)
- " call test_override('ALL', 1)
+ " call test_override('char_avail', 1)
+
call feedkeys("A\<c-v>\<c-n>\<c-v>\<c-l>\<c-v>\<c-b>\<esc>", 'tnix')
call assert_equal(["abc\x0e\x0c\x02"], getline(1, '$'))
@@ -1028,8 +1026,19 @@ func Test_edit_CTRL_V()
set norl
endif
- " call test_override('ALL', 0)
set noshowmode showcmd
+ " call test_override('char_avail', 0)
+
+ " No modifiers should be applied to the char typed using i_CTRL-V_digit.
+ call feedkeys(":append\<CR>\<C-V>76c\<C-V>76\<C-F2>\<C-V>u3c0j\<C-V>u3c0\<M-F3>\<CR>.\<CR>", 'tnix')
+ call assert_equal('LcL<C-F2>πjπ<M-F3>', getline(2))
+
+ if has('osx')
+ " A char with a modifier should not be a valid char for i_CTRL-V_digit.
+ call feedkeys("o\<C-V>\<D-j>\<C-V>\<D-1>\<C-V>\<D-o>\<C-V>\<D-x>\<C-V>\<D-u>", 'tnix')
+ call assert_equal('<D-j><D-1><D-o><D-x><D-u>', getline(3))
+ endif
+
bw!
endfunc
@@ -1550,11 +1559,7 @@ endfunc
func Test_edit_special_chars()
new
- if has("ebcdic")
- let t = "o\<C-V>193\<C-V>xc2\<C-V>o303 \<C-V>90a\<C-V>xfg\<C-V>o578\<Esc>"
- else
- let t = "o\<C-V>65\<C-V>x42\<C-V>o103 \<C-V>33a\<C-V>xfg\<C-V>o78\<Esc>"
- endif
+ let t = "o\<C-V>65\<C-V>x42\<C-V>o103 \<C-V>33a\<C-V>xfg\<C-V>o78\<Esc>"
exe "normal " . t
call assert_equal("ABC !a\<C-O>g\<C-G>8", getline(2))
diff --git a/src/nvim/testdir/test_eval_stuff.vim b/src/nvim/testdir/test_eval_stuff.vim
index 883ba5de3d..95eccde35c 100644
--- a/src/nvim/testdir/test_eval_stuff.vim
+++ b/src/nvim/testdir/test_eval_stuff.vim
@@ -65,11 +65,9 @@ func Test_E963()
endfunc
func Test_for_invalid()
- " Vim gives incorrect emsg here until v8.2.3284, but the exact emsg from that
- " patch cannot be used until v8.2.2658 is ported (for loop over Strings)
- call assert_fails("for x in 99", 'E897:')
- call assert_fails("for x in function('winnr')", 'E897:')
- call assert_fails("for x in {'a': 9}", 'E897:')
+ call assert_fails("for x in 99", 'E1098:')
+ call assert_fails("for x in function('winnr')", 'E1098:')
+ call assert_fails("for x in {'a': 9}", 'E1098:')
if 0
/1/5/2/s/\n
diff --git a/src/nvim/testdir/test_ex_mode.vim b/src/nvim/testdir/test_ex_mode.vim
index 92e0559618..dcec5f7cc6 100644
--- a/src/nvim/testdir/test_ex_mode.vim
+++ b/src/nvim/testdir/test_ex_mode.vim
@@ -29,12 +29,11 @@ endfunc
" Test editing line in Ex mode (both Q and gQ)
func Test_ex_mode()
- throw 'skipped: TODO: '
+ throw 'Skipped: Nvim only supports Vim Ex mode'
let encoding_save = &encoding
set sw=2
- " for e in ['utf8', 'latin1']
- for e in ['utf8']
+ for e in ['utf8', 'latin1']
exe 'set encoding=' . e
call assert_equal(['bar', 'bar'], Ex("foo bar\<C-u>bar"), e)
@@ -98,4 +97,14 @@ func Test_ex_mode_count_overflow()
call delete('Xexmodescript')
endfunc
+func Test_ex_mode_large_indent()
+ new
+ set ts=500 ai
+ call setline(1, "\t")
+ exe "normal gQi\<CR>."
+ set ts=8 noai
+ bwipe!
+endfunc
+
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_excmd.vim b/src/nvim/testdir/test_excmd.vim
index 2d01cbba83..8055a51a11 100644
--- a/src/nvim/testdir/test_excmd.vim
+++ b/src/nvim/testdir/test_excmd.vim
@@ -1,6 +1,8 @@
" Tests for various Ex commands.
source check.vim
+source shared.vim
+source term_util.vim
func Test_ex_delete()
new
@@ -122,6 +124,27 @@ func Test_append_cmd()
close!
endfunc
+func Test_append_cmd_empty_buf()
+ CheckRunVimInTerminal
+ let lines =<< trim END
+ func Timer(timer)
+ append
+ aaaaa
+ bbbbb
+ .
+ endfunc
+ call timer_start(10, 'Timer')
+ END
+ call writefile(lines, 'Xtest_append_cmd_empty_buf')
+ let buf = RunVimInTerminal('-S Xtest_append_cmd_empty_buf', {'rows': 6})
+ call WaitForAssert({-> assert_equal('bbbbb', term_getline(buf, 2))})
+ call WaitForAssert({-> assert_equal('aaaaa', term_getline(buf, 1))})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('Xtest_append_cmd_empty_buf')
+endfunc
+
" Test for the :insert command
func Test_insert_cmd()
set noautoindent " test assumes noautoindent, but it's on by default in Nvim
@@ -151,6 +174,27 @@ func Test_insert_cmd()
close!
endfunc
+func Test_insert_cmd_empty_buf()
+ CheckRunVimInTerminal
+ let lines =<< trim END
+ func Timer(timer)
+ insert
+ aaaaa
+ bbbbb
+ .
+ endfunc
+ call timer_start(10, 'Timer')
+ END
+ call writefile(lines, 'Xtest_insert_cmd_empty_buf')
+ let buf = RunVimInTerminal('-S Xtest_insert_cmd_empty_buf', {'rows': 6})
+ call WaitForAssert({-> assert_equal('bbbbb', term_getline(buf, 2))})
+ call WaitForAssert({-> assert_equal('aaaaa', term_getline(buf, 1))})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('Xtest_insert_cmd_empty_buf')
+endfunc
+
" Test for the :change command
func Test_change_cmd()
set noautoindent " test assumes noautoindent, but it's on by default in Nvim
@@ -356,3 +400,27 @@ func Test_winsize_cmd()
call assert_fails('win_getid(1)', 'E475: Invalid argument: _getid(1)')
" Actually changing the window size would be flaky.
endfunc
+
+func Test_not_break_expression_register()
+ call setreg('=', '1+1')
+ if 0
+ put =1
+ endif
+ call assert_equal('1+1', getreg('=', 1))
+endfunc
+
+func Test_address_line_overflow()
+ throw 'Skipped: v:sizeoflong is N/A' " use legacy/excmd_spec.lua instead
+
+ if v:sizeoflong < 8
+ throw 'Skipped: only works with 64 bit long ints'
+ endif
+ new
+ call setline(1, 'text')
+ call assert_fails('|.44444444444444444444444', 'E1247:')
+ call assert_fails('|.9223372036854775806', 'E1247:')
+ bwipe!
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_exec_while_if.vim b/src/nvim/testdir/test_exec_while_if.vim
index 3da2784d77..3f13b09945 100644
--- a/src/nvim/testdir/test_exec_while_if.vim
+++ b/src/nvim/testdir/test_exec_while_if.vim
@@ -6,11 +6,7 @@ func Test_exec_while_if()
let i = 0
while i < 12
let i = i + 1
- if has("ebcdic")
- execute "normal o" . i . "\047"
- else
- execute "normal o" . i . "\033"
- endif
+ execute "normal o" . i . "\033"
if i % 2
normal Ax
if i == 9
@@ -21,21 +17,13 @@ func Test_exec_while_if()
else
let j = 9
while j > 0
- if has("ebcdic")
- execute "normal" j . "a" . j . "\x27"
- else
- execute "normal" j . "a" . j . "\x1b"
- endif
+ execute "normal" j . "a" . j . "\x1b"
let j = j - 1
endwhile
endif
endif
if i == 9
- if has("ebcdic")
- execute "normal Az\047"
- else
- execute "normal Az\033"
- endif
+ execute "normal Az\033"
endif
endwhile
unlet i j
diff --git a/src/nvim/testdir/test_execute_func.vim b/src/nvim/testdir/test_execute_func.vim
index 2cb6d73407..16cc20e9a7 100644
--- a/src/nvim/testdir/test_execute_func.vim
+++ b/src/nvim/testdir/test_execute_func.vim
@@ -147,3 +147,30 @@ func Test_win_execute_other_tab()
tabclose
unlet xyz
endfunc
+
+func Test_win_execute_visual_redraw()
+ call setline(1, ['a', 'b', 'c'])
+ new
+ wincmd p
+ " start Visual in current window, redraw in other window with fewer lines
+ call feedkeys("G\<C-V>", 'txn')
+ call win_execute(winnr('#')->win_getid(), 'redraw')
+ call feedkeys("\<Esc>", 'txn')
+ bwipe!
+ bwipe!
+
+ enew
+ new
+ call setline(1, ['a', 'b', 'c'])
+ let winid = win_getid()
+ wincmd p
+ " start Visual in current window, extend it in other window with more lines
+ call feedkeys("\<C-V>", 'txn')
+ call win_execute(winid, 'call feedkeys("G\<C-V>", ''txn'')')
+ redraw
+
+ bwipe!
+ bwipe!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_expand.vim b/src/nvim/testdir/test_expand.vim
new file mode 100644
index 0000000000..48dce25bb3
--- /dev/null
+++ b/src/nvim/testdir/test_expand.vim
@@ -0,0 +1,83 @@
+" Test for expanding file names
+
+func Test_with_directories()
+ call mkdir('Xdir1')
+ call mkdir('Xdir2')
+ call mkdir('Xdir3')
+ cd Xdir3
+ call mkdir('Xdir4')
+ cd ..
+
+ split Xdir1/file
+ call setline(1, ['a', 'b'])
+ w
+ w Xdir3/Xdir4/file
+ close
+
+ next Xdir?/*/file
+ call assert_equal('Xdir3/Xdir4/file', expand('%'))
+ if has('unix')
+ next! Xdir?/*/nofile
+ call assert_equal('Xdir?/*/nofile', expand('%'))
+ endif
+ " Edit another file, on MS-Windows the swap file would be in use and can't
+ " be deleted.
+ edit foo
+
+ call assert_equal(0, delete('Xdir1', 'rf'))
+ call assert_equal(0, delete('Xdir2', 'rf'))
+ call assert_equal(0, delete('Xdir3', 'rf'))
+endfunc
+
+func Test_with_tilde()
+ let dir = getcwd()
+ call mkdir('Xdir ~ dir')
+ call assert_true(isdirectory('Xdir ~ dir'))
+ cd Xdir\ ~\ dir
+ call assert_true(getcwd() =~ 'Xdir \~ dir')
+ call chdir(dir)
+ call delete('Xdir ~ dir', 'd')
+ call assert_false(isdirectory('Xdir ~ dir'))
+endfunc
+
+func Test_expand_tilde_filename()
+ split ~
+ call assert_equal('~', expand('%'))
+ call assert_notequal(expand('%:p'), expand('~/'))
+ call assert_match('\~', expand('%:p'))
+ bwipe!
+endfunc
+
+func Test_expandcmd()
+ let $FOO = 'Test'
+ call assert_equal('e x/Test/y', expandcmd('e x/$FOO/y'))
+ unlet $FOO
+
+ new
+ edit Xfile1
+ call assert_equal('e Xfile1', expandcmd('e %'))
+ edit Xfile2
+ edit Xfile1
+ call assert_equal('e Xfile2', 'e #'->expandcmd())
+ edit Xfile2
+ edit Xfile3
+ edit Xfile4
+ let bnum = bufnr('Xfile2')
+ call assert_equal('e Xfile2', expandcmd('e #' . bnum))
+ call setline('.', 'Vim!@#')
+ call assert_equal('e Vim', expandcmd('e <cword>'))
+ call assert_equal('e Vim!@#', expandcmd('e <cWORD>'))
+ enew!
+ edit Xfile.java
+ call assert_equal('e Xfile.py', expandcmd('e %:r.py'))
+ call assert_equal('make abc.java', expandcmd('make abc.%:e'))
+ call assert_equal('make Xabc.java', expandcmd('make %:s?file?abc?'))
+ edit a1a2a3.rb
+ call assert_equal('make b1b2b3.rb a1a2a3 Xfile.o', expandcmd('make %:gs?a?b? %< #<.o'))
+
+ call assert_fails('call expandcmd("make <afile>")', 'E495:')
+ call assert_fails('call expandcmd("make <afile>")', 'E495:')
+ enew
+ call assert_fails('call expandcmd("make %")', 'E499:')
+ close
+endfunc
diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim
index 1d7fd3e385..5b10e691e5 100644
--- a/src/nvim/testdir/test_expr.vim
+++ b/src/nvim/testdir/test_expr.vim
@@ -283,6 +283,71 @@ function Test_printf_misc()
call assert_equal('🐍', printf('%.2S', '🐍🐍'))
call assert_equal('', printf('%.1S', '🐍🐍'))
+ call assert_equal('[ あいう]', printf('[%10.6S]', 'あいうえお'))
+ call assert_equal('[ あいうえ]', printf('[%10.8S]', 'あいうえお'))
+ call assert_equal('[あいうえお]', printf('[%10.10S]', 'あいうえお'))
+ call assert_equal('[あいうえお]', printf('[%10.12S]', 'あいうえお'))
+
+ call assert_equal('あいう', printf('%S', 'あいう'))
+ call assert_equal('あいう', printf('%#S', 'あいう'))
+
+ call assert_equal('あb', printf('%2S', 'あb'))
+ call assert_equal('あb', printf('%.4S', 'あb'))
+ call assert_equal('あ', printf('%.2S', 'あb'))
+ call assert_equal(' あb', printf('%4S', 'あb'))
+ call assert_equal('0あb', printf('%04S', 'あb'))
+ call assert_equal('あb ', printf('%-4S', 'あb'))
+ call assert_equal('あ ', printf('%-4.2S', 'あb'))
+
+ call assert_equal('aい', printf('%2S', 'aい'))
+ call assert_equal('aい', printf('%.4S', 'aい'))
+ call assert_equal('a', printf('%.2S', 'aい'))
+ call assert_equal(' aい', printf('%4S', 'aい'))
+ call assert_equal('0aい', printf('%04S', 'aい'))
+ call assert_equal('aい ', printf('%-4S', 'aい'))
+ call assert_equal('a ', printf('%-4.2S', 'aい'))
+
+ call assert_equal('[あいう]', printf('[%05S]', 'あいう'))
+ call assert_equal('[あいう]', printf('[%06S]', 'あいう'))
+ call assert_equal('[0あいう]', printf('[%07S]', 'あいう'))
+
+ call assert_equal('[あiう]', printf('[%05S]', 'あiう'))
+ call assert_equal('[0あiう]', printf('[%06S]', 'あiう'))
+ call assert_equal('[00あiう]', printf('[%07S]', 'あiう'))
+
+ call assert_equal('[0あい]', printf('[%05.4S]', 'あいう'))
+ call assert_equal('[00あい]', printf('[%06.4S]', 'あいう'))
+ call assert_equal('[000あい]', printf('[%07.4S]', 'あいう'))
+
+ call assert_equal('[00あi]', printf('[%05.4S]', 'あiう'))
+ call assert_equal('[000あi]', printf('[%06.4S]', 'あiう'))
+ call assert_equal('[0000あi]', printf('[%07.4S]', 'あiう'))
+
+ call assert_equal('[0あい]', printf('[%05.5S]', 'あいう'))
+ call assert_equal('[00あい]', printf('[%06.5S]', 'あいう'))
+ call assert_equal('[000あい]', printf('[%07.5S]', 'あいう'))
+
+ call assert_equal('[あiう]', printf('[%05.5S]', 'あiう'))
+ call assert_equal('[0あiう]', printf('[%06.5S]', 'あiう'))
+ call assert_equal('[00あiう]', printf('[%07.5S]', 'あiう'))
+
+ call assert_equal('[0000000000]', printf('[%010.0S]', 'あいう'))
+ call assert_equal('[0000000000]', printf('[%010.1S]', 'あいう'))
+ call assert_equal('[00000000あ]', printf('[%010.2S]', 'あいう'))
+ call assert_equal('[00000000あ]', printf('[%010.3S]', 'あいう'))
+ call assert_equal('[000000あい]', printf('[%010.4S]', 'あいう'))
+ call assert_equal('[000000あい]', printf('[%010.5S]', 'あいう'))
+ call assert_equal('[0000あいう]', printf('[%010.6S]', 'あいう'))
+ call assert_equal('[0000あいう]', printf('[%010.7S]', 'あいう'))
+
+ call assert_equal('[0000000000]', printf('[%010.1S]', 'あiう'))
+ call assert_equal('[00000000あ]', printf('[%010.2S]', 'あiう'))
+ call assert_equal('[0000000あi]', printf('[%010.3S]', 'あiう'))
+ call assert_equal('[0000000あi]', printf('[%010.4S]', 'あiう'))
+ call assert_equal('[00000あiう]', printf('[%010.5S]', 'あiう'))
+ call assert_equal('[00000あiう]', printf('[%010.6S]', 'あiう'))
+ call assert_equal('[00000あiう]', printf('[%010.7S]', 'あiう'))
+
call assert_equal('1%', printf('%d%%', 1))
endfunc
diff --git a/src/nvim/testdir/test_feedkeys.vim b/src/nvim/testdir/test_feedkeys.vim
index 70500f2bb5..f343b0174c 100644
--- a/src/nvim/testdir/test_feedkeys.vim
+++ b/src/nvim/testdir/test_feedkeys.vim
@@ -12,3 +12,15 @@ func Test_feedkeys_x_with_empty_string()
call assert_equal('foo', getline('.'))
quit!
endfunc
+
+func Test_feedkeys_with_abbreviation()
+ new
+ inoreabbrev trigger value
+ call feedkeys("atrigger ", 'x')
+ call feedkeys("atrigger ", 'x')
+ call assert_equal('value value ', getline(1))
+ bwipe!
+ iunabbrev trigger
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_filechanged.vim b/src/nvim/testdir/test_filechanged.vim
index b95cd5faf8..06ccd6e85f 100644
--- a/src/nvim/testdir/test_filechanged.vim
+++ b/src/nvim/testdir/test_filechanged.vim
@@ -1,9 +1,10 @@
" Tests for when a file was changed outside of Vim.
+source check.vim
+
func Test_FileChangedShell_reload()
- if !has('unix')
- return
- endif
+ CheckUnix
+
augroup testreload
au FileChangedShell Xchanged_r let g:reason = v:fcs_reason | let v:fcs_choice = 'reload'
augroup END
@@ -90,11 +91,107 @@ func Test_FileChangedShell_reload()
call delete('Xchanged_r')
endfunc
+func Test_FileChangedShell_edit()
+ CheckUnix
+
+ new Xchanged_r
+ call setline(1, 'reload this')
+ set fileformat=unix
+ write
+
+ " File format changed, reload (content only, no 'ff' etc)
+ augroup testreload
+ au!
+ au FileChangedShell Xchanged_r let g:reason = v:fcs_reason | let v:fcs_choice = 'reload'
+ augroup END
+ call assert_equal(&fileformat, 'unix')
+ sleep 10m " make the test less flaky in Nvim
+ call writefile(["line1\r", "line2\r"], 'Xchanged_r')
+ let g:reason = ''
+ checktime
+ call assert_equal('changed', g:reason)
+ call assert_equal(&fileformat, 'unix')
+ call assert_equal("line1\r", getline(1))
+ call assert_equal("line2\r", getline(2))
+ %s/\r
+ write
+
+ " File format changed, reload with 'ff', etc
+ augroup testreload
+ au!
+ au FileChangedShell Xchanged_r let g:reason = v:fcs_reason | let v:fcs_choice = 'edit'
+ augroup END
+ call assert_equal(&fileformat, 'unix')
+ sleep 10m " make the test less flaky in Nvim
+ call writefile(["line1\r", "line2\r"], 'Xchanged_r')
+ let g:reason = ''
+ checktime
+ call assert_equal('changed', g:reason)
+ call assert_equal(&fileformat, 'dos')
+ call assert_equal('line1', getline(1))
+ call assert_equal('line2', getline(2))
+ set fileformat=unix
+ write
+
+ au! testreload
+ bwipe!
+ call delete(undofile('Xchanged_r'))
+ call delete('Xchanged_r')
+endfunc
+
+func Test_FileChangedShell_edit_dialog()
+ throw 'Skipped: requires a UI to be active'
+ CheckNotGui
+
+ new Xchanged_r
+ call setline(1, 'reload this')
+ set fileformat=unix
+ write
+
+ " File format changed, reload (content only) via prompt
+ augroup testreload
+ au!
+ au FileChangedShell Xchanged_r let g:reason = v:fcs_reason | let v:fcs_choice = 'ask'
+ augroup END
+ call assert_equal(&fileformat, 'unix')
+ call writefile(["line1\r", "line2\r"], 'Xchanged_r')
+ let g:reason = ''
+ call feedkeys('L', 'L') " load file content only
+ checktime
+ call assert_equal('changed', g:reason)
+ call assert_equal(&fileformat, 'unix')
+ call assert_equal("line1\r", getline(1))
+ call assert_equal("line2\r", getline(2))
+ %s/\r
+ write
+
+ " File format changed, reload (file and options) via prompt
+ augroup testreload
+ au!
+ au FileChangedShell Xchanged_r let g:reason = v:fcs_reason | let v:fcs_choice = 'ask'
+ augroup END
+ call assert_equal(&fileformat, 'unix')
+ call writefile(["line1\r", "line2\r"], 'Xchanged_r')
+ let g:reason = ''
+ call feedkeys('a', 'L') " load file content and options
+ checktime
+ call assert_equal('changed', g:reason)
+ call assert_equal(&fileformat, 'dos')
+ call assert_equal("line1", getline(1))
+ call assert_equal("line2", getline(2))
+ set fileformat=unix
+ write
+
+ au! testreload
+ bwipe!
+ call delete(undofile('Xchanged_r'))
+ call delete('Xchanged_r')
+endfunc
+
func Test_file_changed_dialog()
- throw 'skipped: TODO: '
- if !has('unix') || has('gui_running')
- return
- endif
+ throw 'Skipped: requires a UI to be active'
+ CheckUnix
+ CheckNotGui
au! FileChangedShell
new Xchanged_d
diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim
index 1ee23bb646..5f4a7dac6e 100644
--- a/src/nvim/testdir/test_filetype.vim
+++ b/src/nvim/testdir/test_filetype.vim
@@ -76,10 +76,12 @@ let s:filename_checks = {
\ 'ave': ['file.ave'],
\ 'awk': ['file.awk', 'file.gawk'],
\ 'b': ['file.mch', 'file.ref', 'file.imp'],
+ \ 'basic': ['file.bas', 'file.bi', 'file.bm'],
\ 'bzl': ['file.bazel', 'file.bzl', 'WORKSPACE'],
\ 'bc': ['file.bc'],
\ 'bdf': ['file.bdf'],
\ 'bib': ['file.bib'],
+ \ 'bicep': ['file.bicep'],
\ 'beancount': ['file.beancount'],
\ 'bindzone': ['named.root', '/bind/db.file', '/named/db.file', 'any/bind/db.file', 'any/named/db.file'],
\ 'blank': ['file.bl'],
@@ -130,6 +132,7 @@ let s:filename_checks = {
\ 'cvs': ['cvs123'],
\ 'cvsrc': ['.cvsrc'],
\ 'cynpp': ['file.cyn'],
+ \ 'd': ['file.d'],
\ 'dart': ['file.dart', 'file.drt'],
\ 'datascript': ['file.ds'],
\ 'dcd': ['file.dcd'],
@@ -146,12 +149,13 @@ let s:filename_checks = {
\ 'diff': ['file.diff', 'file.rej'],
\ 'dircolors': ['.dir_colors', '.dircolors', '/etc/DIR_COLORS', 'any/etc/DIR_COLORS'],
\ 'dnsmasq': ['/etc/dnsmasq.conf', '/etc/dnsmasq.d/file', 'any/etc/dnsmasq.conf', 'any/etc/dnsmasq.d/file'],
- \ 'dockerfile': ['Containerfile', 'Dockerfile', 'file.Dockerfile'],
+ \ 'dockerfile': ['Containerfile', 'Dockerfile', 'file.Dockerfile', 'Dockerfile.debian', 'Containerfile.something'],
\ 'dosbatch': ['file.bat', 'file.sys'],
\ 'dosini': ['.editorconfig', '/etc/pacman.conf', '/etc/yum.conf', 'file.ini', 'npmrc', '.npmrc', 'php.ini', 'php.ini-5', 'php.ini-file', '/etc/yum.repos.d/file', 'any/etc/pacman.conf', 'any/etc/yum.conf', 'any/etc/yum.repos.d/file', 'file.wrap'],
\ 'dot': ['file.dot', 'file.gv'],
\ 'dracula': ['file.drac', 'file.drc', 'filelvs', 'filelpe', 'drac.file', 'lpe', 'lvs', 'some-lpe', 'some-lvs'],
\ 'dtd': ['file.dtd'],
+ \ 'dtrace': ['/usr/lib/dtrace/io.d'],
\ 'dts': ['file.dts', 'file.dtsi'],
\ 'dune': ['jbuild', 'dune', 'dune-project', 'dune-workspace'],
\ 'dylan': ['file.dylan'],
@@ -182,47 +186,59 @@ let s:filename_checks = {
\ 'fgl': ['file.4gl', 'file.4gh', 'file.m4gl'],
\ 'fish': ['file.fish'],
\ 'focexec': ['file.fex', 'file.focexec'],
+ \ 'form': ['file.frm'],
\ 'forth': ['file.ft', 'file.fth'],
\ 'fortran': ['file.f', 'file.for', 'file.fortran', 'file.fpp', 'file.ftn', 'file.f77', 'file.f90', 'file.f95', 'file.f03', 'file.f08'],
\ 'fpcmake': ['file.fpc'],
\ 'framescript': ['file.fsl'],
- \ 'freebasic': ['file.fb', 'file.bi'],
+ \ 'freebasic': ['file.fb'],
\ 'fsharp': ['file.fs', 'file.fsi', 'file.fsx'],
\ 'fstab': ['fstab', 'mtab'],
+ \ 'fusion': ['file.fusion'],
\ 'fvwm': ['/.fvwm/file', 'any/.fvwm/file'],
\ 'gdb': ['.gdbinit', 'gdbinit'],
+ \ 'gdresource': ['file.tscn', 'file.tres'],
+ \ 'gdscript': ['file.gd'],
\ 'gdmo': ['file.mo', 'file.gdmo'],
\ 'gedcom': ['file.ged', 'lltxxxxx.txt', '/tmp/lltmp', '/tmp/lltmp-file', 'any/tmp/lltmp', 'any/tmp/lltmp-file'],
\ 'gemtext': ['file.gmi', 'file.gemini'],
\ 'gift': ['file.gift'],
- \ 'gitcommit': ['COMMIT_EDITMSG', 'MERGE_MSG', 'TAG_EDITMSG'],
- \ 'gitconfig': ['file.git/config', '.gitconfig', '.gitmodules', 'file.git/modules//config', '/.config/git/config', '/etc/gitconfig', '/etc/gitconfig.d/file', '/.gitconfig.d/file', 'any/.config/git/config', 'any/.gitconfig.d/file', 'some.git/config', 'some.git/modules/any/config'],
+ \ 'gitcommit': ['COMMIT_EDITMSG', 'MERGE_MSG', 'TAG_EDITMSG', 'NOTES_EDITMSG', 'EDIT_DESCRIPTION'],
+ \ 'gitconfig': ['file.git/config', 'file.git/config.worktree', 'file.git/worktrees/x/config.worktree', '.gitconfig', '.gitmodules', 'file.git/modules//config', '/.config/git/config', '/etc/gitconfig', '/usr/local/etc/gitconfig', '/etc/gitconfig.d/file', '/.gitconfig.d/file', 'any/.config/git/config', 'any/.gitconfig.d/file', 'some.git/config', 'some.git/modules/any/config'],
\ 'gitolite': ['gitolite.conf', '/gitolite-admin/conf/file', 'any/gitolite-admin/conf/file'],
\ 'gitrebase': ['git-rebase-todo'],
\ 'gitsendemail': ['.gitsendemail.msg.xxxxxx'],
\ 'gkrellmrc': ['gkrellmrc', 'gkrellmrc_x'],
+ \ 'glsl': ['file.glsl'],
\ 'gnash': ['gnashrc', '.gnashrc', 'gnashpluginrc', '.gnashpluginrc'],
- \ 'gnuplot': ['file.gpi'],
+ \ 'gnuplot': ['file.gpi', '.gnuplot'],
\ 'go': ['file.go'],
\ 'gomod': ['go.mod'],
+ \ 'gowork': ['go.work'],
\ 'gp': ['file.gp', '.gprc'],
\ 'gpg': ['/.gnupg/options', '/.gnupg/gpg.conf', '/usr/any/gnupg/options.skel', 'any/.gnupg/gpg.conf', 'any/.gnupg/options', 'any/usr/any/gnupg/options.skel'],
\ 'grads': ['file.gs'],
+ \ 'graphql': ['file.graphql', 'file.graphqls', 'file.gql'],
\ 'gretl': ['file.gretl'],
\ 'groovy': ['file.gradle', 'file.groovy'],
\ 'group': ['any/etc/group', 'any/etc/group-', 'any/etc/group.edit', 'any/etc/gshadow', 'any/etc/gshadow-', 'any/etc/gshadow.edit', 'any/var/backups/group.bak', 'any/var/backups/gshadow.bak', '/etc/group', '/etc/group-', '/etc/group.edit', '/etc/gshadow', '/etc/gshadow-', '/etc/gshadow.edit', '/var/backups/group.bak', '/var/backups/gshadow.bak'],
\ 'grub': ['/boot/grub/menu.lst', '/boot/grub/grub.conf', '/etc/grub.conf', 'any/boot/grub/grub.conf', 'any/boot/grub/menu.lst', 'any/etc/grub.conf'],
\ 'gsp': ['file.gsp'],
\ 'gtkrc': ['.gtkrc', 'gtkrc', '.gtkrc-file', 'gtkrc-file'],
+ \ 'hack': ['file.hack', 'file.hackpartial'],
\ 'haml': ['file.haml'],
\ 'hamster': ['file.hsm'],
+ \ 'handlebars': ['file.hbs'],
\ 'haskell': ['file.hs', 'file.hsc', 'file.hs-boot', 'file.hsig'],
\ 'haste': ['file.ht'],
\ 'hastepreproc': ['file.htpp'],
\ 'hb': ['file.hb'],
+ \ 'hcl': ['file.hcl'],
\ 'hercules': ['file.vc', 'file.ev', 'file.sum', 'file.errsum'],
+ \ 'heex': ['file.heex'],
\ 'hex': ['file.hex', 'file.h32'],
\ 'hgcommit': ['hg-editor-file.txt'],
+ \ 'hjson': ['file.hjson'],
\ 'hog': ['file.hog', 'snort.conf', 'vision.conf'],
\ 'hollywood': ['file.hws'],
\ 'hostconf': ['/etc/host.conf', 'any/etc/host.conf'],
@@ -258,12 +274,14 @@ let s:filename_checks = {
\ 'java': ['file.java', 'file.jav'],
\ 'javacc': ['file.jj', 'file.jjt'],
\ 'javascript': ['file.js', 'file.javascript', 'file.es', 'file.mjs', 'file.cjs'],
+ \ 'javascript.glimmer': ['file.gjs'],
\ 'javascriptreact': ['file.jsx'],
\ 'jess': ['file.clp'],
\ 'jgraph': ['file.jgr'],
\ 'jovial': ['file.jov', 'file.j73', 'file.jovial'],
\ 'jproperties': ['file.properties', 'file.properties_xx', 'file.properties_xx_xx', 'some.properties_xx_xx_file'],
\ 'json': ['file.json', 'file.jsonp', 'file.json-patch', 'file.webmanifest', 'Pipfile.lock', 'file.ipynb', '.babelrc', '.eslintrc', '.prettierrc', '.firebaserc', 'file.slnf'],
+ \ 'json5': ['file.json5'],
\ 'jsonc': ['file.jsonc'],
\ 'jsp': ['file.jsp'],
\ 'julia': ['file.jl'],
@@ -277,6 +295,7 @@ let s:filename_checks = {
\ 'latte': ['file.latte', 'file.lte'],
\ 'ld': ['file.ld'],
\ 'ldif': ['file.ldif'],
+ \ 'ledger': ['file.ldg', 'file.ledger', 'file.journal'],
\ 'less': ['file.less'],
\ 'lex': ['file.lex', 'file.l', 'file.lxx', 'file.l++'],
\ 'lftp': ['lftp.conf', '.lftprc', 'anylftp/rc', 'lftp/rc', 'some-lftp/rc'],
@@ -286,7 +305,7 @@ let s:filename_checks = {
\ 'lilo': ['lilo.conf', 'lilo.conf-file'],
\ 'limits': ['/etc/limits', '/etc/anylimits.conf', '/etc/anylimits.d/file.conf', '/etc/limits.conf', '/etc/limits.d/file.conf', '/etc/some-limits.conf', '/etc/some-limits.d/file.conf', 'any/etc/limits', 'any/etc/limits.conf', 'any/etc/limits.d/file.conf', 'any/etc/some-limits.conf', 'any/etc/some-limits.d/file.conf'],
\ 'liquid': ['file.liquid'],
- \ 'lisp': ['file.lsp', 'file.lisp', 'file.el', 'file.cl', '.emacs', '.sawfishrc', 'sbclrc', '.sbclrc'],
+ \ 'lisp': ['file.lsp', 'file.lisp', 'file.asd', 'file.el', 'file.cl', '.emacs', '.sawfishrc', 'sbclrc', '.sbclrc'],
\ 'lite': ['file.lite', 'file.lt'],
\ 'litestep': ['/LiteStep/any/file.rc', 'any/LiteStep/any/file.rc'],
\ 'loginaccess': ['/etc/login.access', 'any/etc/login.access'],
@@ -350,6 +369,7 @@ let s:filename_checks = {
\ 'netrc': ['.netrc'],
\ 'nginx': ['file.nginx', 'nginxfile.conf', 'filenginx.conf', 'any/etc/nginx/file', 'any/usr/local/nginx/conf/file', 'any/nginx/file.conf'],
\ 'ninja': ['file.ninja'],
+ \ 'nix': ['file.nix'],
\ 'nqc': ['file.nqc'],
\ 'nroff': ['file.tr', 'file.nr', 'file.roff', 'file.tmac', 'file.mom', 'tmac.file'],
\ 'nsis': ['file.nsi', 'file.nsh'],
@@ -392,6 +412,7 @@ let s:filename_checks = {
\ 'ppd': ['file.ppd'],
\ 'ppwiz': ['file.it', 'file.ih'],
\ 'privoxy': ['file.action'],
+ \ 'prisma': ['file.prisma'],
\ 'proc': ['file.pc'],
\ 'procmail': ['.procmail', '.procmailrc'],
\ 'prolog': ['file.pdb'],
@@ -402,10 +423,12 @@ let s:filename_checks = {
\ 'ps1xml': ['file.ps1xml'],
\ 'psf': ['file.psf'],
\ 'psl': ['file.psl'],
+ \ 'pug': ['file.pug'],
\ 'puppet': ['file.pp'],
\ 'pyret': ['file.arr'],
\ 'pyrex': ['file.pyx', 'file.pxd'],
\ 'python': ['file.py', 'file.pyw', '.pythonstartup', '.pythonrc', 'file.ptl', 'file.pyi', 'SConstruct'],
+ \ 'ql': ['file.ql', 'file.qll'],
\ 'quake': ['anybaseq2/file.cfg', 'anyid1/file.cfg', 'quake3/file.cfg', 'baseq2/file.cfg', 'id1/file.cfg', 'quake1/file.cfg', 'some-baseq2/file.cfg', 'some-id1/file.cfg', 'some-quake1/file.cfg'],
\ 'radiance': ['file.rad', 'file.mat'],
\ 'raku': ['file.pm6', 'file.p6', 'file.t6', 'file.pod6', 'file.raku', 'file.rakumod', 'file.rakudoc', 'file.rakutest'],
@@ -416,6 +439,7 @@ let s:filename_checks = {
\ 'readline': ['.inputrc', 'inputrc'],
\ 'remind': ['.reminders', 'file.remind', 'file.rem', '.reminders-file'],
\ 'rego': ['file.rego'],
+ \ 'rescript': ['file.res', 'file.resi'],
\ 'resolv': ['resolv.conf'],
\ 'reva': ['file.frt'],
\ 'rexx': ['file.rex', 'file.orx', 'file.rxo', 'file.rxj', 'file.jrexx', 'file.rexxj', 'file.rexx', 'file.testGroup', 'file.testUnit'],
@@ -436,7 +460,7 @@ let s:filename_checks = {
\ 'sather': ['file.sa'],
\ 'sbt': ['file.sbt'],
\ 'scala': ['file.scala', 'file.sc'],
- \ 'scheme': ['file.scm', 'file.ss', 'file.rkt', 'file.rktd', 'file.rktl'],
+ \ 'scheme': ['file.scm', 'file.ss', 'file.sld', 'file.rkt', 'file.rktd', 'file.rktl'],
\ 'scilab': ['file.sci', 'file.sce'],
\ 'screen': ['.screenrc', 'screenrc'],
\ 'sexplib': ['file.sexp'],
@@ -446,7 +470,7 @@ let s:filename_checks = {
\ 'sdc': ['file.sdc'],
\ 'sdl': ['file.sdl', 'file.pr'],
\ 'sed': ['file.sed'],
- \ 'sensors': ['/etc/sensors.conf', '/etc/sensors3.conf', 'any/etc/sensors.conf', 'any/etc/sensors3.conf'],
+ \ 'sensors': ['/etc/sensors.conf', '/etc/sensors3.conf', '/etc/sensors.d/file', 'any/etc/sensors.conf', 'any/etc/sensors3.conf', 'any/etc/sensors.d/file'],
\ 'services': ['/etc/services', 'any/etc/services'],
\ 'setserial': ['/etc/serial.conf', 'any/etc/serial.conf'],
\ 'sh': ['.bashrc', 'file.bash', '/usr/share/doc/bash-completion/filter.sh','/etc/udev/cdsymlinks.conf', 'any/etc/udev/cdsymlinks.conf'],
@@ -457,6 +481,7 @@ let s:filename_checks = {
\ 'skill': ['file.il', 'file.ils', 'file.cdf'],
\ 'slang': ['file.sl'],
\ 'slice': ['file.ice'],
+ \ 'solidity': ['file.sol'],
\ 'solution': ['file.sln'],
\ 'slpconf': ['/etc/slp.conf', 'any/etc/slp.conf'],
\ 'slpreg': ['/etc/slp.reg', 'any/etc/slp.reg'],
@@ -481,12 +506,13 @@ let s:filename_checks = {
\ 'squid': ['squid.conf'],
\ 'squirrel': ['file.nut'],
\ 'srec': ['file.s19', 'file.s28', 'file.s37', 'file.mot', 'file.srec'],
- \ 'sshconfig': ['ssh_config', '/.ssh/config', '/etc/ssh/ssh_config.d/file.conf', 'any/etc/ssh/ssh_config.d/file.conf', 'any/.ssh/config'],
+ \ 'sshconfig': ['ssh_config', '/.ssh/config', '/etc/ssh/ssh_config.d/file.conf', 'any/etc/ssh/ssh_config.d/file.conf', 'any/.ssh/config', 'any/.ssh/file.conf'],
\ 'sshdconfig': ['sshd_config', '/etc/ssh/sshd_config.d/file.conf', 'any/etc/ssh/sshd_config.d/file.conf'],
\ 'st': ['file.st'],
\ 'stata': ['file.ado', 'file.do', 'file.imata', 'file.mata'],
\ 'stp': ['file.stp'],
\ 'sudoers': ['any/etc/sudoers', 'sudoers.tmp', '/etc/sudoers', 'any/etc/sudoers.d/file'],
+ \ 'surface': ['file.sface'],
\ 'svg': ['file.svg'],
\ 'svn': ['svn-commitfile.tmp', 'svn-commit-file.tmp', 'svn-commit.tmp'],
\ 'swift': ['file.swift'],
@@ -500,8 +526,10 @@ let s:filename_checks = {
\ 'taskdata': ['pending.data', 'completed.data', 'undo.data'],
\ 'taskedit': ['file.task'],
\ 'tcl': ['file.tcl', 'file.tm', 'file.tk', 'file.itcl', 'file.itk', 'file.jacl', '.tclshrc', 'tclsh.rc', '.wishrc'],
+ \ 'teal': ['file.tl'],
\ 'teraterm': ['file.ttl'],
\ 'terminfo': ['file.ti'],
+ \ 'terraform': ['file.tfvars'],
\ 'tex': ['file.latex', 'file.sty', 'file.dtx', 'file.ltx', 'file.bbl'],
\ 'texinfo': ['file.texinfo', 'file.texi', 'file.txi'],
\ 'texmf': ['texmf.cnf'],
@@ -509,6 +537,7 @@ let s:filename_checks = {
\ 'tf': ['file.tf', '.tfrc', 'tfrc'],
\ 'tidy': ['.tidyrc', 'tidyrc', 'tidy.conf'],
\ 'tilde': ['file.t.html'],
+ \ 'tla': ['file.tla'],
\ 'tli': ['file.tli'],
\ 'tmux': ['tmuxfile.conf', '.tmuxfile.conf', '.tmux-file.conf', '.tmux.conf', 'tmux-file.conf', 'tmux.conf', 'tmux.conf.local'],
\ 'toml': ['file.toml', 'Gopkg.lock', 'Pipfile', '/home/user/.cargo/config'],
@@ -520,6 +549,7 @@ let s:filename_checks = {
\ 'tssgm': ['file.tssgm'],
\ 'tssop': ['file.tssop'],
\ 'twig': ['file.twig'],
+ \ 'typescript.glimmer': ['file.gts'],
\ 'typescriptreact': ['file.tsx'],
\ 'uc': ['file.uc'],
\ 'udevconf': ['/etc/udev/udev.conf', 'any/etc/udev/udev.conf'],
@@ -534,6 +564,7 @@ let s:filename_checks = {
\ 'usserverlog': ['usserver.log', 'USSERVER.LOG', 'usserver.file.log', 'USSERVER.FILE.LOG', 'file.usserver.log', 'FILE.USSERVER.LOG'],
\ 'usw2kagtlog': ['usw2kagt.log', 'USW2KAGT.LOG', 'usw2kagt.file.log', 'USW2KAGT.FILE.LOG', 'file.usw2kagt.log', 'FILE.USW2KAGT.LOG'],
\ 'vb': ['file.sba', 'file.vb', 'file.vbs', 'file.dsm', 'file.ctl'],
+ \ 'vala': ['file.vala'],
\ 'vera': ['file.vr', 'file.vri', 'file.vrh'],
\ 'verilog': ['file.v'],
\ 'verilogams': ['file.va', 'file.vams'],
@@ -569,6 +600,7 @@ let s:filename_checks = {
\ 'xslt': ['file.xsl', 'file.xslt'],
\ 'yacc': ['file.yy', 'file.yxx', 'file.y++'],
\ 'yaml': ['file.yaml', 'file.yml'],
+ \ 'yang': ['file.yang'],
\ 'raml': ['file.raml'],
\ 'z8a': ['file.z8a'],
\ 'zig': ['file.zig'],
@@ -653,7 +685,7 @@ let s:script_checks = {
\ ['#!/path/nodejs'],
\ ['#!/path/rhino']],
\ 'bc': [['#!/path/bc']],
- \ 'sed': [['#!/path/sed']],
+ \ 'sed': [['#!/path/sed'], ['#n'], ['#n comment']],
\ 'ocaml': [['#!/path/ocaml']],
\ 'awk': [['#!/path/awk'],
\ ['#!/path/gawk']],
@@ -706,83 +738,162 @@ func Test_setfiletype_completion()
call assert_equal('"setfiletype java javacc javascript javascriptreact', @:)
endfunc
-func Test_hook_file()
+"""""""""""""""""""""""""""""""""""""""""""""""""
+" Tests for specific extentions and filetypes.
+" Keep sorted.
+"""""""""""""""""""""""""""""""""""""""""""""""""
+
+func Test_bas_file()
filetype on
- call writefile(['[Trigger]', 'this is pacman config'], 'Xfile.hook')
- split Xfile.hook
- call assert_equal('dosini', &filetype)
+ call writefile(['looks like BASIC'], 'Xfile.bas')
+ split Xfile.bas
+ call assert_equal('basic', &filetype)
bwipe!
- call writefile(['not pacman'], 'Xfile.hook')
- split Xfile.hook
- call assert_notequal('dosini', &filetype)
+ " Test dist#ft#FTbas()
+
+ let g:filetype_bas = 'freebasic'
+ split Xfile.bas
+ call assert_equal('freebasic', &filetype)
bwipe!
+ unlet g:filetype_bas
- call delete('Xfile.hook')
- filetype off
-endfunc
+ " FreeBASIC
-func Test_ts_file()
- filetype on
+ call writefile(["/' FreeBASIC multiline comment '/"], 'Xfile.bas')
+ split Xfile.bas
+ call assert_equal('freebasic', &filetype)
+ bwipe!
- call writefile(['<?xml version="1.0" encoding="utf-8"?>'], 'Xfile.ts')
- split Xfile.ts
- call assert_equal('xml', &filetype)
+ call writefile(['#define TESTING'], 'Xfile.bas')
+ split Xfile.bas
+ call assert_equal('freebasic', &filetype)
bwipe!
- call writefile(['// looks like Typescript'], 'Xfile.ts')
- split Xfile.ts
- call assert_equal('typescript', &filetype)
+ call writefile(['option byval'], 'Xfile.bas')
+ split Xfile.bas
+ call assert_equal('freebasic', &filetype)
bwipe!
- call delete('Xfile.hook')
+ call writefile(['extern "C"'], 'Xfile.bas')
+ split Xfile.bas
+ call assert_equal('freebasic', &filetype)
+ bwipe!
+
+ " QB64
+
+ call writefile(['$LET TESTING = 1'], 'Xfile.bas')
+ split Xfile.bas
+ call assert_equal('qb64', &filetype)
+ bwipe!
+
+ call writefile(['OPTION _EXPLICIT'], 'Xfile.bas')
+ split Xfile.bas
+ call assert_equal('qb64', &filetype)
+ bwipe!
+
+ " Visual Basic
+
+ call writefile(['Attribute VB_NAME = "Testing"'], 'Xfile.bas')
+ split Xfile.bas
+ call assert_equal('vb', &filetype)
+ bwipe!
+
+ call delete('Xfile.bas')
filetype off
endfunc
-func Test_ttl_file()
+func Test_d_file()
filetype on
- call writefile(['@base <http://example.org/> .'], 'Xfile.ttl')
- split Xfile.ttl
- call assert_equal('turtle', &filetype)
+ call writefile(['looks like D'], 'Xfile.d')
+ split Xfile.d
+ call assert_equal('d', &filetype)
bwipe!
- call writefile(['looks like Tera Term Language'], 'Xfile.ttl')
- split Xfile.ttl
- call assert_equal('teraterm', &filetype)
+ call writefile(['#!/some/bin/dtrace'], 'Xfile.d')
+ split Xfile.d
+ call assert_equal('dtrace', &filetype)
+ bwipe!
+
+ call writefile(['#pragma D option'], 'Xfile.d')
+ split Xfile.d
+ call assert_equal('dtrace', &filetype)
+ bwipe!
+
+ call writefile([':some:thing:'], 'Xfile.d')
+ split Xfile.d
+ call assert_equal('dtrace', &filetype)
+ bwipe!
+
+ call writefile(['module this', '#pragma D option'], 'Xfile.d')
+ split Xfile.d
+ call assert_equal('d', &filetype)
+ bwipe!
+
+ call writefile(['import that', '#pragma D option'], 'Xfile.d')
+ split Xfile.d
+ call assert_equal('d', &filetype)
bwipe!
- call delete('Xfile.ttl')
filetype off
endfunc
-func Test_pp_file()
+func Test_dep3patch_file()
filetype on
- call writefile(['looks like puppet'], 'Xfile.pp')
- split Xfile.pp
- call assert_equal('puppet', &filetype)
+ call assert_true(mkdir('debian/patches', 'p'))
+
+ " series files are not patches
+ call writefile(['Description: some awesome patch'], 'debian/patches/series')
+ split debian/patches/series
+ call assert_notequal('dep3patch', &filetype)
bwipe!
- let g:filetype_pp = 'pascal'
- split Xfile.pp
- call assert_equal('pascal', &filetype)
+ " diff/patch files without the right headers should still show up as ft=diff
+ call writefile([], 'debian/patches/foo.diff')
+ split debian/patches/foo.diff
+ call assert_equal('diff', &filetype)
bwipe!
- unlet g:filetype_pp
- " Test dist#ft#FTpp()
- call writefile(['{ pascal comment'], 'Xfile.pp')
- split Xfile.pp
- call assert_equal('pascal', &filetype)
+ " Files with the right headers are detected as dep3patch, even if they don't
+ " have a diff/patch extension
+ call writefile(['Subject: dep3patches'], 'debian/patches/bar')
+ split debian/patches/bar
+ call assert_equal('dep3patch', &filetype)
bwipe!
- call writefile(['procedure pascal'], 'Xfile.pp')
- split Xfile.pp
- call assert_equal('pascal', &filetype)
+ " Files in sub-directories are detected
+ call assert_true(mkdir('debian/patches/s390x', 'p'))
+ call writefile(['Subject: dep3patches'], 'debian/patches/s390x/bar')
+ split debian/patches/s390x/bar
+ call assert_equal('dep3patch', &filetype)
bwipe!
- call delete('Xfile.pp')
+ " The detection stops when seeing the "header end" marker
+ call writefile(['---', 'Origin: the cloud'], 'debian/patches/baz')
+ split debian/patches/baz
+ call assert_notequal('dep3patch', &filetype)
+ bwipe!
+
+ call delete('debian', 'rf')
+endfunc
+
+func Test_dsl_file()
+ filetype on
+
+ call writefile([' <!doctype dsssl-spec ['], 'dslfile.dsl')
+ split dslfile.dsl
+ call assert_equal('dsl', &filetype)
+ bwipe!
+
+ call writefile(['workspace {'], 'dslfile.dsl')
+ split dslfile.dsl
+ call assert_equal('structurizr', &filetype)
+ bwipe!
+
+ call delete('dslfile.dsl')
filetype off
endfunc
@@ -823,20 +934,183 @@ func Test_ex_file()
filetype off
endfunc
-func Test_dsl_file()
+func Test_foam_file()
filetype on
+ call assert_true(mkdir('0', 'p'))
+ call assert_true(mkdir('0.orig', 'p'))
- call writefile([' <!doctype dsssl-spec ['], 'dslfile.dsl')
- split dslfile.dsl
- call assert_equal('dsl', &filetype)
+ call writefile(['FoamFile {', ' object something;'], 'Xfile1Dict')
+ split Xfile1Dict
+ call assert_equal('foam', &filetype)
bwipe!
- call writefile(['workspace {'], 'dslfile.dsl')
- split dslfile.dsl
- call assert_equal('structurizr', &filetype)
+ call writefile(['FoamFile {', ' object something;'], 'Xfile1Dict.something')
+ split Xfile1Dict.something
+ call assert_equal('foam', &filetype)
bwipe!
- call delete('dslfile.dsl')
+ call writefile(['FoamFile {', ' object something;'], 'XfileProperties')
+ split XfileProperties
+ call assert_equal('foam', &filetype)
+ bwipe!
+
+ call writefile(['FoamFile {', ' object something;'], 'XfileProperties.something')
+ split XfileProperties.something
+ call assert_equal('foam', &filetype)
+ bwipe!
+
+ call writefile(['FoamFile {', ' object something;'], 'XfileProperties')
+ split XfileProperties
+ call assert_equal('foam', &filetype)
+ bwipe!
+
+ call writefile(['FoamFile {', ' object something;'], 'XfileProperties.something')
+ split XfileProperties.something
+ call assert_equal('foam', &filetype)
+ bwipe!
+
+ call writefile(['FoamFile {', ' object something;'], '0/Xfile')
+ split 0/Xfile
+ call assert_equal('foam', &filetype)
+ bwipe!
+
+ call writefile(['FoamFile {', ' object something;'], '0.orig/Xfile')
+ split 0.orig/Xfile
+ call assert_equal('foam', &filetype)
+ bwipe!
+
+ call delete('0', 'rf')
+ call delete('0.orig', 'rf')
+ call delete('Xfile1Dict')
+ call delete('Xfile1Dict.something')
+ call delete('XfileProperties')
+ call delete('XfileProperties.something')
+ filetype off
+endfunc
+
+func Test_frm_file()
+ filetype on
+
+ call writefile(['looks like FORM'], 'Xfile.frm')
+ split Xfile.frm
+ call assert_equal('form', &filetype)
+ bwipe!
+
+ " Test dist#ft#FTfrm()
+
+ let g:filetype_frm = 'form'
+ split Xfile.frm
+ call assert_equal('form', &filetype)
+ bwipe!
+ unlet g:filetype_frm
+
+ " Visual Basic
+
+ call writefile(['Begin VB.Form Form1'], 'Xfile.frm')
+ split Xfile.frm
+ call assert_equal('vb', &filetype)
+ bwipe!
+
+ call delete('Xfile.frm')
+ filetype off
+endfunc
+
+func Test_fs_file()
+ filetype on
+
+ call writefile(['looks like F#'], 'Xfile.fs')
+ split Xfile.fs
+ call assert_equal('fsharp', &filetype)
+ bwipe!
+
+ let g:filetype_fs = 'forth'
+ split Xfile.fs
+ call assert_equal('forth', &filetype)
+ bwipe!
+ unlet g:filetype_fs
+
+ " Test dist#ft#FTfs()
+
+ " Forth (Gforth)
+
+ call writefile(['( Forth inline comment )'], 'Xfile.fs')
+ split Xfile.fs
+ call assert_equal('forth', &filetype)
+ bwipe!
+
+ call writefile(['.( Forth displayed inline comment )'], 'Xfile.fs')
+ split Xfile.fs
+ call assert_equal('forth', &filetype)
+ bwipe!
+
+ call writefile(['\ Forth line comment'], 'Xfile.fs')
+ split Xfile.fs
+ call assert_equal('forth', &filetype)
+ bwipe!
+
+ " empty line comment - no space required
+ call writefile(['\'], 'Xfile.fs')
+ split Xfile.fs
+ call assert_equal('forth', &filetype)
+ bwipe!
+
+ call writefile(['\G Forth documentation comment '], 'Xfile.fs')
+ split Xfile.fs
+ call assert_equal('forth', &filetype)
+ bwipe!
+
+ call writefile([': squared ( n -- n^2 )', 'dup * ;'], 'Xfile.fs')
+ split Xfile.fs
+ call assert_equal('forth', &filetype)
+ bwipe!
+
+ call delete('Xfile.fs')
+ filetype off
+endfunc
+
+func Test_git_file()
+ filetype on
+
+ call assert_true(mkdir('Xrepo.git', 'p'))
+
+ call writefile([], 'Xrepo.git/HEAD')
+ split Xrepo.git/HEAD
+ call assert_equal('', &filetype)
+ bwipe!
+
+ call writefile(['0000000000000000000000000000000000000000'], 'Xrepo.git/HEAD')
+ split Xrepo.git/HEAD
+ call assert_equal('git', &filetype)
+ bwipe!
+
+ call writefile(['0000000000000000000000000000000000000000000000000000000000000000'], 'Xrepo.git/HEAD')
+ split Xrepo.git/HEAD
+ call assert_equal('git', &filetype)
+ bwipe!
+
+ call writefile(['ref: refs/heads/master'], 'Xrepo.git/HEAD')
+ split Xrepo.git/HEAD
+ call assert_equal('git', &filetype)
+ bwipe!
+
+ call delete('Xrepo.git', 'rf')
+ filetype off
+endfunc
+
+func Test_hook_file()
+ filetype on
+
+ call writefile(['[Trigger]', 'this is pacman config'], 'Xfile.hook')
+ split Xfile.hook
+ call assert_equal('dosini', &filetype)
+ bwipe!
+
+ call writefile(['not pacman'], 'Xfile.hook')
+ split Xfile.hook
+ call assert_notequal('dosini', &filetype)
+ bwipe!
+
+ call delete('Xfile.hook')
filetype off
endfunc
@@ -940,109 +1214,154 @@ func Test_m_file()
filetype off
endfunc
-func Test_xpm_file()
+func Test_patch_file()
filetype on
- call writefile(['this is XPM2'], 'file.xpm')
- split file.xpm
- call assert_equal('xpm2', &filetype)
+ call writefile([], 'Xfile.patch')
+ split Xfile.patch
+ call assert_equal('diff', &filetype)
bwipe!
- call delete('file.xpm')
+ call writefile(['From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001'], 'Xfile.patch')
+ split Xfile.patch
+ call assert_equal('gitsendemail', &filetype)
+ bwipe!
+
+ call writefile(['From 0000000000000000000000000000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001'], 'Xfile.patch')
+ split Xfile.patch
+ call assert_equal('gitsendemail', &filetype)
+ bwipe!
+
+ call delete('Xfile.patch')
filetype off
endfunc
-func Test_fs_file()
+func Test_perl_file()
filetype on
- call writefile(['looks like F#'], 'Xfile.fs')
- split Xfile.fs
- call assert_equal('fsharp', &filetype)
- bwipe!
+ " only tests one case, should do more
+ let lines =<< trim END
- let g:filetype_fs = 'forth'
- split Xfile.fs
- call assert_equal('forth', &filetype)
- bwipe!
- unlet g:filetype_fs
+ use a
+ END
+ call writefile(lines, "Xfile.t")
+ split Xfile.t
+ call assert_equal('perl', &filetype)
+ bwipe
- " Test dist#ft#FTfs()
+ call delete('Xfile.t')
+ filetype off
+endfunc
- " Forth (Gforth)
+func Test_pp_file()
+ filetype on
- call writefile(['( Forth inline comment )'], 'Xfile.fs')
- split Xfile.fs
- call assert_equal('forth', &filetype)
+ call writefile(['looks like puppet'], 'Xfile.pp')
+ split Xfile.pp
+ call assert_equal('puppet', &filetype)
bwipe!
- call writefile(['.( Forth displayed inline comment )'], 'Xfile.fs')
- split Xfile.fs
- call assert_equal('forth', &filetype)
+ let g:filetype_pp = 'pascal'
+ split Xfile.pp
+ call assert_equal('pascal', &filetype)
bwipe!
+ unlet g:filetype_pp
- call writefile(['\ Forth line comment'], 'Xfile.fs')
- split Xfile.fs
- call assert_equal('forth', &filetype)
+ " Test dist#ft#FTpp()
+ call writefile(['{ pascal comment'], 'Xfile.pp')
+ split Xfile.pp
+ call assert_equal('pascal', &filetype)
bwipe!
- " empty line comment - no space required
- call writefile(['\'], 'Xfile.fs')
- split Xfile.fs
- call assert_equal('forth', &filetype)
+ call writefile(['procedure pascal'], 'Xfile.pp')
+ split Xfile.pp
+ call assert_equal('pascal', &filetype)
bwipe!
- call writefile(['\G Forth documentation comment '], 'Xfile.fs')
- split Xfile.fs
- call assert_equal('forth', &filetype)
+ call delete('Xfile.pp')
+ filetype off
+endfunc
+
+func Test_tex_file()
+ filetype on
+
+ " only tests one case, should do more
+ let lines =<< trim END
+ % This is a sentence.
+
+ This is a sentence.
+ END
+ call writefile(lines, "Xfile.tex")
+ split Xfile.tex
+ call assert_equal('plaintex', &filetype)
+ bwipe
+
+ call delete('Xfile.tex')
+ filetype off
+endfunc
+
+func Test_tf_file()
+ filetype on
+
+ call writefile([';;; TF MUD client is super duper cool'], 'Xfile.tf')
+ split Xfile.tf
+ call assert_equal('tf', &filetype)
bwipe!
- call writefile([': squared ( n -- n^2 )', 'dup * ;'], 'Xfile.fs')
- split Xfile.fs
- call assert_equal('forth', &filetype)
+ call writefile(['provider "azurerm" {'], 'Xfile.tf')
+ split Xfile.tf
+ call assert_equal('terraform', &filetype)
bwipe!
- call delete('Xfile.fs')
+ call delete('Xfile.tf')
filetype off
endfunc
-func Test_dep3patch_file()
+func Test_ts_file()
filetype on
- call assert_true(mkdir('debian/patches', 'p'))
-
- " series files are not patches
- call writefile(['Description: some awesome patch'], 'debian/patches/series')
- split debian/patches/series
- call assert_notequal('dep3patch', &filetype)
+ call writefile(['<?xml version="1.0" encoding="utf-8"?>'], 'Xfile.ts')
+ split Xfile.ts
+ call assert_equal('xml', &filetype)
bwipe!
- " diff/patch files without the right headers should still show up as ft=diff
- call writefile([], 'debian/patches/foo.diff')
- split debian/patches/foo.diff
- call assert_equal('diff', &filetype)
+ call writefile(['// looks like Typescript'], 'Xfile.ts')
+ split Xfile.ts
+ call assert_equal('typescript', &filetype)
bwipe!
- " Files with the right headers are detected as dep3patch, even if they don't
- " have a diff/patch extension
- call writefile(['Subject: dep3patches'], 'debian/patches/bar')
- split debian/patches/bar
- call assert_equal('dep3patch', &filetype)
+ call delete('Xfile.ts')
+ filetype off
+endfunc
+
+func Test_ttl_file()
+ filetype on
+
+ call writefile(['@base <http://example.org/> .'], 'Xfile.ttl')
+ split Xfile.ttl
+ call assert_equal('turtle', &filetype)
bwipe!
- " Files in sub-directories are detected
- call assert_true(mkdir('debian/patches/s390x', 'p'))
- call writefile(['Subject: dep3patches'], 'debian/patches/s390x/bar')
- split debian/patches/s390x/bar
- call assert_equal('dep3patch', &filetype)
+ call writefile(['looks like Tera Term Language'], 'Xfile.ttl')
+ split Xfile.ttl
+ call assert_equal('teraterm', &filetype)
bwipe!
- " The detection stops when seeing the "header end" marker
- call writefile(['---', 'Origin: the cloud'], 'debian/patches/baz')
- split debian/patches/baz
- call assert_notequal('dep3patch', &filetype)
+ call delete('Xfile.ttl')
+ filetype off
+endfunc
+
+func Test_xpm_file()
+ filetype on
+
+ call writefile(['this is XPM2'], 'file.xpm')
+ split file.xpm
+ call assert_equal('xpm2', &filetype)
bwipe!
- call delete('debian/patches', 'rf')
+ call delete('file.xpm')
+ filetype off
endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_filetype_lua.vim b/src/nvim/testdir/test_filetype_lua.vim
new file mode 100644
index 0000000000..f73e4ca33f
--- /dev/null
+++ b/src/nvim/testdir/test_filetype_lua.vim
@@ -0,0 +1,2 @@
+let g:do_filetype_lua = 1
+source test_filetype.vim
diff --git a/src/nvim/testdir/test_filter_cmd.vim b/src/nvim/testdir/test_filter_cmd.vim
index 0c45db049b..d465e48c7b 100644
--- a/src/nvim/testdir/test_filter_cmd.vim
+++ b/src/nvim/testdir/test_filter_cmd.vim
@@ -145,3 +145,38 @@ func Test_filter_commands()
bwipe! file.h
bwipe! file.hs
endfunc
+
+func Test_filter_display()
+ edit Xdoesnotmatch
+ let @a = '!!willmatch'
+ let @b = '!!doesnotmatch'
+ let @c = "oneline\ntwoline\nwillmatch\n"
+ let @/ = '!!doesnotmatch'
+ call feedkeys(":echo '!!doesnotmatch:'\<CR>", 'ntx')
+ let lines = map(split(execute('filter /willmatch/ display'), "\n"), 'v:val[5:6]')
+
+ call assert_true(index(lines, '"a') >= 0)
+ call assert_false(index(lines, '"b') >= 0)
+ call assert_true(index(lines, '"c') >= 0)
+ call assert_false(index(lines, '"/') >= 0)
+ call assert_false(index(lines, '":') >= 0)
+ call assert_false(index(lines, '"%') >= 0)
+
+ let lines = map(split(execute('filter /doesnotmatch/ display'), "\n"), 'v:val[5:6]')
+ call assert_true(index(lines, '"a') < 0)
+ call assert_false(index(lines, '"b') < 0)
+ call assert_true(index(lines, '"c') < 0)
+ call assert_false(index(lines, '"/') < 0)
+ call assert_false(index(lines, '":') < 0)
+ call assert_false(index(lines, '"%') < 0)
+
+ bwipe!
+endfunc
+
+func Test_filter_scriptnames()
+ let lines = split(execute('filter /test_filter_cmd/ scriptnames'), "\n")
+ call assert_equal(1, len(lines))
+ call assert_match('filter_cmd', lines[0])
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_filter_map.vim b/src/nvim/testdir/test_filter_map.vim
index a52a66ac2f..1cd3a2287b 100644
--- a/src/nvim/testdir/test_filter_map.vim
+++ b/src/nvim/testdir/test_filter_map.vim
@@ -88,4 +88,14 @@ func Test_map_filter_fails()
call assert_fails("let l = filter('abc', '\"> \" . v:val')", 'E896:')
endfunc
+func Test_map_and_modify()
+ let l = ["abc"]
+ " cannot change the list halfway a map()
+ call assert_fails('call map(l, "remove(l, 0)[0]")', 'E741:')
+
+ let d = #{a: 1, b: 2, c: 3}
+ call assert_fails('call map(d, "remove(d, v:key)[0]")', 'E741:')
+ call assert_fails('echo map(d, {k,v -> remove(d, k)})', 'E741:')
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_findfile.vim b/src/nvim/testdir/test_findfile.vim
index 5a20475d3d..1684c5d30a 100644
--- a/src/nvim/testdir/test_findfile.vim
+++ b/src/nvim/testdir/test_findfile.vim
@@ -226,4 +226,26 @@ func Test_find_cmd()
call assert_fails('tabfind', 'E471:')
endfunc
+func Test_find_non_existing_path()
+ new
+ let save_path = &path
+ let save_dir = getcwd()
+ call mkdir('dir1/dir2', 'p')
+ call writefile([], 'dir1/file.txt')
+ call writefile([], 'dir1/dir2/base.txt')
+ call chdir('dir1/dir2')
+ e base.txt
+ set path=../include
+
+ call assert_fails(':find file.txt', 'E345:')
+
+ call chdir(save_dir)
+ bw!
+ call delete('dir1/dir2/base.txt', 'rf')
+ call delete('dir1/dir2', 'rf')
+ call delete('dir1/file.txt', 'rf')
+ call delete('dir1', 'rf')
+ let &path = save_path
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_fnamemodify.vim b/src/nvim/testdir/test_fnamemodify.vim
index 411f7ebbb3..5ae2a5ee17 100644
--- a/src/nvim/testdir/test_fnamemodify.vim
+++ b/src/nvim/testdir/test_fnamemodify.vim
@@ -3,8 +3,10 @@
func Test_fnamemodify()
let save_home = $HOME
let save_shell = &shell
+ let save_shellslash = &shellslash
let $HOME = fnamemodify('.', ':p:h:h')
set shell=sh
+ set shellslash
call assert_equal('/', fnamemodify('.', ':p')[-1:])
call assert_equal('r', fnamemodify('.', ':p:h')[-1:])
@@ -27,6 +29,21 @@ func Test_fnamemodify()
call assert_equal('fb2.tar.gz', fnamemodify('abc.fb2.tar.gz', ':e:e:e'))
call assert_equal('fb2.tar.gz', fnamemodify('abc.fb2.tar.gz', ':e:e:e:e'))
call assert_equal('tar', fnamemodify('abc.fb2.tar.gz', ':e:e:r'))
+ call assert_equal(getcwd(), fnamemodify('', ':p:h'))
+
+ let cwd = getcwd()
+ call chdir($HOME)
+ call assert_equal('foobar', fnamemodify('~/foobar', ':~:.'))
+ call chdir(cwd)
+ call mkdir($HOME . '/XXXXXXXX/a', 'p')
+ call mkdir($HOME . '/XXXXXXXX/b', 'p')
+ call chdir($HOME . '/XXXXXXXX/a/')
+ call assert_equal('foo', fnamemodify($HOME . '/XXXXXXXX/a/foo', ':p:~:.'))
+ call assert_equal('~/XXXXXXXX/b/foo', fnamemodify($HOME . '/XXXXXXXX/b/foo', ':p:~:.'))
+ call mkdir($HOME . '/XXXXXXXX/a.ext', 'p')
+ call assert_equal('~/XXXXXXXX/a.ext/foo', fnamemodify($HOME . '/XXXXXXXX/a.ext/foo', ':p:~:.'))
+ call chdir(cwd)
+ call delete($HOME . '/XXXXXXXX', 'rf')
call assert_equal('''abc def''', fnamemodify('abc def', ':S'))
call assert_equal('''abc" "def''', fnamemodify('abc" "def', ':S'))
@@ -44,6 +61,7 @@ func Test_fnamemodify()
let $HOME = save_home
let &shell = save_shell
+ let &shellslash = save_shellslash
endfunc
func Test_fnamemodify_er()
@@ -73,6 +91,7 @@ func Test_fnamemodify_er()
call assert_equal('b.c', fnamemodify('a.b.c.d.e', ':r:r:e:e:e'))
call assert_equal('b.c', fnamemodify('a.b.c.d.e', ':r:r:e:e:e:e'))
+ call assert_equal('', fnamemodify('', ':p:t'))
call assert_equal('', fnamemodify(v:_null_string, v:_null_string))
endfunc
diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim
index 0edbeb420a..994d74601a 100644
--- a/src/nvim/testdir/test_functions.vim
+++ b/src/nvim/testdir/test_functions.vim
@@ -332,37 +332,6 @@ func Test_simplify()
call assert_fails('call simplify(1.2)', 'E806:')
endfunc
-func Test_setbufvar_options()
- " This tests that aucmd_prepbuf() and aucmd_restbuf() properly restore the
- " window layout.
- call assert_equal(1, winnr('$'))
- split dummy_preview
- resize 2
- set winfixheight winfixwidth
- let prev_id = win_getid()
-
- wincmd j
- let wh = winheight(0)
- let dummy_buf = bufnr('dummy_buf1', v:true)
- call setbufvar(dummy_buf, '&buftype', 'nofile')
- execute 'belowright vertical split #' . dummy_buf
- call assert_equal(wh, winheight(0))
- let dum1_id = win_getid()
-
- wincmd h
- let wh = winheight(0)
- let dummy_buf = bufnr('dummy_buf2', v:true)
- eval 'nofile'->setbufvar(dummy_buf, '&buftype')
- execute 'belowright vertical split #' . dummy_buf
- call assert_equal(wh, winheight(0))
-
- bwipe!
- call win_gotoid(prev_id)
- bwipe!
- call win_gotoid(dum1_id)
- bwipe!
-endfunc
-
func Test_pathshorten()
call assert_equal('', pathshorten(''))
call assert_equal('foo', pathshorten('foo'))
@@ -376,6 +345,25 @@ func Test_pathshorten()
call assert_equal('~.f/bar', pathshorten('~.foo/bar'))
call assert_equal('.~f/bar', pathshorten('.~foo/bar'))
call assert_equal('~/f/bar', pathshorten('~/foo/bar'))
+ call assert_fails('call pathshorten([])', 'E730:')
+
+ " test pathshorten with optional variable to set preferred size of shortening
+ call assert_equal('', pathshorten('', 2))
+ call assert_equal('foo', pathshorten('foo', 2))
+ call assert_equal('/foo', pathshorten('/foo', 2))
+ call assert_equal('fo/', pathshorten('foo/', 2))
+ call assert_equal('fo/bar', pathshorten('foo/bar', 2))
+ call assert_equal('fo/ba/foobar', pathshorten('foo/bar/foobar', 2))
+ call assert_equal('/fo/ba/foobar', pathshorten('/foo/bar/foobar', 2))
+ call assert_equal('.fo/bar', pathshorten('.foo/bar', 2))
+ call assert_equal('~fo/bar', pathshorten('~foo/bar', 2))
+ call assert_equal('~.fo/bar', pathshorten('~.foo/bar', 2))
+ call assert_equal('.~fo/bar', pathshorten('.~foo/bar', 2))
+ call assert_equal('~/fo/bar', pathshorten('~/foo/bar', 2))
+ call assert_fails('call pathshorten([],2)', 'E730:')
+ call assert_notequal('~/fo/bar', pathshorten('~/foo/bar', 3))
+ call assert_equal('~/foo/bar', pathshorten('~/foo/bar', 3))
+ call assert_equal('~/f/bar', pathshorten('~/foo/bar', 0))
endfunc
func Test_strpart()
@@ -1274,6 +1262,37 @@ func Test_shellescape()
let &shell = save_shell
endfunc
+func Test_setbufvar_options()
+ " This tests that aucmd_prepbuf() and aucmd_restbuf() properly restore the
+ " window layout.
+ call assert_equal(1, winnr('$'))
+ split dummy_preview
+ resize 2
+ set winfixheight winfixwidth
+ let prev_id = win_getid()
+
+ wincmd j
+ let wh = winheight(0)
+ let dummy_buf = bufnr('dummy_buf1', v:true)
+ call setbufvar(dummy_buf, '&buftype', 'nofile')
+ execute 'belowright vertical split #' . dummy_buf
+ call assert_equal(wh, winheight(0))
+ let dum1_id = win_getid()
+
+ wincmd h
+ let wh = winheight(0)
+ let dummy_buf = bufnr('dummy_buf2', v:true)
+ eval 'nofile'->setbufvar(dummy_buf, '&buftype')
+ execute 'belowright vertical split #' . dummy_buf
+ call assert_equal(wh, winheight(0))
+
+ bwipe!
+ call win_gotoid(prev_id)
+ bwipe!
+ call win_gotoid(dum1_id)
+ bwipe!
+endfunc
+
func Test_redo_in_nested_functions()
nnoremap g. :set opfunc=Operator<CR>g@
function Operator( type, ... )
@@ -1331,68 +1350,6 @@ func Test_trim()
call assert_equal("x", trim(chars . "x" . chars))
endfunc
-func EditAnotherFile()
- let word = expand('<cword>')
- edit Xfuncrange2
-endfunc
-
-func Test_func_range_with_edit()
- " Define a function that edits another buffer, then call it with a range that
- " is invalid in that buffer.
- call writefile(['just one line'], 'Xfuncrange2')
- new
- eval 10->range()->setline(1)
- write Xfuncrange1
- call assert_fails('5,8call EditAnotherFile()', 'E16:')
-
- call delete('Xfuncrange1')
- call delete('Xfuncrange2')
- bwipe!
-endfunc
-
-func Test_func_exists_on_reload()
- call writefile(['func ExistingFunction()', 'echo "yes"', 'endfunc'], 'Xfuncexists')
- call assert_equal(0, exists('*ExistingFunction'))
- source Xfuncexists
- call assert_equal(1, '*ExistingFunction'->exists())
- " Redefining a function when reloading a script is OK.
- source Xfuncexists
- call assert_equal(1, exists('*ExistingFunction'))
-
- " But redefining in another script is not OK.
- call writefile(['func ExistingFunction()', 'echo "yes"', 'endfunc'], 'Xfuncexists2')
- call assert_fails('source Xfuncexists2', 'E122:')
-
- delfunc ExistingFunction
- call assert_equal(0, exists('*ExistingFunction'))
- call writefile([
- \ 'func ExistingFunction()', 'echo "yes"', 'endfunc',
- \ 'func ExistingFunction()', 'echo "no"', 'endfunc',
- \ ], 'Xfuncexists')
- call assert_fails('source Xfuncexists', 'E122:')
- call assert_equal(1, exists('*ExistingFunction'))
-
- call delete('Xfuncexists2')
- call delete('Xfuncexists')
- delfunc ExistingFunction
-endfunc
-
-sandbox function Fsandbox()
- normal ix
-endfunc
-
-func Test_func_sandbox()
- sandbox let F = {-> 'hello'}
- call assert_equal('hello', F())
-
- sandbox let F = {-> "normal ix\<Esc>"->execute()}
- call assert_fails('call F()', 'E48:')
- unlet F
-
- call assert_fails('call Fsandbox()', 'E48:')
- delfunc Fsandbox
-endfunc
-
" Test for reg_recording() and reg_executing()
func Test_reg_executing_and_recording()
let s:reg_stat = ''
@@ -1494,6 +1451,10 @@ func Test_getchar()
call assert_equal('', getcharstr(0))
call assert_equal('', getcharstr(1))
+ call feedkeys("\<M-F2>", '')
+ call assert_equal("\<M-F2>", getchar(0))
+ call assert_equal(0, getchar(0))
+
call setline(1, 'xxxx')
" call test_setmouse(1, 3)
" let v:mouse_win = 9
@@ -1519,24 +1480,31 @@ func Test_libcall_libcallnr()
let libc = 'msvcrt.dll'
elseif has('mac')
let libc = 'libSystem.B.dylib'
- elseif system('uname -s') =~ 'SunOS'
- " Set the path to libc.so according to the architecture.
- let test_bits = system('file ' . GetVimProg())
- let test_arch = system('uname -p')
- if test_bits =~ '64-bit' && test_arch =~ 'sparc'
- let libc = '/usr/lib/sparcv9/libc.so'
- elseif test_bits =~ '64-bit' && test_arch =~ 'i386'
- let libc = '/usr/lib/amd64/libc.so'
+ elseif executable('ldd')
+ let libc = matchstr(split(system('ldd ' . GetVimProg())), '/libc\.so\>')
+ endif
+ if get(l:, 'libc', '') ==# ''
+ " On Unix, libc.so can be in various places.
+ if has('linux')
+ " There is not documented but regarding the 1st argument of glibc's
+ " dlopen an empty string and nullptr are equivalent, so using an empty
+ " string for the 1st argument of libcall allows to call functions.
+ let libc = ''
+ elseif has('sun')
+ " Set the path to libc.so according to the architecture.
+ let test_bits = system('file ' . GetVimProg())
+ let test_arch = system('uname -p')
+ if test_bits =~ '64-bit' && test_arch =~ 'sparc'
+ let libc = '/usr/lib/sparcv9/libc.so'
+ elseif test_bits =~ '64-bit' && test_arch =~ 'i386'
+ let libc = '/usr/lib/amd64/libc.so'
+ else
+ let libc = '/usr/lib/libc.so'
+ endif
else
- let libc = '/usr/lib/libc.so'
+ " Unfortunately skip this test until a good way is found.
+ return
endif
- elseif system('uname -s') =~ 'OpenBSD'
- let libc = 'libc.so'
- else
- " On Unix, libc.so can be in various places.
- " Interestingly, using an empty string for the 1st argument of libcall
- " allows to call functions from libc which is not documented.
- let libc = ''
endif
if has('win32')
@@ -1559,47 +1527,96 @@ func Test_libcall_libcallnr()
call assert_fails("call libcallnr('Xdoesnotexist_', 'strlen', 'abcd')", 'E364:')
endfunc
-func Test_bufadd_bufload()
- call assert_equal(0, bufexists('someName'))
- let buf = bufadd('someName')
- call assert_notequal(0, buf)
- call assert_equal(1, bufexists('someName'))
- call assert_equal(0, getbufvar(buf, '&buflisted'))
- call assert_equal(0, bufloaded(buf))
- call bufload(buf)
- call assert_equal(1, bufloaded(buf))
- call assert_equal([''], getbufline(buf, 1, '$'))
+sandbox function Fsandbox()
+ normal ix
+endfunc
- let curbuf = bufnr('')
- eval ['some', 'text']->writefile('XotherName')
- let buf = 'XotherName'->bufadd()
- call assert_notequal(0, buf)
- eval 'XotherName'->bufexists()->assert_equal(1)
- call assert_equal(0, getbufvar(buf, '&buflisted'))
- call assert_equal(0, bufloaded(buf))
- eval buf->bufload()
- call assert_equal(1, bufloaded(buf))
- call assert_equal(['some', 'text'], getbufline(buf, 1, '$'))
- call assert_equal(curbuf, bufnr(''))
+func Test_func_sandbox()
+ sandbox let F = {-> 'hello'}
+ call assert_equal('hello', F())
- let buf1 = bufadd('')
- let buf2 = bufadd('')
- call assert_notequal(0, buf1)
- call assert_notequal(0, buf2)
- call assert_notequal(buf1, buf2)
- call assert_equal(1, bufexists(buf1))
- call assert_equal(1, bufexists(buf2))
- call assert_equal(0, bufloaded(buf1))
- exe 'bwipe ' .. buf1
- call assert_equal(0, bufexists(buf1))
- call assert_equal(1, bufexists(buf2))
- exe 'bwipe ' .. buf2
- call assert_equal(0, bufexists(buf2))
+ sandbox let F = {-> "normal ix\<Esc>"->execute()}
+ call assert_fails('call F()', 'E48:')
+ unlet F
- bwipe someName
- bwipe XotherName
- call assert_equal(0, bufexists('someName'))
- call delete('XotherName')
+ call assert_fails('call Fsandbox()', 'E48:')
+ delfunc Fsandbox
+endfunc
+
+func EditAnotherFile()
+ let word = expand('<cword>')
+ edit Xfuncrange2
+endfunc
+
+func Test_func_range_with_edit()
+ " Define a function that edits another buffer, then call it with a range that
+ " is invalid in that buffer.
+ call writefile(['just one line'], 'Xfuncrange2')
+ new
+ eval 10->range()->setline(1)
+ write Xfuncrange1
+ call assert_fails('5,8call EditAnotherFile()', 'E16:')
+
+ call delete('Xfuncrange1')
+ call delete('Xfuncrange2')
+ bwipe!
+endfunc
+
+func Test_func_exists_on_reload()
+ call writefile(['func ExistingFunction()', 'echo "yes"', 'endfunc'], 'Xfuncexists')
+ call assert_equal(0, exists('*ExistingFunction'))
+ source Xfuncexists
+ call assert_equal(1, '*ExistingFunction'->exists())
+ " Redefining a function when reloading a script is OK.
+ source Xfuncexists
+ call assert_equal(1, exists('*ExistingFunction'))
+
+ " But redefining in another script is not OK.
+ call writefile(['func ExistingFunction()', 'echo "yes"', 'endfunc'], 'Xfuncexists2')
+ call assert_fails('source Xfuncexists2', 'E122:')
+
+ delfunc ExistingFunction
+ call assert_equal(0, exists('*ExistingFunction'))
+ call writefile([
+ \ 'func ExistingFunction()', 'echo "yes"', 'endfunc',
+ \ 'func ExistingFunction()', 'echo "no"', 'endfunc',
+ \ ], 'Xfuncexists')
+ call assert_fails('source Xfuncexists', 'E122:')
+ call assert_equal(1, exists('*ExistingFunction'))
+
+ call delete('Xfuncexists2')
+ call delete('Xfuncexists')
+ delfunc ExistingFunction
+endfunc
+
+func Test_platform_name()
+ " The system matches at most only one name.
+ let names = ['amiga', 'beos', 'bsd', 'hpux', 'linux', 'mac', 'qnx', 'sun', 'vms', 'win32', 'win32unix']
+ call assert_inrange(0, 1, len(filter(copy(names), 'has(v:val)')))
+
+ " Is Unix?
+ call assert_equal(has('beos'), has('beos') && has('unix'))
+ call assert_equal(has('bsd'), has('bsd') && has('unix'))
+ call assert_equal(has('hpux'), has('hpux') && has('unix'))
+ call assert_equal(has('linux'), has('linux') && has('unix'))
+ call assert_equal(has('mac'), has('mac') && has('unix'))
+ call assert_equal(has('qnx'), has('qnx') && has('unix'))
+ call assert_equal(has('sun'), has('sun') && has('unix'))
+ call assert_equal(has('win32'), has('win32') && !has('unix'))
+ call assert_equal(has('win32unix'), has('win32unix') && has('unix'))
+
+ if has('unix') && executable('uname')
+ let uname = system('uname')
+ call assert_equal(uname =~? 'BeOS', has('beos'))
+ " GNU userland on BSD kernels (e.g., GNU/kFreeBSD) don't have BSD defined
+ call assert_equal(uname =~? '\%(GNU/k\w\+\)\@<!BSD\|DragonFly', has('bsd'))
+ call assert_equal(uname =~? 'HP-UX', has('hpux'))
+ call assert_equal(uname =~? 'Linux', has('linux'))
+ call assert_equal(uname =~? 'Darwin', has('mac'))
+ call assert_equal(uname =~? 'QNX', has('qnx'))
+ call assert_equal(uname =~? 'SunOS', has('sun'))
+ call assert_equal(uname =~? 'CYGWIN\|MSYS', has('win32unix'))
+ endif
endfunc
func Test_readdir()
@@ -1658,6 +1675,49 @@ func Test_eventhandler()
call assert_equal(0, eventhandler())
endfunc
+func Test_bufadd_bufload()
+ call assert_equal(0, bufexists('someName'))
+ let buf = bufadd('someName')
+ call assert_notequal(0, buf)
+ call assert_equal(1, bufexists('someName'))
+ call assert_equal(0, getbufvar(buf, '&buflisted'))
+ call assert_equal(0, bufloaded(buf))
+ call bufload(buf)
+ call assert_equal(1, bufloaded(buf))
+ call assert_equal([''], getbufline(buf, 1, '$'))
+
+ let curbuf = bufnr('')
+ eval ['some', 'text']->writefile('XotherName')
+ let buf = 'XotherName'->bufadd()
+ call assert_notequal(0, buf)
+ eval 'XotherName'->bufexists()->assert_equal(1)
+ call assert_equal(0, getbufvar(buf, '&buflisted'))
+ call assert_equal(0, bufloaded(buf))
+ eval buf->bufload()
+ call assert_equal(1, bufloaded(buf))
+ call assert_equal(['some', 'text'], getbufline(buf, 1, '$'))
+ call assert_equal(curbuf, bufnr(''))
+
+ let buf1 = bufadd('')
+ let buf2 = bufadd('')
+ call assert_notequal(0, buf1)
+ call assert_notequal(0, buf2)
+ call assert_notequal(buf1, buf2)
+ call assert_equal(1, bufexists(buf1))
+ call assert_equal(1, bufexists(buf2))
+ call assert_equal(0, bufloaded(buf1))
+ exe 'bwipe ' .. buf1
+ call assert_equal(0, bufexists(buf1))
+ call assert_equal(1, bufexists(buf2))
+ exe 'bwipe ' .. buf2
+ call assert_equal(0, bufexists(buf2))
+
+ bwipe someName
+ bwipe XotherName
+ call assert_equal(0, bufexists('someName'))
+ call delete('XotherName')
+endfunc
+
" Test for the eval() function
func Test_eval()
call assert_fails("call eval('5 a')", 'E488:')
@@ -1675,6 +1735,33 @@ func Test_nr2char()
call assert_equal("\x80\xfc\b\xfd\x80\xfeX\x80\xfeX\x80\xfeX\x80\xfeX\x80\xfeX", eval('"\<M-' .. nr2char(0x40000000) .. '>"'))
endfunc
+" Test for getcurpos() and setpos()
+func Test_getcurpos_setpos()
+ new
+ call setline(1, ['012345678', '012345678'])
+ normal gg6l
+ let sp = getcurpos()
+ normal 0
+ call setpos('.', sp)
+ normal jyl
+ call assert_equal('6', @")
+ call assert_equal(-1, setpos('.', v:_null_list))
+ call assert_equal(-1, setpos('.', {}))
+
+ let winid = win_getid()
+ normal G$
+ let pos = getcurpos()
+ wincmd w
+ call assert_equal(pos, getcurpos(winid))
+
+ wincmd w
+ close!
+
+ call assert_equal(getcurpos(), getcurpos(0))
+ call assert_equal([0, 0, 0, 0, 0], getcurpos(-1))
+ call assert_equal([0, 0, 0, 0, 0], getcurpos(1999))
+endfunc
+
func HasDefault(msg = 'msg')
return a:msg
endfunc
diff --git a/src/nvim/testdir/test_gf.vim b/src/nvim/testdir/test_gf.vim
index 43efd6248e..589899f532 100644
--- a/src/nvim/testdir/test_gf.vim
+++ b/src/nvim/testdir/test_gf.vim
@@ -19,11 +19,7 @@ func Test_gf_url()
call search("^second")
call search("URL")
call assert_equal("URL://machine.name/tmp/vimtest2b", expand("<cfile>"))
- if has("ebcdic")
- set isf=@,240-249,/,.,-,_,+,,,$,:,~,\
- else
- set isf=@,48-57,/,.,-,_,+,,,$,~,\
- endif
+ set isf=@,48-57,/,.,-,_,+,,,$,~,\
call search("^third")
call search("name")
call assert_equal("URL:\\\\machine.name\\vimtest2c", expand("<cfile>"))
@@ -76,11 +72,7 @@ endfunc
" Test for invoking 'gf' on a ${VAR} variable
func Test_gf()
- if has("ebcdic")
- set isfname=@,240-249,/,.,-,_,+,,,$,:,~,{,}
- else
- set isfname=@,48-57,/,.,-,_,+,,,$,:,~,{,}
- endif
+ set isfname=@,48-57,/,.,-,_,+,,,$,:,~,{,}
call writefile(["Test for gf command"], "Xtest1")
if has("unix")
diff --git a/src/nvim/testdir/test_global.vim b/src/nvim/testdir/test_global.vim
index 8edc9c2608..ad561baf4a 100644
--- a/src/nvim/testdir/test_global.vim
+++ b/src/nvim/testdir/test_global.vim
@@ -36,6 +36,36 @@ func Test_global_error()
call assert_fails('g/\(/y', 'E476:')
endfunc
+" Test for printing lines using :g with different search patterns
+func Test_global_print()
+ new
+ call setline(1, ['foo', 'bar', 'foo', 'foo'])
+ let @/ = 'foo'
+ let t = execute("g/")->trim()->split("\n")
+ call assert_equal(['foo', 'foo', 'foo'], t)
+
+ " Test for Vi compatible patterns
+ let @/ = 'bar'
+ let t = execute('g\/')->trim()->split("\n")
+ call assert_equal(['bar'], t)
+
+ normal gg
+ s/foo/foo/
+ let t = execute('g\&')->trim()->split("\n")
+ call assert_equal(['foo', 'foo', 'foo'], t)
+
+ let @/ = 'bar'
+ let t = execute('g?')->trim()->split("\n")
+ call assert_equal(['bar'], t)
+
+ " Test for the 'Pattern found in every line' message
+ let v:statusmsg = ''
+ v/foo\|bar/p
+ call assert_notequal('', v:statusmsg)
+
+ close!
+endfunc
+
func Test_wrong_delimiter()
call assert_fails('g x^bxd', 'E146:')
endfunc
diff --git a/src/nvim/testdir/test_help.vim b/src/nvim/testdir/test_help.vim
index 8e59efd22d..b2d943be00 100644
--- a/src/nvim/testdir/test_help.vim
+++ b/src/nvim/testdir/test_help.vim
@@ -1,4 +1,3 @@
-
" Tests for :help
func Test_help_restore_snapshot()
@@ -13,6 +12,18 @@ endfunc
func Test_help_errors()
call assert_fails('help doesnotexist', 'E149:')
call assert_fails('help!', 'E478:')
+ if has('multi_lang')
+ call assert_fails('help help@xy', 'E661:')
+ endif
+
+ let save_hf = &helpfile
+ set helpfile=help_missing
+ help
+ call assert_equal(1, winnr('$'))
+ call assert_notequal('help', &buftype)
+ let &helpfile = save_hf
+
+ call assert_fails('help ' . repeat('a', 1048), 'E149:')
new
set keywordprg=:help
@@ -101,4 +112,13 @@ func Test_helptag_cmd()
call delete('Xdir', 'rf')
endfunc
+func Test_help_long_argument()
+ try
+ exe 'help \%' .. repeat('0', 1021)
+ catch
+ call assert_match("E149:", v:exception)
+ endtry
+endfunc
+
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_help_tagjump.vim b/src/nvim/testdir/test_help_tagjump.vim
index a6494c531c..a43889b57e 100644
--- a/src/nvim/testdir/test_help_tagjump.vim
+++ b/src/nvim/testdir/test_help_tagjump.vim
@@ -23,6 +23,11 @@ func Test_help_tagjump()
call assert_true(getline('.') =~ '\*bar\*')
helpclose
+ help "
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*quote\*')
+ helpclose
+
help "*
call assert_equal("help", &filetype)
call assert_true(getline('.') =~ '\*quotestar\*')
@@ -86,11 +91,40 @@ func Test_help_tagjump()
call assert_true(getline('.') =~ '\*i_^_CTRL-D\*')
helpclose
+ help i^x^y
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*i_CTRL-X_CTRL-Y\*')
+ helpclose
+
+ exe "help i\<C-\>\<C-G>"
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*i_CTRL-\\_CTRL-G\*')
+ helpclose
+
exec "help \<C-V>"
call assert_equal("help", &filetype)
call assert_true(getline('.') =~ '\*CTRL-V\*')
helpclose
+ help /\|
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*/\\bar\*')
+ helpclose
+
+ help CTRL-\_CTRL-N
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*CTRL-\\_CTRL-N\*')
+ helpclose
+
+ help `:pwd`,
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*:pwd\*')
+ helpclose
+
+ help `:ls`.
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*:ls\*')
+ helpclose
exec "help! ('textwidth'"
call assert_equal("help", &filetype)
@@ -122,6 +156,15 @@ func Test_help_tagjump()
call assert_true(getline('.') =~ '\*{address}\*')
helpclose
+ " Use special patterns in the help tag
+ for h in ['/\w', '/\%^', '/\%(', '/\zs', '/\@<=', '/\_$', '[++opt]', '/\{']
+ exec "help! " . h
+ call assert_equal("help", &filetype)
+ let pat = '\*' . escape(h, '\$[') . '\*'
+ call assert_true(getline('.') =~ pat, pat)
+ helpclose
+ endfor
+
exusage
call assert_equal("help", &filetype)
call assert_true(getline('.') =~ '\*:index\*')
diff --git a/src/nvim/testdir/test_highlight.vim b/src/nvim/testdir/test_highlight.vim
index 899eb530ec..6971ecd357 100644
--- a/src/nvim/testdir/test_highlight.vim
+++ b/src/nvim/testdir/test_highlight.vim
@@ -597,6 +597,31 @@ func Test_cursorline_with_visualmode()
call delete('Xtest_cursorline_with_visualmode')
endfunc
+func Test_colorcolumn()
+ CheckScreendump
+
+ " check that setting 'colorcolumn' when entering a buffer works
+ let lines =<< trim END
+ split
+ edit X
+ call setline(1, ["1111111111","22222222222","3333333333"])
+ set nomodified
+ set colorcolumn=3,9
+ set number cursorline cursorlineopt=number
+ wincmd w
+ buf X
+ END
+ call writefile(lines, 'Xtest_colorcolumn')
+ let buf = RunVimInTerminal('-S Xtest_colorcolumn', {'rows': 10})
+ call term_sendkeys(buf, ":\<CR>")
+ call term_wait(buf)
+ call VerifyScreenDump(buf, 'Test_colorcolumn_1', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('Xtest_colorcolumn')
+endfunc
+
func Test_colorcolumn_bri()
CheckScreendump
diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim
index ce75799551..186fa8871f 100644
--- a/src/nvim/testdir/test_ins_complete.vim
+++ b/src/nvim/testdir/test_ins_complete.vim
@@ -109,7 +109,7 @@ func s:CompleteDone_CompleteFuncNone( findstart, base )
return v:none
endfunc
-function! s:CompleteDone_CompleteFuncDict( findstart, base )
+func s:CompleteDone_CompleteFuncDict( findstart, base )
if a:findstart
return 0
endif
@@ -126,7 +126,7 @@ function! s:CompleteDone_CompleteFuncDict( findstart, base )
\ }
\ ]
\ }
-endfunction
+endfunc
func s:CompleteDone_CheckCompletedItemNone()
let s:called_completedone = 1
@@ -341,6 +341,14 @@ func Test_compl_feedkeys()
set completeopt&
endfunc
+func s:ComplInCmdwin_GlobalCompletion(a, l, p)
+ return 'global'
+endfunc
+
+func s:ComplInCmdwin_LocalCompletion(a, l, p)
+ return 'local'
+endfunc
+
func Test_compl_in_cmdwin()
set wildmenu wildchar=<Tab>
com! -nargs=1 -complete=command GetInput let input = <q-args>
@@ -376,6 +384,47 @@ func Test_compl_in_cmdwin()
call feedkeys("q::GetInput b:test_\<Tab>\<CR>:q\<CR>", 'tx!')
call assert_equal('b:test_', input)
+
+ " Argument completion of buffer-local command
+ func s:ComplInCmdwin_GlobalCompletionList(a, l, p)
+ return ['global']
+ endfunc
+
+ func s:ComplInCmdwin_LocalCompletionList(a, l, p)
+ return ['local']
+ endfunc
+
+ func s:ComplInCmdwin_CheckCompletion(arg)
+ call assert_equal('local', a:arg)
+ endfunc
+
+ com! -nargs=1 -complete=custom,<SID>ComplInCmdwin_GlobalCompletion
+ \ TestCommand call s:ComplInCmdwin_CheckCompletion(<q-args>)
+ com! -buffer -nargs=1 -complete=custom,<SID>ComplInCmdwin_LocalCompletion
+ \ TestCommand call s:ComplInCmdwin_CheckCompletion(<q-args>)
+ call feedkeys("q:iTestCommand \<Tab>\<CR>", 'tx!')
+
+ com! -nargs=1 -complete=customlist,<SID>ComplInCmdwin_GlobalCompletionList
+ \ TestCommand call s:ComplInCmdwin_CheckCompletion(<q-args>)
+ com! -buffer -nargs=1 -complete=customlist,<SID>ComplInCmdwin_LocalCompletionList
+ \ TestCommand call s:ComplInCmdwin_CheckCompletion(<q-args>)
+
+ call feedkeys("q:iTestCommand \<Tab>\<CR>", 'tx!')
+
+ func! s:ComplInCmdwin_CheckCompletion(arg)
+ call assert_equal('global', a:arg)
+ endfunc
+ new
+ call feedkeys("q:iTestCommand \<Tab>\<CR>", 'tx!')
+ quit
+
+ delfunc s:ComplInCmdwin_GlobalCompletion
+ delfunc s:ComplInCmdwin_LocalCompletion
+ delfunc s:ComplInCmdwin_GlobalCompletionList
+ delfunc s:ComplInCmdwin_LocalCompletionList
+ delfunc s:ComplInCmdwin_CheckCompletion
+
+ delcom -buffer TestCommand
delcom TestCommand
delcom GetInput
unlet w:test_winvar
@@ -445,6 +494,28 @@ func Test_issue_7021()
set completeslash=
endfunc
+func Test_pum_stopped_by_timer()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, ['hello', 'hullo', 'heeee', ''])
+ func StartCompl()
+ call timer_start(100, { -> execute('stopinsert') })
+ call feedkeys("Gah\<C-N>")
+ endfunc
+ END
+
+ call writefile(lines, 'Xpumscript')
+ let buf = RunVimInTerminal('-S Xpumscript', #{rows: 12})
+ call term_sendkeys(buf, ":call StartCompl()\<CR>")
+ call TermWait(buf, 200)
+ call term_sendkeys(buf, "k")
+ call VerifyScreenDump(buf, 'Test_pum_stopped_by_timer', {})
+
+ call StopVimInTerminal(buf)
+ call delete('Xpumscript')
+endfunc
+
func Test_pum_with_folds_two_tabs()
CheckScreendump
diff --git a/src/nvim/testdir/test_lambda.vim b/src/nvim/testdir/test_lambda.vim
index 63bb4ae1ef..c1fe47d1c9 100644
--- a/src/nvim/testdir/test_lambda.vim
+++ b/src/nvim/testdir/test_lambda.vim
@@ -1,24 +1,24 @@
" Test for lambda and closure
-function! Test_lambda_feature()
+func Test_lambda_feature()
call assert_equal(1, has('lambda'))
-endfunction
+endfunc
-function! Test_lambda_with_filter()
+func Test_lambda_with_filter()
let s:x = 2
call assert_equal([2, 3], filter([1, 2, 3], {i, v -> v >= s:x}))
-endfunction
+endfunc
-function! Test_lambda_with_map()
+func Test_lambda_with_map()
let s:x = 1
call assert_equal([2, 3, 4], map([1, 2, 3], {i, v -> v + s:x}))
-endfunction
+endfunc
-function! Test_lambda_with_sort()
+func Test_lambda_with_sort()
call assert_equal([1, 2, 3, 4, 7], sort([3,7,2,1,4], {a, b -> a - b}))
-endfunction
+endfunc
-function! Test_lambda_with_timer()
+func Test_lambda_with_timer()
if !has('timers')
return
endif
@@ -54,10 +54,10 @@ function! Test_lambda_with_timer()
call assert_true(s:n > m)
endfunc
-function! Test_lambda_with_partial()
+func Test_lambda_with_partial()
let l:Cb = function({... -> ['zero', a:1, a:2, a:3]}, ['one', 'two'])
call assert_equal(['zero', 'one', 'two', 'three'], l:Cb('three'))
-endfunction
+endfunc
function Test_lambda_fails()
call assert_equal(3, {a, b -> a + b}(1, 2))
@@ -70,59 +70,59 @@ func Test_not_lambda()
call assert_equal('foo', x['>'])
endfunc
-function! Test_lambda_capture_by_reference()
+func Test_lambda_capture_by_reference()
let v = 1
let l:F = {x -> x + v}
let v = 2
call assert_equal(12, l:F(10))
-endfunction
+endfunc
-function! Test_lambda_side_effect()
- function! s:update_and_return(arr)
+func Test_lambda_side_effect()
+ func! s:update_and_return(arr)
let a:arr[1] = 5
return a:arr
- endfunction
+ endfunc
- function! s:foo(arr)
+ func! s:foo(arr)
return {-> s:update_and_return(a:arr)}
- endfunction
+ endfunc
let arr = [3,2,1]
call assert_equal([3, 5, 1], s:foo(arr)())
-endfunction
+endfunc
-function! Test_lambda_refer_local_variable_from_other_scope()
- function! s:foo(X)
+func Test_lambda_refer_local_variable_from_other_scope()
+ func! s:foo(X)
return a:X() " refer l:x in s:bar()
- endfunction
+ endfunc
- function! s:bar()
+ func! s:bar()
let x = 123
return s:foo({-> x})
- endfunction
+ endfunc
call assert_equal(123, s:bar())
-endfunction
+endfunc
-function! Test_lambda_do_not_share_local_variable()
- function! s:define_funcs()
+func Test_lambda_do_not_share_local_variable()
+ func! s:define_funcs()
let l:One = {-> split(execute("let a = 'abc' | echo a"))[0]}
let l:Two = {-> exists("a") ? a : "no"}
return [l:One, l:Two]
- endfunction
+ endfunc
let l:F = s:define_funcs()
call assert_equal('no', l:F[1]())
call assert_equal('abc', l:F[0]())
call assert_equal('no', l:F[1]())
-endfunction
+endfunc
-function! Test_lambda_closure_counter()
- function! s:foo()
+func Test_lambda_closure_counter()
+ func! s:foo()
let x = 0
return {-> [execute("let x += 1"), x][-1]}
- endfunction
+ endfunc
let l:F = s:foo()
call garbagecollect()
@@ -130,52 +130,52 @@ function! Test_lambda_closure_counter()
call assert_equal(2, l:F())
call assert_equal(3, l:F())
call assert_equal(4, l:F())
-endfunction
+endfunc
-function! Test_lambda_with_a_var()
- function! s:foo()
+func Test_lambda_with_a_var()
+ func! s:foo()
let x = 2
return {... -> a:000 + [x]}
- endfunction
- function! s:bar()
+ endfunc
+ func! s:bar()
return s:foo()(1)
- endfunction
+ endfunc
call assert_equal([1, 2], s:bar())
-endfunction
+endfunc
-function! Test_lambda_call_lambda_from_lambda()
- function! s:foo(x)
+func Test_lambda_call_lambda_from_lambda()
+ func! s:foo(x)
let l:F1 = {-> {-> a:x}}
return {-> l:F1()}
- endfunction
+ endfunc
let l:F = s:foo(1)
call assert_equal(1, l:F()())
-endfunction
+endfunc
-function! Test_lambda_delfunc()
- function! s:gen()
+func Test_lambda_delfunc()
+ func! s:gen()
let pl = l:
let l:Foo = {-> get(pl, "Foo", get(pl, "Bar", {-> 0}))}
let l:Bar = l:Foo
delfunction l:Foo
return l:Bar
- endfunction
+ endfunc
let l:F = s:gen()
call assert_fails(':call l:F()', 'E933:')
-endfunction
+endfunc
-function! Test_lambda_scope()
- function! s:NewCounter()
+func Test_lambda_scope()
+ func! s:NewCounter()
let c = 0
return {-> [execute('let c += 1'), c][-1]}
- endfunction
+ endfunc
- function! s:NewCounter2()
+ func! s:NewCounter2()
return {-> [execute('let c += 100'), c][-1]}
- endfunction
+ endfunc
let l:C = s:NewCounter()
let l:D = s:NewCounter2()
@@ -183,37 +183,37 @@ function! Test_lambda_scope()
call assert_equal(1, l:C())
call assert_fails(':call l:D()', 'E121:')
call assert_equal(2, l:C())
-endfunction
+endfunc
-function! Test_lambda_share_scope()
- function! s:New()
+func Test_lambda_share_scope()
+ func! s:New()
let c = 0
let l:Inc0 = {-> [execute('let c += 1'), c][-1]}
let l:Dec0 = {-> [execute('let c -= 1'), c][-1]}
return [l:Inc0, l:Dec0]
- endfunction
+ endfunc
let [l:Inc, l:Dec] = s:New()
call assert_equal(1, l:Inc())
call assert_equal(2, l:Inc())
call assert_equal(1, l:Dec())
-endfunction
+endfunc
-function! Test_lambda_circular_reference()
- function! s:Foo()
+func Test_lambda_circular_reference()
+ func! s:Foo()
let d = {}
let d.f = {-> d}
return d.f
- endfunction
+ endfunc
call s:Foo()
call garbagecollect()
let i = 0 | while i < 10000 | call s:Foo() | let i+= 1 | endwhile
call garbagecollect()
-endfunction
+endfunc
-function! Test_lambda_combination()
+func Test_lambda_combination()
call assert_equal(2, {x -> {x -> x}}(1)(2))
call assert_equal(10, {y -> {x -> x(y)(10)}({y -> y})}({z -> z}))
call assert_equal(5.0, {x -> {y -> x / y}}(10)(2.0))
@@ -226,17 +226,17 @@ function! Test_lambda_combination()
let Z = {f -> {x -> f({y -> x(x)(y)})}({x -> f({y -> x(x)(y)})})}
let Fact = {f -> {x -> x == 0 ? 1 : x * f(x - 1)}}
call assert_equal(120, Z(Fact)(5))
-endfunction
+endfunc
-function! Test_closure_counter()
- function! s:foo()
+func Test_closure_counter()
+ func! s:foo()
let x = 0
- function! s:bar() closure
+ func! s:bar() closure
let x += 1
return x
- endfunction
+ endfunc
return function('s:bar')
- endfunction
+ endfunc
let l:F = s:foo()
call garbagecollect()
@@ -244,30 +244,30 @@ function! Test_closure_counter()
call assert_equal(2, l:F())
call assert_equal(3, l:F())
call assert_equal(4, l:F())
-endfunction
+endfunc
-function! Test_closure_unlet()
- function! s:foo()
+func Test_closure_unlet()
+ func! s:foo()
let x = 1
- function! s:bar() closure
+ func! s:bar() closure
unlet x
- endfunction
+ endfunc
call s:bar()
return l:
- endfunction
+ endfunc
call assert_false(has_key(s:foo(), 'x'))
call garbagecollect()
-endfunction
+endfunc
-function! LambdaFoo()
+func LambdaFoo()
let x = 0
- function! LambdaBar() closure
+ func! LambdaBar() closure
let x += 1
return x
- endfunction
+ endfunc
return function('LambdaBar')
-endfunction
+endfunc
func Test_closure_refcount()
let g:Count = LambdaFoo()
@@ -303,3 +303,8 @@ func Test_lambda_with_index()
let Extract = {-> function(List, ['foobar'])()[0]}
call assert_equal('foobar', Extract())
endfunc
+
+func Test_lambda_error()
+ " This was causing a crash
+ call assert_fails('ec{@{->{d->()()', 'E15')
+endfunc
diff --git a/src/nvim/testdir/test_listchars.vim b/src/nvim/testdir/test_listchars.vim
index 0bcbd9c4a5..c6e2ebd406 100644
--- a/src/nvim/testdir/test_listchars.vim
+++ b/src/nvim/testdir/test_listchars.vim
@@ -1,6 +1,8 @@
" Tests for 'listchars' display with 'list' and :list
+source check.vim
source view_util.vim
+source screendump.vim
func Test_listchars()
enew!
@@ -517,4 +519,34 @@ func Test_listchars_window_local()
set list& listchars&
endfunc
+func Test_listchars_foldcolumn()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, ['aaa', '', 'a', 'aaaaaa'])
+ vsplit
+ vsplit
+ windo set signcolumn=yes foldcolumn=1 winminwidth=0 nowrap list listchars=extends:>,precedes:<
+ END
+ call writefile(lines, 'XTest_listchars')
+
+ let buf = RunVimInTerminal('-S XTest_listchars', {'rows': 10, 'cols': 60})
+
+ call term_sendkeys(buf, "13\<C-W>>")
+ call VerifyScreenDump(buf, 'Test_listchars_01', {})
+ call term_sendkeys(buf, "\<C-W>>")
+ call VerifyScreenDump(buf, 'Test_listchars_02', {})
+ call term_sendkeys(buf, "\<C-W>>")
+ call VerifyScreenDump(buf, 'Test_listchars_03', {})
+ call term_sendkeys(buf, "\<C-W>>")
+ call VerifyScreenDump(buf, 'Test_listchars_04', {})
+ call term_sendkeys(buf, "\<C-W>>")
+ call VerifyScreenDump(buf, 'Test_listchars_05', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('XTest_listchars')
+endfunc
+
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_listdict.vim b/src/nvim/testdir/test_listdict.vim
index f6c404d390..aa66d86af1 100644
--- a/src/nvim/testdir/test_listdict.vim
+++ b/src/nvim/testdir/test_listdict.vim
@@ -516,22 +516,22 @@ func Test_dict_lock_operator()
endfunc
" No remove() of write-protected scope-level variable
-func! Tfunc(this_is_a_long_parameter_name)
+func Tfunc1(this_is_a_long_parameter_name)
call assert_fails("call remove(a:, 'this_is_a_long_parameter_name')", 'E742')
-endfun
+endfunc
func Test_dict_scope_var_remove()
- call Tfunc('testval')
+ call Tfunc1('testval')
endfunc
" No extend() of write-protected scope-level variable
func Test_dict_scope_var_extend()
call assert_fails("call extend(a:, {'this_is_a_long_parameter_name': 1234})", 'E742')
endfunc
-func! Tfunc(this_is_a_long_parameter_name)
+func Tfunc2(this_is_a_long_parameter_name)
call assert_fails("call extend(a:, {'this_is_a_long_parameter_name': 1234})", 'E742')
endfunc
func Test_dict_scope_var_extend_overwrite()
- call Tfunc('testval')
+ call Tfunc2('testval')
endfunc
" No :unlet of variable in locked scope
@@ -620,6 +620,49 @@ func Test_reverse_sort_uniq()
call assert_fails('call reverse("")', 'E899:')
endfunc
+" reduce a list or a blob
+func Test_reduce()
+ call assert_equal(1, reduce([], { acc, val -> acc + val }, 1))
+ call assert_equal(10, reduce([1, 3, 5], { acc, val -> acc + val }, 1))
+ call assert_equal(2 * (2 * ((2 * 1) + 2) + 3) + 4, reduce([2, 3, 4], { acc, val -> 2 * acc + val }, 1))
+ call assert_equal('a x y z', ['x', 'y', 'z']->reduce({ acc, val -> acc .. ' ' .. val}, 'a'))
+ call assert_equal(#{ x: 1, y: 1, z: 1 }, ['x', 'y', 'z']->reduce({ acc, val -> extend(acc, { val: 1 }) }, {}))
+ call assert_equal([0, 1, 2, 3], reduce([1, 2, 3], function('add'), [0]))
+
+ let l = ['x', 'y', 'z']
+ call assert_equal(42, reduce(l, function('get'), #{ x: #{ y: #{ z: 42 } } }))
+ call assert_equal(['x', 'y', 'z'], l)
+
+ call assert_equal(1, reduce([1], { acc, val -> acc + val }))
+ call assert_equal('x y z', reduce(['x', 'y', 'z'], { acc, val -> acc .. ' ' .. val }))
+ call assert_equal(120, range(1, 5)->reduce({ acc, val -> acc * val }))
+ call assert_fails("call reduce([], { acc, val -> acc + val })", 'E998: Reduce of an empty List with no initial value')
+
+ call assert_equal(1, reduce(0z, { acc, val -> acc + val }, 1))
+ call assert_equal(1 + 0xaf + 0xbf + 0xcf, reduce(0zAFBFCF, { acc, val -> acc + val }, 1))
+ call assert_equal(2 * (2 * 1 + 0xaf) + 0xbf, 0zAFBF->reduce({ acc, val -> 2 * acc + val }, 1))
+
+ call assert_equal(0xff, reduce(0zff, { acc, val -> acc + val }))
+ call assert_equal(2 * (2 * 0xaf + 0xbf) + 0xcf, reduce(0zAFBFCF, { acc, val -> 2 * acc + val }))
+ call assert_fails("call reduce(0z, { acc, val -> acc + val })", 'E998: Reduce of an empty Blob with no initial value')
+
+ call assert_fails("call reduce({}, { acc, val -> acc + val }, 1)", 'E897:')
+ call assert_fails("call reduce(0, { acc, val -> acc + val }, 1)", 'E897:')
+ call assert_fails("call reduce('', { acc, val -> acc + val }, 1)", 'E897:')
+
+ let g:lut = [1, 2, 3, 4]
+ func EvilRemove()
+ call remove(g:lut, 1)
+ return 1
+ endfunc
+ call assert_fails("call reduce(g:lut, { acc, val -> EvilRemove() }, 1)", 'E742:')
+ unlet g:lut
+ delfunc EvilRemove
+
+ call assert_equal(42, reduce(v:_null_list, function('add'), 42))
+ call assert_equal(42, reduce(v:_null_blob, function('add'), 42))
+endfunc
+
" splitting a string to a List
func Test_str_split()
call assert_equal(['aa', 'bb'], split(' aa bb '))
diff --git a/src/nvim/testdir/test_listlbr_utf8.vim b/src/nvim/testdir/test_listlbr_utf8.vim
index c38e0c5f3c..1f100d6244 100644
--- a/src/nvim/testdir/test_listlbr_utf8.vim
+++ b/src/nvim/testdir/test_listlbr_utf8.vim
@@ -69,6 +69,16 @@ func Test_nolinebreak_with_list()
call s:close_windows()
endfunc
+" this was causing a crash
+func Test_linebreak_with_list_and_tabs()
+ set linebreak list listchars=tab:⇤\ ⇥ tabstop=100
+ new
+ call setline(1, "\t\t\ttext")
+ redraw
+ bwipe!
+ set nolinebreak nolist listchars&vim tabstop=8
+endfunc
+
func Test_linebreak_with_nolist()
call s:test_windows('setl nolist')
call setline(1, "\t*mask = nil;")
diff --git a/src/nvim/testdir/test_mapping.vim b/src/nvim/testdir/test_mapping.vim
index f88e8cf843..1080a3c85b 100644
--- a/src/nvim/testdir/test_mapping.vim
+++ b/src/nvim/testdir/test_mapping.vim
@@ -1,6 +1,8 @@
" Tests for mappings and abbreviations
source shared.vim
+source check.vim
+source screendump.vim
func Test_abbreviation()
" abbreviation with 0x80 should work
@@ -451,6 +453,82 @@ func Test_expr_map_gets_cursor()
nunmap !
endfunc
+func Test_expr_map_restore_cursor()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, ['one', 'two', 'three'])
+ 2
+ set ls=2
+ hi! link StatusLine ErrorMsg
+ noremap <expr> <C-B> Func()
+ func Func()
+ let g:on = !get(g:, 'on', 0)
+ redraws
+ return ''
+ endfunc
+ func Status()
+ return get(g:, 'on', 0) ? '[on]' : ''
+ endfunc
+ set stl=%{Status()}
+ END
+ call writefile(lines, 'XtestExprMap')
+ let buf = RunVimInTerminal('-S XtestExprMap', #{rows: 10})
+ call term_sendkeys(buf, "\<C-B>")
+ call VerifyScreenDump(buf, 'Test_map_expr_1', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('XtestExprMap')
+endfunc
+
+func Test_map_listing()
+ CheckScreendump
+
+ let lines =<< trim END
+ nmap a b
+ END
+ call writefile(lines, 'XtestMapList')
+ let buf = RunVimInTerminal('-S XtestMapList', #{rows: 6})
+ call term_sendkeys(buf, ": nmap a\<CR>")
+ call VerifyScreenDump(buf, 'Test_map_list_1', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('XtestMapList')
+endfunc
+
+func Test_expr_map_error()
+ CheckScreendump
+
+ let lines =<< trim END
+ func Func()
+ throw 'test'
+ return ''
+ endfunc
+
+ nnoremap <expr> <F2> Func()
+ cnoremap <expr> <F2> Func()
+
+ call test_override('ui_delay', 10)
+ END
+ call writefile(lines, 'XtestExprMap')
+ let buf = RunVimInTerminal('-S XtestExprMap', #{rows: 10})
+ call term_sendkeys(buf, "\<F2>")
+ call TermWait(buf)
+ call term_sendkeys(buf, "\<CR>")
+ call VerifyScreenDump(buf, 'Test_map_expr_2', {})
+
+ call term_sendkeys(buf, ":abc\<F2>")
+ call VerifyScreenDump(buf, 'Test_map_expr_3', {})
+ call term_sendkeys(buf, "\<Esc>0")
+ call VerifyScreenDump(buf, 'Test_map_expr_4', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('XtestExprMap')
+endfunc
+
" Test for mapping errors
func Test_map_error()
call assert_fails('unmap', 'E474:')
diff --git a/src/nvim/testdir/test_marks.vim b/src/nvim/testdir/test_marks.vim
index b3035d73ce..00ee8f6d6a 100644
--- a/src/nvim/testdir/test_marks.vim
+++ b/src/nvim/testdir/test_marks.vim
@@ -1,6 +1,6 @@
" Test that a deleted mark is restored after delete-undo-redo-undo.
-function! Test_Restore_DelMark()
+func Test_Restore_DelMark()
enew!
call append(0, [" textline A", " textline B", " textline C"])
normal! 2gg
@@ -11,10 +11,10 @@ function! Test_Restore_DelMark()
call assert_equal(2, pos[1])
call assert_equal(1, pos[2])
enew!
-endfunction
+endfunc
" Test that CTRL-A and CTRL-X updates last changed mark '[, '].
-function! Test_Incr_Marks()
+func Test_Incr_Marks()
enew!
call append(0, ["123 123 123", "123 123 123", "123 123 123"])
normal! gg
@@ -23,7 +23,17 @@ function! Test_Incr_Marks()
call assert_equal("123 XXXXXXX", getline(2))
call assert_equal("XXX 123 123", getline(3))
enew!
-endfunction
+endfunc
+
+func Test_previous_jump_mark()
+ new
+ call setline(1, ['']->repeat(6))
+ normal Ggg
+ call assert_equal(6, getpos("''")[1])
+ normal jjjjj
+ call assert_equal(6, getpos("''")[1])
+ bwipe!
+endfunc
func Test_setpos()
new Xone
@@ -207,6 +217,21 @@ func Test_mark_error()
call assert_fails('mark _', 'E191:')
endfunc
+" Test for :lockmarks when pasting content
+func Test_lockmarks_with_put()
+ new
+ call append(0, repeat(['sky is blue'], 4))
+ normal gg
+ 1,2yank r
+ put r
+ normal G
+ lockmarks put r
+ call assert_equal(2, line("'["))
+ call assert_equal(3, line("']"))
+
+ bwipe!
+endfunc
+
" Test for the getmarklist() function
func Test_getmarklist()
new
@@ -231,3 +256,5 @@ func Test_getmarklist()
call assert_equal([], {}->getmarklist())
close!
endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_matchadd_conceal.vim b/src/nvim/testdir/test_matchadd_conceal.vim
index 2cbaf5cb76..29a2c30b0d 100644
--- a/src/nvim/testdir/test_matchadd_conceal.vim
+++ b/src/nvim/testdir/test_matchadd_conceal.vim
@@ -7,7 +7,7 @@ source shared.vim
source term_util.vim
source view_util.vim
-function! Test_simple_matchadd()
+func Test_simple_matchadd()
new
1put='# This is a Test'
@@ -333,7 +333,7 @@ func Test_matchadd_and_syn_conceal()
call assert_notequal(screenattr(1, 10) , screenattr(1, 11))
call assert_notequal(screenattr(1, 11) , screenattr(1, 12))
call assert_equal(screenattr(1, 11) , screenattr(1, 32))
-endfunction
+endfunc
func Test_cursor_column_in_concealed_line_after_window_scroll()
CheckRunVimInTerminal
diff --git a/src/nvim/testdir/test_matchadd_conceal_utf8.vim b/src/nvim/testdir/test_matchadd_conceal_utf8.vim
index 7bfac13ad8..1d0c740734 100644
--- a/src/nvim/testdir/test_matchadd_conceal_utf8.vim
+++ b/src/nvim/testdir/test_matchadd_conceal_utf8.vim
@@ -3,19 +3,19 @@ if !has('conceal')
finish
endif
-function! s:screenline(lnum) abort
+func s:screenline(lnum) abort
let line = []
for c in range(1, winwidth(0))
call add(line, nr2char(a:lnum->screenchar(c)))
endfor
return s:trim(join(line, ''))
-endfunction
+endfunc
-function! s:trim(str) abort
+func s:trim(str) abort
return matchstr(a:str,'^\s*\zs.\{-}\ze\s*$')
-endfunction
+endfunc
-function! Test_match_using_multibyte_conceal_char()
+func Test_match_using_multibyte_conceal_char()
new
setlocal concealcursor=n conceallevel=1
diff --git a/src/nvim/testdir/test_matchfuzzy.vim b/src/nvim/testdir/test_matchfuzzy.vim
new file mode 100644
index 0000000000..abcc9b40c1
--- /dev/null
+++ b/src/nvim/testdir/test_matchfuzzy.vim
@@ -0,0 +1,248 @@
+" Tests for fuzzy matching
+
+source shared.vim
+source check.vim
+
+" Test for matchfuzzy()
+func Test_matchfuzzy()
+ call assert_fails('call matchfuzzy(10, "abc")', 'E686:')
+ " Needs v8.2.1183; match the final error that's thrown for now
+ " call assert_fails('call matchfuzzy(["abc"], [])', 'E730:')
+ call assert_fails('call matchfuzzy(["abc"], [])', 'E475:')
+ call assert_fails("let x = matchfuzzy(v:_null_list, 'foo')", 'E686:')
+ call assert_fails('call matchfuzzy(["abc"], v:_null_string)', 'E475:')
+ call assert_equal([], matchfuzzy([], 'abc'))
+ call assert_equal([], matchfuzzy(['abc'], ''))
+ call assert_equal(['abc'], matchfuzzy(['abc', 10], 'ac'))
+ call assert_equal([], matchfuzzy([10, 20], 'ac'))
+ call assert_equal(['abc'], matchfuzzy(['abc'], 'abc'))
+ call assert_equal(['crayon', 'camera'], matchfuzzy(['camera', 'crayon'], 'cra'))
+ call assert_equal(['aabbaa', 'aaabbbaaa', 'aaaabbbbaaaa', 'aba'], matchfuzzy(['aba', 'aabbaa', 'aaabbbaaa', 'aaaabbbbaaaa'], 'aa'))
+ call assert_equal(['one'], matchfuzzy(['one', 'two'], 'one'))
+ call assert_equal(['oneTwo', 'onetwo'], matchfuzzy(['onetwo', 'oneTwo'], 'oneTwo'))
+ call assert_equal(['onetwo', 'one_two'], matchfuzzy(['onetwo', 'one_two'], 'oneTwo'))
+ call assert_equal(['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'], matchfuzzy(['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'], 'aa'))
+ call assert_equal(256, matchfuzzy([repeat('a', 256)], repeat('a', 256))[0]->len())
+ call assert_equal([], matchfuzzy([repeat('a', 300)], repeat('a', 257)))
+ " matches with same score should not be reordered
+ let l = ['abc1', 'abc2', 'abc3']
+ call assert_equal(l, l->matchfuzzy('abc'))
+
+ " Tests for match preferences
+ " preference for camel case match
+ call assert_equal(['oneTwo', 'onetwo'], ['onetwo', 'oneTwo']->matchfuzzy('onetwo'))
+ " preference for match after a separator (_ or space)
+ call assert_equal(['onetwo', 'one_two', 'one two'], ['onetwo', 'one_two', 'one two']->matchfuzzy('onetwo'))
+ " preference for leading letter match
+ call assert_equal(['onetwo', 'xonetwo'], ['xonetwo', 'onetwo']->matchfuzzy('onetwo'))
+ " preference for sequential match
+ call assert_equal(['onetwo', 'oanbectdweo'], ['oanbectdweo', 'onetwo']->matchfuzzy('onetwo'))
+ " non-matching leading letter(s) penalty
+ call assert_equal(['xonetwo', 'xxonetwo'], ['xxonetwo', 'xonetwo']->matchfuzzy('onetwo'))
+ " total non-matching letter(s) penalty
+ call assert_equal(['one', 'onex', 'onexx'], ['onexx', 'one', 'onex']->matchfuzzy('one'))
+ " prefer complete matches over separator matches
+ call assert_equal(['.vim/vimrc', '.vim/vimrc_colors', '.vim/v_i_m_r_c'], ['.vim/vimrc', '.vim/vimrc_colors', '.vim/v_i_m_r_c']->matchfuzzy('vimrc'))
+ " gap penalty
+ call assert_equal(['xxayybxxxx', 'xxayyybxxx', 'xxayyyybxx'], ['xxayyyybxx', 'xxayyybxxx', 'xxayybxxxx']->matchfuzzy('ab'))
+ " path separator vs word separator
+ call assert_equal(['color/setup.vim', 'color\\setup.vim', 'color setup.vim', 'color_setup.vim', 'colorsetup.vim'], matchfuzzy(['colorsetup.vim', 'color setup.vim', 'color/setup.vim', 'color_setup.vim', 'color\\setup.vim'], 'setup.vim'))
+
+ " match multiple words (separated by space)
+ call assert_equal(['foo bar baz'], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzy('baz foo'))
+ call assert_equal([], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzy('one two'))
+ call assert_equal([], ['foo bar']->matchfuzzy(" \t "))
+
+ " test for matching a sequence of words
+ call assert_equal(['bar foo'], ['foo bar', 'bar foo', 'foobar', 'barfoo']->matchfuzzy('bar foo', {'matchseq' : 1}))
+ call assert_equal([#{text: 'two one'}], [#{text: 'one two'}, #{text: 'two one'}]->matchfuzzy('two one', #{key: 'text', matchseq: v:true}))
+
+ %bw!
+ eval ['somebuf', 'anotherone', 'needle', 'yetanotherone']->map({_, v -> bufadd(v) + bufload(v)})
+ let l = getbufinfo()->map({_, v -> v.name})->matchfuzzy('ndl')
+ call assert_equal(1, len(l))
+ call assert_match('needle', l[0])
+
+ " Test for fuzzy matching dicts
+ let l = [{'id' : 5, 'val' : 'crayon'}, {'id' : 6, 'val' : 'camera'}]
+ call assert_equal([{'id' : 6, 'val' : 'camera'}], matchfuzzy(l, 'cam', {'text_cb' : {v -> v.val}}))
+ call assert_equal([{'id' : 6, 'val' : 'camera'}], matchfuzzy(l, 'cam', {'key' : 'val'}))
+ call assert_equal([], matchfuzzy(l, 'day', {'text_cb' : {v -> v.val}}))
+ call assert_equal([], matchfuzzy(l, 'day', {'key' : 'val'}))
+ call assert_fails("let x = matchfuzzy(l, 'cam', 'random')", 'E715:')
+ call assert_equal([], matchfuzzy(l, 'day', {'text_cb' : {v -> []}}))
+ call assert_equal([], matchfuzzy(l, 'day', {'text_cb' : {v -> 1}}))
+ call assert_fails("let x = matchfuzzy(l, 'day', {'text_cb' : {a, b -> 1}})", 'E119:')
+ call assert_equal([], matchfuzzy(l, 'cam'))
+ " Nvim's callback implementation is different, so E6000 is expected instead,
+ " but we need v8.2.1183 to assert it
+ " call assert_fails("let x = matchfuzzy(l, 'cam', {'text_cb' : []})", 'E921:')
+ " call assert_fails("let x = matchfuzzy(l, 'cam', {'text_cb' : []})", 'E6000:')
+ call assert_fails("let x = matchfuzzy(l, 'cam', {'text_cb' : []})", 'E475:')
+ " call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : []})", 'E730:')
+ call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : []})", 'E475:')
+ call assert_fails("let x = matchfuzzy(l, 'cam', v:_null_dict)", 'E715:')
+ call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : v:_null_string})", 'E475:')
+ " Nvim doesn't have null functions
+ " call assert_fails("let x = matchfuzzy(l, 'foo', {'text_cb' : test_null_function()})", 'E475:')
+ " matches with same score should not be reordered
+ let l = [#{text: 'abc', id: 1}, #{text: 'abc', id: 2}, #{text: 'abc', id: 3}]
+ call assert_equal(l, l->matchfuzzy('abc', #{key: 'text'}))
+
+ let l = [{'id' : 5, 'name' : 'foo'}, {'id' : 6, 'name' : []}, {'id' : 7}]
+ call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : 'name'})", 'E730:')
+
+ " Test in latin1 encoding
+ let save_enc = &encoding
+ " Nvim supports utf-8 encoding only
+ " set encoding=latin1
+ call assert_equal(['abc'], matchfuzzy(['abc'], 'abc'))
+ let &encoding = save_enc
+endfunc
+
+" Test for the matchfuzzypos() function
+func Test_matchfuzzypos()
+ call assert_equal([['curl', 'world'], [[2,3], [2,3]], [128, 127]], matchfuzzypos(['world', 'curl'], 'rl'))
+ call assert_equal([['curl', 'world'], [[2,3], [2,3]], [128, 127]], matchfuzzypos(['world', 'one', 'curl'], 'rl'))
+ call assert_equal([['hello', 'hello world hello world'],
+ \ [[0, 1, 2, 3, 4], [0, 1, 2, 3, 4]], [275, 257]],
+ \ matchfuzzypos(['hello world hello world', 'hello', 'world'], 'hello'))
+ call assert_equal([['aaaaaaa'], [[0, 1, 2]], [191]], matchfuzzypos(['aaaaaaa'], 'aaa'))
+ call assert_equal([['a b'], [[0, 3]], [219]], matchfuzzypos(['a b'], 'a b'))
+ call assert_equal([['a b'], [[0, 3]], [219]], matchfuzzypos(['a b'], 'a b'))
+ call assert_equal([['a b'], [[0]], [112]], matchfuzzypos(['a b'], ' a '))
+ call assert_equal([[], [], []], matchfuzzypos(['a b'], ' '))
+ call assert_equal([[], [], []], matchfuzzypos(['world', 'curl'], 'ab'))
+ let x = matchfuzzypos([repeat('a', 256)], repeat('a', 256))
+ call assert_equal(range(256), x[1][0])
+ call assert_equal([[], [], []], matchfuzzypos([repeat('a', 300)], repeat('a', 257)))
+ call assert_equal([[], [], []], matchfuzzypos([], 'abc'))
+
+ " match in a long string
+ call assert_equal([[repeat('x', 300) .. 'abc'], [[300, 301, 302]], [-135]],
+ \ matchfuzzypos([repeat('x', 300) .. 'abc'], 'abc'))
+
+ " preference for camel case match
+ call assert_equal([['xabcxxaBc'], [[6, 7, 8]], [189]], matchfuzzypos(['xabcxxaBc'], 'abc'))
+ " preference for match after a separator (_ or space)
+ call assert_equal([['xabx_ab'], [[5, 6]], [145]], matchfuzzypos(['xabx_ab'], 'ab'))
+ " preference for leading letter match
+ call assert_equal([['abcxabc'], [[0, 1]], [150]], matchfuzzypos(['abcxabc'], 'ab'))
+ " preference for sequential match
+ call assert_equal([['aobncedone'], [[7, 8, 9]], [158]], matchfuzzypos(['aobncedone'], 'one'))
+ " best recursive match
+ call assert_equal([['xoone'], [[2, 3, 4]], [168]], matchfuzzypos(['xoone'], 'one'))
+
+ " match multiple words (separated by space)
+ call assert_equal([['foo bar baz'], [[8, 9, 10, 0, 1, 2]], [369]], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('baz foo'))
+ call assert_equal([[], [], []], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('one two'))
+ call assert_equal([[], [], []], ['foo bar']->matchfuzzypos(" \t "))
+ call assert_equal([['grace'], [[1, 2, 3, 4, 2, 3, 4, 0, 1, 2, 3, 4]], [657]], ['grace']->matchfuzzypos('race ace grace'))
+
+ let l = [{'id' : 5, 'val' : 'crayon'}, {'id' : 6, 'val' : 'camera'}]
+ call assert_equal([[{'id' : 6, 'val' : 'camera'}], [[0, 1, 2]], [192]],
+ \ matchfuzzypos(l, 'cam', {'text_cb' : {v -> v.val}}))
+ call assert_equal([[{'id' : 6, 'val' : 'camera'}], [[0, 1, 2]], [192]],
+ \ matchfuzzypos(l, 'cam', {'key' : 'val'}))
+ call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'text_cb' : {v -> v.val}}))
+ call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'key' : 'val'}))
+ call assert_fails("let x = matchfuzzypos(l, 'cam', 'random')", 'E715:')
+ call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'text_cb' : {v -> []}}))
+ call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'text_cb' : {v -> 1}}))
+ call assert_fails("let x = matchfuzzypos(l, 'day', {'text_cb' : {a, b -> 1}})", 'E119:')
+ call assert_equal([[], [], []], matchfuzzypos(l, 'cam'))
+ " Nvim's callback implementation is different, so E6000 is expected instead,
+ " but we need v8.2.1183 to assert it
+ " call assert_fails("let x = matchfuzzypos(l, 'cam', {'text_cb' : []})", 'E921:')
+ " call assert_fails("let x = matchfuzzypos(l, 'cam', {'text_cb' : []})", 'E6000:')
+ call assert_fails("let x = matchfuzzypos(l, 'cam', {'text_cb' : []})", 'E475:')
+ " call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : []})", 'E730:')
+ call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : []})", 'E475:')
+ call assert_fails("let x = matchfuzzypos(l, 'cam', v:_null_dict)", 'E715:')
+ call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : v:_null_string})", 'E475:')
+ " Nvim doesn't have null functions
+ " call assert_fails("let x = matchfuzzypos(l, 'foo', {'text_cb' : test_null_function()})", 'E475:')
+
+ let l = [{'id' : 5, 'name' : 'foo'}, {'id' : 6, 'name' : []}, {'id' : 7}]
+ call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : 'name'})", 'E730:')
+endfunc
+
+" Test for matchfuzzy() with multibyte characters
+func Test_matchfuzzy_mbyte()
+ CheckFeature multi_lang
+ call assert_equal(['ンヹㄇヺヴ'], matchfuzzy(['ンヹㄇヺヴ'], 'ヹヺ'))
+ " reverse the order of characters
+ call assert_equal([], matchfuzzy(['ンヹㄇヺヴ'], 'ヺヹ'))
+ call assert_equal(['αβΩxxx', 'xαxβxΩx'],
+ \ matchfuzzy(['αβΩxxx', 'xαxβxΩx'], 'αβΩ'))
+ call assert_equal(['ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ', 'πbπ'],
+ \ matchfuzzy(['πbπ', 'ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ'], 'ππ'))
+
+ " match multiple words (separated by space)
+ call assert_equal(['세 마리의 작은 돼지'], ['세 마리의 작은 돼지', '마리의', '마리의 작은', '작은 돼지']->matchfuzzy('돼지 마리의'))
+ call assert_equal([], ['세 마리의 작은 돼지', '마리의', '마리의 작은', '작은 돼지']->matchfuzzy('파란 하늘'))
+
+ " preference for camel case match
+ call assert_equal(['oneĄwo', 'oneąwo'],
+ \ ['oneąwo', 'oneĄwo']->matchfuzzy('oneąwo'))
+ " preference for complete match then match after separator (_ or space)
+ call assert_equal(['ⅠⅡabㄟㄠ'] + sort(['ⅠⅡa_bㄟㄠ', 'ⅠⅡa bㄟㄠ']),
+ \ ['ⅠⅡabㄟㄠ', 'ⅠⅡa bㄟㄠ', 'ⅠⅡa_bㄟㄠ']->matchfuzzy('ⅠⅡabㄟㄠ'))
+ " preference for match after a separator (_ or space)
+ call assert_equal(['ㄓㄔabㄟㄠ', 'ㄓㄔa_bㄟㄠ', 'ㄓㄔa bㄟㄠ'],
+ \ ['ㄓㄔa_bㄟㄠ', 'ㄓㄔa bㄟㄠ', 'ㄓㄔabㄟㄠ']->matchfuzzy('ㄓㄔabㄟㄠ'))
+ " preference for leading letter match
+ call assert_equal(['ŗŝţũŵż', 'xŗŝţũŵż'],
+ \ ['xŗŝţũŵż', 'ŗŝţũŵż']->matchfuzzy('ŗŝţũŵż'))
+ " preference for sequential match
+ call assert_equal(['ㄞㄡㄤfffifl', 'ㄞaㄡbㄤcffdfiefl'],
+ \ ['ㄞaㄡbㄤcffdfiefl', 'ㄞㄡㄤfffifl']->matchfuzzy('ㄞㄡㄤfffifl'))
+ " non-matching leading letter(s) penalty
+ call assert_equal(['xㄞㄡㄤfffifl', 'xxㄞㄡㄤfffifl'],
+ \ ['xxㄞㄡㄤfffifl', 'xㄞㄡㄤfffifl']->matchfuzzy('ㄞㄡㄤfffifl'))
+ " total non-matching letter(s) penalty
+ call assert_equal(['ŗŝţ', 'ŗŝţx', 'ŗŝţxx'],
+ \ ['ŗŝţxx', 'ŗŝţ', 'ŗŝţx']->matchfuzzy('ŗŝţ'))
+endfunc
+
+" Test for matchfuzzypos() with multibyte characters
+func Test_matchfuzzypos_mbyte()
+ CheckFeature multi_lang
+ call assert_equal([['こんにちは世界'], [[0, 1, 2, 3, 4]], [273]],
+ \ matchfuzzypos(['こんにちは世界'], 'こんにちは'))
+ call assert_equal([['ンヹㄇヺヴ'], [[1, 3]], [88]], matchfuzzypos(['ンヹㄇヺヴ'], 'ヹヺ'))
+ " reverse the order of characters
+ call assert_equal([[], [], []], matchfuzzypos(['ンヹㄇヺヴ'], 'ヺヹ'))
+ call assert_equal([['αβΩxxx', 'xαxβxΩx'], [[0, 1, 2], [1, 3, 5]], [222, 113]],
+ \ matchfuzzypos(['αβΩxxx', 'xαxβxΩx'], 'αβΩ'))
+ call assert_equal([['ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ', 'πbπ'],
+ \ [[0, 1], [0, 1], [0, 1], [0, 2]], [151, 148, 145, 110]],
+ \ matchfuzzypos(['πbπ', 'ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ'], 'ππ'))
+ call assert_equal([['ααααααα'], [[0, 1, 2]], [191]],
+ \ matchfuzzypos(['ααααααα'], 'ααα'))
+
+ call assert_equal([[], [], []], matchfuzzypos(['ンヹㄇ', 'ŗŝţ'], 'fffifl'))
+ let x = matchfuzzypos([repeat('Ψ', 256)], repeat('Ψ', 256))
+ call assert_equal(range(256), x[1][0])
+ call assert_equal([[], [], []], matchfuzzypos([repeat('✓', 300)], repeat('✓', 257)))
+
+ " match multiple words (separated by space)
+ call assert_equal([['세 마리의 작은 돼지'], [[9, 10, 2, 3, 4]], [328]], ['세 마리의 작은 돼지', '마리의', '마리의 작은', '작은 돼지']->matchfuzzypos('돼지 마리의'))
+ call assert_equal([[], [], []], ['세 마리의 작은 돼지', '마리의', '마리의 작은', '작은 돼지']->matchfuzzypos('파란 하늘'))
+
+ " match in a long string
+ call assert_equal([[repeat('ぶ', 300) .. 'ẼẼẼ'], [[300, 301, 302]], [-135]],
+ \ matchfuzzypos([repeat('ぶ', 300) .. 'ẼẼẼ'], 'ẼẼẼ'))
+ " preference for camel case match
+ call assert_equal([['xѳѵҁxxѳѴҁ'], [[6, 7, 8]], [189]], matchfuzzypos(['xѳѵҁxxѳѴҁ'], 'ѳѵҁ'))
+ " preference for match after a separator (_ or space)
+ call assert_equal([['xちだx_ちだ'], [[5, 6]], [145]], matchfuzzypos(['xちだx_ちだ'], 'ちだ'))
+ " preference for leading letter match
+ call assert_equal([['ѳѵҁxѳѵҁ'], [[0, 1]], [150]], matchfuzzypos(['ѳѵҁxѳѵҁ'], 'ѳѵ'))
+ " preference for sequential match
+ call assert_equal([['aンbヹcㄇdンヹㄇ'], [[7, 8, 9]], [158]], matchfuzzypos(['aンbヹcㄇdンヹㄇ'], 'ンヹㄇ'))
+ " best recursive match
+ call assert_equal([['xффйд'], [[2, 3, 4]], [168]], matchfuzzypos(['xффйд'], 'фйд'))
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_messages.vim b/src/nvim/testdir/test_messages.vim
index e0286548d9..9c84d77dd2 100644
--- a/src/nvim/testdir/test_messages.vim
+++ b/src/nvim/testdir/test_messages.vim
@@ -40,7 +40,7 @@ endfunc
" indicator (e.g., "-- INSERT --") when ":stopinsert" is invoked. Message
" output could then be disturbed when 'cmdheight' was greater than one.
" This test ensures that the bugfix for this issue remains in place.
-function! Test_stopinsert_does_not_break_message_output()
+func Test_stopinsert_does_not_break_message_output()
set cmdheight=2
redraw!
@@ -55,7 +55,7 @@ function! Test_stopinsert_does_not_break_message_output()
redraw!
set cmdheight&
-endfunction
+endfunc
func Test_message_completion()
call feedkeys(":message \<C-A>\<C-B>\"\<CR>", 'tx')
diff --git a/src/nvim/testdir/test_mksession.vim b/src/nvim/testdir/test_mksession.vim
index 057895047d..a8e50af510 100644
--- a/src/nvim/testdir/test_mksession.vim
+++ b/src/nvim/testdir/test_mksession.vim
@@ -309,6 +309,31 @@ func Test_mksession_buffer_count()
set nohidden
endfunc
+func Test_mksession_buffer_order()
+ %bwipe!
+ e Xfoo | e Xbar | e Xbaz | e Xqux
+ bufdo write
+ mksession! Xtest_mks.out
+
+ " Verify that loading the session preserves order of buffers
+ %bwipe!
+ source Xtest_mks.out
+
+ let s:buf_info = getbufinfo()
+ call assert_true(s:buf_info[0]['name'] =~# 'Xfoo$')
+ call assert_true(s:buf_info[1]['name'] =~# 'Xbar$')
+ call assert_true(s:buf_info[2]['name'] =~# 'Xbaz$')
+ call assert_true(s:buf_info[3]['name'] =~# 'Xqux$')
+
+ " Clean up.
+ call delete('Xfoo')
+ call delete('Xbar')
+ call delete('Xbaz')
+ call delete('Xqux')
+ call delete('Xtest_mks.out')
+ %bwipe!
+endfunc
+
if has('extra_search')
func Test_mksession_hlsearch()
@@ -696,6 +721,36 @@ func Test_mksession_foldopt()
set sessionoptions&
endfunc
+" Test for mksession with "help" but not "options" in 'sessionoptions'
+func Test_mksession_help_noopt()
+ set sessionoptions-=options
+ set sessionoptions+=help
+ help
+ let fname = expand('%')
+ mksession! Xtest_mks.out
+ bwipe
+
+ source Xtest_mks.out
+ call assert_equal('help', &buftype)
+ call assert_equal('help', &filetype)
+ call assert_equal(fname, expand('%'))
+ call assert_false(&modifiable)
+ call assert_true(&readonly)
+
+ helpclose
+ help index
+ let fname = expand('%')
+ mksession! Xtest_mks.out
+ bwipe
+
+ source Xtest_mks.out
+ call assert_equal('help', &buftype)
+ call assert_equal(fname, expand('%'))
+
+ call delete('Xtest_mks.out')
+ set sessionoptions&
+endfunc
+
" Test for mksession with window position
func Test_mksession_winpos()
if !has('gui_running')
diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim
index aff22f5d01..f45cd96733 100644
--- a/src/nvim/testdir/test_normal.vim
+++ b/src/nvim/testdir/test_normal.vim
@@ -1118,7 +1118,7 @@ func Test_normal20_exmode()
endif
call writefile(['1a', 'foo', 'bar', '.', 'w! Xfile2', 'q!'], 'Xscript')
call writefile(['1', '2'], 'Xfile')
- call system(v:progpath .' -e -s < Xscript Xfile')
+ call system(GetVimCommand() .. ' -e -s < Xscript Xfile')
let a=readfile('Xfile2')
call assert_equal(['1', 'foo', 'bar', '2'], a)
@@ -1171,13 +1171,13 @@ func Test_normal22_zet()
endfor
call writefile(['1', '2'], 'Xfile_Test_normal22_zet')
- let args = ' --headless -u NONE -N -U NONE -i NONE --noplugins'
- call system(v:progpath . args . ' -c "%d" -c ":norm! ZZ" Xfile_Test_normal22_zet')
+ let args = ' -N -i NONE --noplugins -X --headless'
+ call system(GetVimCommand() .. args .. ' -c "%d" -c ":norm! ZZ" Xfile_Test_normal22_zet')
let a = readfile('Xfile_Test_normal22_zet')
call assert_equal([], a)
" Test for ZQ
call writefile(['1', '2'], 'Xfile_Test_normal22_zet')
- call system(v:progpath . args . ' -c "%d" -c ":norm! ZQ" Xfile_Test_normal22_zet')
+ call system(GetVimCommand() . args . ' -c "%d" -c ":norm! ZQ" Xfile_Test_normal22_zet')
let a = readfile('Xfile_Test_normal22_zet')
call assert_equal(['1', '2'], a)
@@ -1812,7 +1812,15 @@ fun! Test_normal33_g_cmd2()
call assert_equal(87, col('.'))
call assert_equal('E', getreg(0))
+ " Test for gM with Tab characters
+ call setline('.', "\ta\tb\tc\td\te\tf")
+ norm! gMyl
+ call assert_equal(6, col('.'))
+ call assert_equal("c", getreg(0))
+
" Test for g Ctrl-G
+ call setline('.', lineC)
+ norm! 60gMyl
set ff=unix
let a=execute(":norm! g\<c-g>")
call assert_match('Col 87 of 144; Line 2 of 2; Word 1 of 1; Byte 88 of 146', a)
@@ -2759,4 +2767,37 @@ func Test_normal_count_after_operator()
bw!
endfunc
+func Test_normal_gj_on_extra_wide_char()
+ new | 25vsp
+ let text='1 foooooooo ar e ins‍zwe1 foooooooo ins‍zwei' .
+ \ ' i drei vier fünf sechs sieben acht un zehn elf zwöfl' .
+ \ ' dreizehn v ierzehn fünfzehn'
+ put =text
+ call cursor(2,1)
+ norm! gj
+ call assert_equal([0,2,25,0], getpos('.'))
+ bw!
+endfunc
+
+func Test_normal_count_out_of_range()
+ new
+ call setline(1, 'text')
+ normal 44444444444|
+ call assert_equal(999999999, v:count)
+ normal 444444444444|
+ call assert_equal(999999999, v:count)
+ normal 4444444444444|
+ call assert_equal(999999999, v:count)
+ normal 4444444444444444444|
+ call assert_equal(999999999, v:count)
+
+ normal 9y99999999|
+ call assert_equal(899999991, v:count)
+ normal 10y99999999|
+ call assert_equal(999999999, v:count)
+ normal 44444444444y44444444444|
+ call assert_equal(999999999, v:count)
+ bwipe!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_number.vim b/src/nvim/testdir/test_number.vim
index d737ebe9f0..dfbdc0bffd 100644
--- a/src/nvim/testdir/test_number.vim
+++ b/src/nvim/testdir/test_number.vim
@@ -284,10 +284,10 @@ func Test_relativenumber_colors()
" Default colors
call VerifyScreenDump(buf, 'Test_relnr_colors_1', {})
- call term_sendkeys(buf, ":hi LineNrAbove ctermfg=blue\<CR>")
+ call term_sendkeys(buf, ":hi LineNrAbove ctermfg=blue\<CR>:\<CR>")
call VerifyScreenDump(buf, 'Test_relnr_colors_2', {})
- call term_sendkeys(buf, ":hi LineNrBelow ctermfg=green\<CR>")
+ call term_sendkeys(buf, ":hi LineNrBelow ctermfg=green\<CR>:\<CR>")
call VerifyScreenDump(buf, 'Test_relnr_colors_3', {})
call term_sendkeys(buf, ":hi clear LineNrAbove\<CR>")
diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim
index 5946732937..8612b7013b 100644
--- a/src/nvim/testdir/test_options.vim
+++ b/src/nvim/testdir/test_options.vim
@@ -22,16 +22,16 @@ func Test_whichwrap()
call assert_equal('h', &whichwrap)
set whichwrap&
-endfunction
+endfunc
-function! Test_isfname()
+func Test_isfname()
" This used to cause Vim to access uninitialized memory.
set isfname=
call assert_equal("~X", expand("~X"))
set isfname&
-endfunction
+endfunc
-function Test_wildchar()
+func Test_wildchar()
" Empty 'wildchar' used to access invalid memory.
call assert_fails('set wildchar=', 'E521:')
call assert_fails('set wildchar=abc', 'E521:')
@@ -42,7 +42,7 @@ function Test_wildchar()
let a=execute('set wildchar?')
call assert_equal("\n wildchar=<Esc>", a)
set wildchar&
-endfunction
+endfunc
func Test_wildoptions()
set wildoptions=
@@ -51,7 +51,7 @@ func Test_wildoptions()
call assert_equal('tagfile', &wildoptions)
endfunc
-function! Test_options()
+func Test_options_command()
let caught = 'ok'
try
options
@@ -88,9 +88,9 @@ function! Test_options()
" close option-window
close
-endfunction
+endfunc
-function! Test_path_keep_commas()
+func Test_path_keep_commas()
" Test that changing 'path' keeps two commas.
set path=foo,,bar
set path-=bar
@@ -98,7 +98,7 @@ function! Test_path_keep_commas()
call assert_equal('foo,,bar', &path)
set path&
-endfunction
+endfunc
func Test_filetype_valid()
set ft=valid_name
@@ -259,6 +259,8 @@ func Test_set_errors()
call assert_fails('set shiftwidth=-1', 'E487:')
call assert_fails('set sidescroll=-1', 'E487:')
call assert_fails('set tabstop=-1', 'E487:')
+ call assert_fails('set tabstop=10000', 'E474:')
+ call assert_fails('set tabstop=5500000000', 'E474:')
call assert_fails('set textwidth=-1', 'E487:')
call assert_fails('set timeoutlen=-1', 'E487:')
call assert_fails('set updatecount=-1', 'E487:')
@@ -312,21 +314,50 @@ func Test_set_errors()
set modifiable&
endfunc
+func CheckWasSet(name)
+ let verb_cm = execute('verbose set ' .. a:name .. '?')
+ call assert_match('Last set from.*test_options.vim', verb_cm)
+endfunc
+func CheckWasNotSet(name)
+ let verb_cm = execute('verbose set ' .. a:name .. '?')
+ call assert_notmatch('Last set from', verb_cm)
+endfunc
+
" Must be executed before other tests that set 'term'.
func Test_000_term_option_verbose()
if has('nvim') || has('gui_running')
return
endif
- let verb_cm = execute('verbose set t_cm')
- call assert_notmatch('Last set from', verb_cm)
+
+ call CheckWasNotSet('t_cm')
let term_save = &term
set term=ansi
- let verb_cm = execute('verbose set t_cm')
- call assert_match('Last set from.*test_options.vim', verb_cm)
+ call CheckWasSet('t_cm')
let &term = term_save
endfunc
+func Test_copy_context()
+ setlocal list
+ call CheckWasSet('list')
+ split
+ call CheckWasSet('list')
+ quit
+ setlocal nolist
+
+ set ai
+ call CheckWasSet('ai')
+ set filetype=perl
+ call CheckWasSet('filetype')
+ set fo=tcroq
+ call CheckWasSet('fo')
+
+ split Xsomebuf
+ call CheckWasSet('ai')
+ call CheckWasNotSet('filetype')
+ call CheckWasSet('fo')
+endfunc
+
func Test_set_ttytype()
" Nvim does not support 'ttytype'.
if !has('nvim') && !has('gui_running') && has('unix')
@@ -368,6 +399,13 @@ func Test_set_all()
set tw& iskeyword& splitbelow&
endfunc
+func Test_set_one_column()
+ let out_mult = execute('set all')->split("\n")
+ let out_one = execute('set! all')->split("\n")
+ " one column should be two to four times as many lines
+ call assert_inrange(len(out_mult) * 2, len(out_mult) * 4, len(out_one))
+endfunc
+
func Test_set_values()
" The file is only generated when running "make test" in the src directory.
if filereadable('opt_test.vim')
@@ -647,6 +685,19 @@ func Test_buftype()
close!
endfunc
+" Test for the 'shellquote' option
+func Test_shellquote()
+ CheckUnix
+ set shellquote=#
+ set verbose=20
+ redir => v
+ silent! !echo Hello
+ redir END
+ set verbose&
+ set shellquote&
+ call assert_match(': "#echo Hello#"', v)
+endfunc
+
" Test for setting option values using v:false and v:true
func Test_opt_boolean()
set number&
diff --git a/src/nvim/testdir/test_packadd.vim b/src/nvim/testdir/test_packadd.vim
new file mode 100644
index 0000000000..fcb8b8033b
--- /dev/null
+++ b/src/nvim/testdir/test_packadd.vim
@@ -0,0 +1,361 @@
+" Tests for 'packpath' and :packadd
+
+
+func SetUp()
+ let s:topdir = getcwd() . '/Xdir'
+ exe 'set packpath=' . s:topdir
+ let s:plugdir = s:topdir . '/pack/mine/opt/mytest'
+endfunc
+
+func TearDown()
+ call delete(s:topdir, 'rf')
+endfunc
+
+func Test_packadd()
+ if !exists('s:plugdir')
+ echomsg 'when running this test manually, call SetUp() first'
+ return
+ endif
+
+ call mkdir(s:plugdir . '/plugin/also', 'p')
+ call mkdir(s:plugdir . '/ftdetect', 'p')
+ call mkdir(s:plugdir . '/after', 'p')
+ set rtp&
+ let rtp = &rtp
+ filetype on
+
+ let rtp_entries = split(rtp, ',')
+ for entry in rtp_entries
+ if entry =~? '\<after\>'
+ let first_after_entry = entry
+ break
+ endif
+ endfor
+
+ exe 'split ' . s:plugdir . '/plugin/test.vim'
+ call setline(1, 'let g:plugin_works = 42')
+ wq
+
+ exe 'split ' . s:plugdir . '/plugin/also/loaded.vim'
+ call setline(1, 'let g:plugin_also_works = 77')
+ wq
+
+ exe 'split ' . s:plugdir . '/ftdetect/test.vim'
+ call setline(1, 'let g:ftdetect_works = 17')
+ wq
+
+ packadd mytest
+
+ call assert_equal(42, g:plugin_works)
+ call assert_equal(77, g:plugin_also_works)
+ call assert_equal(17, g:ftdetect_works)
+ call assert_true(len(&rtp) > len(rtp))
+ call assert_match('/testdir/Xdir/pack/mine/opt/mytest\($\|,\)', &rtp)
+
+ let new_after = match(&rtp, '/testdir/Xdir/pack/mine/opt/mytest/after,')
+ let forwarded = substitute(first_after_entry, '\\', '[/\\\\]', 'g')
+ let old_after = match(&rtp, ',' . forwarded . '\>')
+ call assert_true(new_after > 0, 'rtp is ' . &rtp)
+ call assert_true(old_after > 0, 'match ' . forwarded . ' in ' . &rtp)
+ call assert_true(new_after < old_after, 'rtp is ' . &rtp)
+
+ " NOTE: '/.../opt/myte' forwardly matches with '/.../opt/mytest'
+ call mkdir(fnamemodify(s:plugdir, ':h') . '/myte', 'p')
+ let rtp = &rtp
+ packadd myte
+
+ " Check the path of 'myte' is added
+ call assert_true(len(&rtp) > len(rtp))
+ call assert_match('/testdir/Xdir/pack/mine/opt/myte\($\|,\)', &rtp)
+
+ " Check exception
+ call assert_fails("packadd directorynotfound", 'E919:')
+ call assert_fails("packadd", 'E471:')
+endfunc
+
+func Test_packadd_start()
+ let plugdir = s:topdir . '/pack/mine/start/other'
+ call mkdir(plugdir . '/plugin', 'p')
+ set rtp&
+ let rtp = &rtp
+ filetype on
+
+ exe 'split ' . plugdir . '/plugin/test.vim'
+ call setline(1, 'let g:plugin_works = 24')
+ wq
+
+ packadd other
+
+ call assert_equal(24, g:plugin_works)
+ call assert_true(len(&rtp) > len(rtp))
+ call assert_match('/testdir/Xdir/pack/mine/start/other\($\|,\)', &rtp)
+endfunc
+
+func Test_packadd_noload()
+ call mkdir(s:plugdir . '/plugin', 'p')
+ call mkdir(s:plugdir . '/syntax', 'p')
+ set rtp&
+ let rtp = &rtp
+
+ exe 'split ' . s:plugdir . '/plugin/test.vim'
+ call setline(1, 'let g:plugin_works = 42')
+ wq
+ let g:plugin_works = 0
+
+ packadd! mytest
+
+ call assert_true(len(&rtp) > len(rtp))
+ call assert_match('testdir/Xdir/pack/mine/opt/mytest\($\|,\)', &rtp)
+ call assert_equal(0, g:plugin_works)
+
+ " check the path is not added twice
+ let new_rtp = &rtp
+ packadd! mytest
+ call assert_equal(new_rtp, &rtp)
+endfunc
+
+func Test_packadd_symlink_dir()
+ if !has('unix')
+ return
+ endif
+ let top2_dir = s:topdir . '/Xdir2'
+ let real_dir = s:topdir . '/Xsym'
+ call mkdir(real_dir, 'p')
+ exec "silent !ln -s Xsym" top2_dir
+ let &rtp = top2_dir . ',' . top2_dir . '/after'
+ let &packpath = &rtp
+
+ let s:plugdir = top2_dir . '/pack/mine/opt/mytest'
+ call mkdir(s:plugdir . '/plugin', 'p')
+
+ exe 'split ' . s:plugdir . '/plugin/test.vim'
+ call setline(1, 'let g:plugin_works = 44')
+ wq
+ let g:plugin_works = 0
+
+ packadd mytest
+
+ " Must have been inserted in the middle, not at the end
+ call assert_match('/pack/mine/opt/mytest,', &rtp)
+ call assert_equal(44, g:plugin_works)
+
+ " No change when doing it again.
+ let rtp_before = &rtp
+ packadd mytest
+ call assert_equal(rtp_before, &rtp)
+
+ set rtp&
+ let rtp = &rtp
+ exec "silent !rm" top2_dir
+endfunc
+
+func Test_packadd_symlink_dir2()
+ if !has('unix')
+ return
+ endif
+ let top2_dir = s:topdir . '/Xdir2'
+ let real_dir = s:topdir . '/Xsym/pack'
+ call mkdir(top2_dir, 'p')
+ call mkdir(real_dir, 'p')
+ let &rtp = top2_dir . ',' . top2_dir . '/after'
+ let &packpath = &rtp
+
+ exec "silent !ln -s ../Xsym/pack" top2_dir . '/pack'
+ let s:plugdir = top2_dir . '/pack/mine/opt/mytest'
+ call mkdir(s:plugdir . '/plugin', 'p')
+
+ exe 'split ' . s:plugdir . '/plugin/test.vim'
+ call setline(1, 'let g:plugin_works = 48')
+ wq
+ let g:plugin_works = 0
+
+ packadd mytest
+
+ " Must have been inserted in the middle, not at the end
+ call assert_match('/Xdir2/pack/mine/opt/mytest,', &rtp)
+ call assert_equal(48, g:plugin_works)
+
+ " No change when doing it again.
+ let rtp_before = &rtp
+ packadd mytest
+ call assert_equal(rtp_before, &rtp)
+
+ set rtp&
+ let rtp = &rtp
+ exec "silent !rm" top2_dir . '/pack'
+ exec "silent !rmdir" top2_dir
+endfunc
+
+" Check command-line completion for 'packadd'
+func Test_packadd_completion()
+ let optdir1 = &packpath . '/pack/mine/opt'
+ let optdir2 = &packpath . '/pack/candidate/opt'
+
+ call mkdir(optdir1 . '/pluginA', 'p')
+ call mkdir(optdir1 . '/pluginC', 'p')
+ call mkdir(optdir2 . '/pluginB', 'p')
+ call mkdir(optdir2 . '/pluginC', 'p')
+
+ let li = []
+ call feedkeys(":packadd \<Tab>')\<C-B>call add(li, '\<CR>", 't')
+ call feedkeys(":packadd " . repeat("\<Tab>", 2) . "')\<C-B>call add(li, '\<CR>", 't')
+ call feedkeys(":packadd " . repeat("\<Tab>", 3) . "')\<C-B>call add(li, '\<CR>", 't')
+ call feedkeys(":packadd " . repeat("\<Tab>", 4) . "')\<C-B>call add(li, '\<CR>", 'tx')
+ call assert_equal("packadd pluginA", li[0])
+ call assert_equal("packadd pluginB", li[1])
+ call assert_equal("packadd pluginC", li[2])
+ call assert_equal("packadd ", li[3])
+endfunc
+
+func Test_packloadall()
+ " plugin foo with an autoload directory
+ let fooplugindir = &packpath . '/pack/mine/start/foo/plugin'
+ call mkdir(fooplugindir, 'p')
+ call writefile(['let g:plugin_foo_number = 1234',
+ \ 'let g:plugin_foo_auto = bbb#value',
+ \ 'let g:plugin_extra_auto = extra#value'], fooplugindir . '/bar.vim')
+ let fooautodir = &packpath . '/pack/mine/start/foo/autoload'
+ call mkdir(fooautodir, 'p')
+ call writefile(['let bar#value = 77'], fooautodir . '/bar.vim')
+
+ " plugin aaa with an autoload directory
+ let aaaplugindir = &packpath . '/pack/mine/start/aaa/plugin'
+ call mkdir(aaaplugindir, 'p')
+ call writefile(['let g:plugin_aaa_number = 333',
+ \ 'let g:plugin_aaa_auto = bar#value'], aaaplugindir . '/bbb.vim')
+ let aaaautodir = &packpath . '/pack/mine/start/aaa/autoload'
+ call mkdir(aaaautodir, 'p')
+ call writefile(['let bbb#value = 55'], aaaautodir . '/bbb.vim')
+
+ " plugin extra with only an autoload directory
+ let extraautodir = &packpath . '/pack/mine/start/extra/autoload'
+ call mkdir(extraautodir, 'p')
+ call writefile(['let extra#value = 99'], extraautodir . '/extra.vim')
+
+ packloadall
+ call assert_equal(1234, g:plugin_foo_number)
+ call assert_equal(55, g:plugin_foo_auto)
+ call assert_equal(99, g:plugin_extra_auto)
+ call assert_equal(333, g:plugin_aaa_number)
+ call assert_equal(77, g:plugin_aaa_auto)
+
+ " only works once
+ call writefile(['let g:plugin_bar_number = 4321'], fooplugindir . '/bar2.vim')
+ packloadall
+ call assert_false(exists('g:plugin_bar_number'))
+
+ " works when ! used
+ packloadall!
+ call assert_equal(4321, g:plugin_bar_number)
+endfunc
+
+func Test_helptags()
+ let docdir1 = &packpath . '/pack/mine/start/foo/doc'
+ let docdir2 = &packpath . '/pack/mine/start/bar/doc'
+ call mkdir(docdir1, 'p')
+ call mkdir(docdir2, 'p')
+ call writefile(['look here: *look-here*'], docdir1 . '/bar.txt')
+ call writefile(['look away: *look-away*'], docdir2 . '/foo.txt')
+ exe 'set rtp=' . &packpath . '/pack/mine/start/foo,' . &packpath . '/pack/mine/start/bar'
+
+ helptags ALL
+
+ let tags1 = readfile(docdir1 . '/tags')
+ call assert_match('look-here', tags1[0])
+ let tags2 = readfile(docdir2 . '/tags')
+ call assert_match('look-away', tags2[0])
+
+ call assert_fails('helptags abcxyz', 'E150:')
+endfunc
+
+func Test_colorscheme()
+ let colordirrun = &packpath . '/runtime/colors'
+ let colordirstart = &packpath . '/pack/mine/start/foo/colors'
+ let colordiropt = &packpath . '/pack/mine/opt/bar/colors'
+ call mkdir(colordirrun, 'p')
+ call mkdir(colordirstart, 'p')
+ call mkdir(colordiropt, 'p')
+ call writefile(['let g:found_one = 1'], colordirrun . '/one.vim')
+ call writefile(['let g:found_two = 1'], colordirstart . '/two.vim')
+ call writefile(['let g:found_three = 1'], colordiropt . '/three.vim')
+ exe 'set rtp=' . &packpath . '/runtime'
+
+ colorscheme one
+ call assert_equal(1, g:found_one)
+ colorscheme two
+ call assert_equal(1, g:found_two)
+ colorscheme three
+ call assert_equal(1, g:found_three)
+endfunc
+
+func Test_colorscheme_completion()
+ let colordirrun = &packpath . '/runtime/colors'
+ let colordirstart = &packpath . '/pack/mine/start/foo/colors'
+ let colordiropt = &packpath . '/pack/mine/opt/bar/colors'
+ call mkdir(colordirrun, 'p')
+ call mkdir(colordirstart, 'p')
+ call mkdir(colordiropt, 'p')
+ call writefile(['let g:found_one = 1'], colordirrun . '/one.vim')
+ call writefile(['let g:found_two = 1'], colordirstart . '/two.vim')
+ call writefile(['let g:found_three = 1'], colordiropt . '/three.vim')
+ exe 'set rtp=' . &packpath . '/runtime'
+
+ let li=[]
+ call feedkeys(":colorscheme " . repeat("\<Tab>", 1) . "')\<C-B>call add(li, '\<CR>", 't')
+ call feedkeys(":colorscheme " . repeat("\<Tab>", 2) . "')\<C-B>call add(li, '\<CR>", 't')
+ call feedkeys(":colorscheme " . repeat("\<Tab>", 3) . "')\<C-B>call add(li, '\<CR>", 't')
+ call feedkeys(":colorscheme " . repeat("\<Tab>", 4) . "')\<C-B>call add(li, '\<CR>", 'tx')
+ call assert_equal("colorscheme one", li[0])
+ call assert_equal("colorscheme three", li[1])
+ call assert_equal("colorscheme two", li[2])
+ call assert_equal("colorscheme ", li[3])
+endfunc
+
+func Test_runtime()
+ let rundir = &packpath . '/runtime/extra'
+ let startdir = &packpath . '/pack/mine/start/foo/extra'
+ let optdir = &packpath . '/pack/mine/opt/bar/extra'
+ call mkdir(rundir, 'p')
+ call mkdir(startdir, 'p')
+ call mkdir(optdir, 'p')
+ call writefile(['let g:sequence .= "run"'], rundir . '/bar.vim')
+ call writefile(['let g:sequence .= "start"'], startdir . '/bar.vim')
+ call writefile(['let g:sequence .= "foostart"'], startdir . '/foo.vim')
+ call writefile(['let g:sequence .= "opt"'], optdir . '/bar.vim')
+ call writefile(['let g:sequence .= "xxxopt"'], optdir . '/xxx.vim')
+ exe 'set rtp=' . &packpath . '/runtime'
+
+ let g:sequence = ''
+ runtime extra/bar.vim
+ call assert_equal('run', g:sequence)
+ let g:sequence = ''
+ runtime START extra/bar.vim
+ call assert_equal('start', g:sequence)
+ let g:sequence = ''
+ runtime OPT extra/bar.vim
+ call assert_equal('opt', g:sequence)
+ let g:sequence = ''
+ runtime PACK extra/bar.vim
+ call assert_equal('start', g:sequence)
+ let g:sequence = ''
+ runtime! PACK extra/bar.vim
+ call assert_equal('startopt', g:sequence)
+ let g:sequence = ''
+ runtime PACK extra/xxx.vim
+ call assert_equal('xxxopt', g:sequence)
+
+ let g:sequence = ''
+ runtime ALL extra/bar.vim
+ call assert_equal('run', g:sequence)
+ let g:sequence = ''
+ runtime ALL extra/foo.vim
+ call assert_equal('foostart', g:sequence)
+ let g:sequence = ''
+ runtime! ALL extra/xxx.vim
+ call assert_equal('xxxopt', g:sequence)
+ let g:sequence = ''
+ runtime! ALL extra/bar.vim
+ call assert_equal('runstartopt', g:sequence)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_profile.vim b/src/nvim/testdir/test_profile.vim
index 4b0097617e..fdb6f13e2b 100644
--- a/src/nvim/testdir/test_profile.vim
+++ b/src/nvim/testdir/test_profile.vim
@@ -1,8 +1,9 @@
" Test Vim profiler
-if !has('profile')
- finish
-endif
+source check.vim
+CheckFeature profile
+
+source shared.vim
source screendump.vim
func Test_profile_func()
@@ -37,7 +38,7 @@ func Test_profile_func()
[CODE]
call writefile(lines, 'Xprofile_func.vim')
- call system(v:progpath
+ call system(GetVimCommand()
\ . ' -es --clean'
\ . ' -c "so Xprofile_func.vim"'
\ . ' -c "qall!"')
@@ -124,8 +125,8 @@ func Test_profile_func_with_ifelse()
[CODE]
call writefile(lines, 'Xprofile_func.vim')
- call system(v:progpath
- \ . ' -es -u NONE -U NONE -i NONE --noplugin'
+ call system(GetVimCommand()
+ \ . ' -es -i NONE --noplugin'
\ . ' -c "profile start Xprofile_func.log"'
\ . ' -c "profile func Foo*"'
\ . ' -c "so Xprofile_func.vim"'
@@ -237,8 +238,8 @@ func Test_profile_func_with_trycatch()
[CODE]
call writefile(lines, 'Xprofile_func.vim')
- call system(v:progpath
- \ . ' -es -u NONE -U NONE -i NONE --noplugin'
+ call system(GetVimCommand()
+ \ . ' -es -i NONE --noplugin'
\ . ' -c "profile start Xprofile_func.log"'
\ . ' -c "profile func Foo*"'
\ . ' -c "so Xprofile_func.vim"'
@@ -324,8 +325,8 @@ func Test_profile_file()
[CODE]
call writefile(lines, 'Xprofile_file.vim')
- call system(v:progpath
- \ . ' -es --clean'
+ call system(GetVimCommandClean()
+ \ . ' -es'
\ . ' -c "profile start Xprofile_file.log"'
\ . ' -c "profile file Xprofile_file.vim"'
\ . ' -c "so Xprofile_file.vim"'
@@ -369,8 +370,8 @@ func Test_profile_file_with_cont()
\ ]
call writefile(lines, 'Xprofile_file.vim')
- call system(v:progpath
- \ . ' -es -u NONE -U NONE -i NONE --noplugin'
+ call system(GetVimCommandClean()
+ \ . ' -es'
\ . ' -c "profile start Xprofile_file.log"'
\ . ' -c "profile file Xprofile_file.vim"'
\ . ' -c "so Xprofile_file.vim"'
@@ -427,7 +428,7 @@ func Test_profile_truncate_mbyte()
\ ]
call writefile(lines, 'Xprofile_file.vim')
- call system(v:progpath
+ call system(GetVimCommandClean()
\ . ' -es --cmd "set enc=utf-8"'
\ . ' -c "profile start Xprofile_file.log"'
\ . ' -c "profile file Xprofile_file.vim"'
@@ -474,7 +475,7 @@ func Test_profdel_func()
call Foo3()
[CODE]
call writefile(lines, 'Xprofile_file.vim')
- call system(v:progpath . ' -es --clean -c "so Xprofile_file.vim" -c q')
+ call system(GetVimCommandClean() . ' -es -c "so Xprofile_file.vim" -c q')
call assert_equal(0, v:shell_error)
let lines = readfile('Xprofile_file.log')
@@ -509,7 +510,7 @@ func Test_profdel_star()
call Foo()
[CODE]
call writefile(lines, 'Xprofile_file.vim')
- call system(v:progpath . ' -es --clean -c "so Xprofile_file.vim" -c q')
+ call system(GetVimCommandClean() . ' -es -c "so Xprofile_file.vim" -c q')
call assert_equal(0, v:shell_error)
let lines = readfile('Xprofile_file.log')
diff --git a/src/nvim/testdir/test_put.vim b/src/nvim/testdir/test_put.vim
index f42b177c50..65232175c6 100644
--- a/src/nvim/testdir/test_put.vim
+++ b/src/nvim/testdir/test_put.vim
@@ -1,5 +1,7 @@
" Tests for put commands, e.g. ":put", "p", "gp", "P", "gP", etc.
+source check.vim
+
func Test_put_block()
new
call feedkeys("i\<C-V>u2500\<CR>x\<ESC>", 'x')
@@ -112,15 +114,92 @@ func Test_put_p_indent_visual()
bwipe!
endfunc
+func Test_gp_with_count_leaves_cursor_at_end()
+ new
+ call setline(1, '<---->')
+ call setreg('@', "foo\nbar", 'c')
+ normal 1G3|3gp
+ call assert_equal([0, 4, 4, 0], getpos("."))
+ call assert_equal(['<--foo', 'barfoo', 'barfoo', 'bar-->'], getline(1, '$'))
+ call assert_equal([0, 4, 3, 0], getpos("']"))
+
+ bwipe!
+endfunc
+
+func Test_p_with_count_leaves_mark_at_end()
+ new
+ call setline(1, '<---->')
+ call setreg('@', "start\nend", 'c')
+ normal 1G3|3p
+ call assert_equal([0, 1, 4, 0], getpos("."))
+ call assert_equal(['<--start', 'endstart', 'endstart', 'end-->'], getline(1, '$'))
+ call assert_equal([0, 4, 3, 0], getpos("']"))
+
+ bwipe!
+endfunc
+
+func Test_very_large_count()
+ new
+ " total put-length (21474837 * 100) brings 32 bit int overflow
+ let @" = repeat('x', 100)
+ call assert_fails('norm 21474837p', 'E1240:')
+ bwipe!
+endfunc
+
+func Test_very_large_count_64bit()
+ throw 'Skipped: v:sizeoflong is N/A' " use legacy/put_spec.lua instead
+
+ if v:sizeoflong < 8
+ throw 'Skipped: only works with 64 bit long ints'
+ endif
+
+ new
+ let @" = repeat('x', 100)
+ call assert_fails('norm 999999999p', 'E1240:')
+ bwipe!
+endfunc
+
+func Test_very_large_count_block()
+ new
+ " total put-length (21474837 * 100) brings 32 bit int overflow
+ call setline(1, repeat('x', 100))
+ exe "norm \<C-V>99ly"
+ call assert_fails('norm 21474837p', 'E1240:')
+ bwipe!
+endfunc
+
+func Test_very_large_count_block_64bit()
+ throw 'Skipped: v:sizeoflong is N/A' " use legacy/put_spec.lua instead
+
+ if v:sizeoflong < 8
+ throw 'Skipped: only works with 64 bit long ints'
+ endif
+
+ new
+ call setline(1, repeat('x', 100))
+ exe "norm \<C-V>$y"
+ call assert_fails('norm 999999999p', 'E1240:')
+ bwipe!
+endfunc
+
+func Test_put_above_first_line()
+ new
+ let @" = 'text'
+ silent! normal 0o00
+ 0put
+ call assert_equal('text', getline(1))
+ bwipe!
+endfunc
+
func Test_multibyte_op_end_mark()
- new
- call setline(1, 'тест')
- normal viwdp
- call assert_equal([0, 1, 7, 0], getpos("'>"))
- call assert_equal([0, 1, 7, 0], getpos("']"))
-
- normal Vyp
- call assert_equal([0, 1, 2147483647, 0], getpos("'>"))
- call assert_equal([0, 2, 7, 0], getpos("']"))
- bwipe!
- endfunc
+ new
+ call setline(1, 'тест')
+ normal viwdp
+ call assert_equal([0, 1, 7, 0], getpos("'>"))
+ call assert_equal([0, 1, 7, 0], getpos("']"))
+
+ normal Vyp
+ call assert_equal([0, 1, 2147483647, 0], getpos("'>"))
+ call assert_equal([0, 2, 7, 0], getpos("']"))
+ bwipe!
+endfunc
diff --git a/src/nvim/testdir/test_python3.vim b/src/nvim/testdir/test_python3.vim
index 54da3d2eba..f6a1942e24 100644
--- a/src/nvim/testdir/test_python3.vim
+++ b/src/nvim/testdir/test_python3.vim
@@ -69,6 +69,7 @@ func Test_vim_function()
endfunc
func Test_skipped_python3_command_does_not_affect_pyxversion()
+ throw 'skipped: Nvim hardcodes pyxversion=3'
set pyxversion=0
if 0
python3 import vim
diff --git a/src/nvim/testdir/test_pyx2.vim b/src/nvim/testdir/test_pyx2.vim
index b6ed80f842..6a8ebf3da0 100644
--- a/src/nvim/testdir/test_pyx2.vim
+++ b/src/nvim/testdir/test_pyx2.vim
@@ -1,9 +1,9 @@
" Test for pyx* commands and functions with Python 2.
-set pyx=2
if !has('python')
finish
endif
+set pyx=2
let s:py2pattern = '^2\.[0-7]\.\d\+'
let s:py3pattern = '^3\.\d\+\.\d\+'
diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim
index 6db679c5f9..6852f53ea8 100644
--- a/src/nvim/testdir/test_quickfix.vim
+++ b/src/nvim/testdir/test_quickfix.vim
@@ -32,7 +32,7 @@ func s:setup_commands(cchar)
command! -count -nargs=* -bang Xnfile <mods><count>cnfile<bang> <args>
command! -nargs=* -bang Xpfile <mods>cpfile<bang> <args>
command! -nargs=* Xexpr <mods>cexpr <args>
- command! -count -nargs=* Xvimgrep <mods> <count>vimgrep <args>
+ command! -count=999 -nargs=* Xvimgrep <mods> <count>vimgrep <args>
command! -nargs=* Xvimgrepadd <mods> vimgrepadd <args>
command! -nargs=* Xgrep <mods> grep <args>
command! -nargs=* Xgrepadd <mods> grepadd <args>
@@ -69,7 +69,7 @@ func s:setup_commands(cchar)
command! -count -nargs=* -bang Xnfile <mods><count>lnfile<bang> <args>
command! -nargs=* -bang Xpfile <mods>lpfile<bang> <args>
command! -nargs=* Xexpr <mods>lexpr <args>
- command! -count -nargs=* Xvimgrep <mods> <count>lvimgrep <args>
+ command! -count=999 -nargs=* Xvimgrep <mods> <count>lvimgrep <args>
command! -nargs=* Xvimgrepadd <mods> lvimgrepadd <args>
command! -nargs=* Xgrep <mods> lgrep <args>
command! -nargs=* Xgrepadd <mods> lgrepadd <args>
@@ -1384,6 +1384,29 @@ func Test_efm_error_type()
let &efm = save_efm
endfunc
+" Test for end_lnum ('%e') and end_col ('%k') fields in 'efm'
+func Test_efm_end_lnum_col()
+ let save_efm = &efm
+
+ " single line
+ set efm=%f:%l-%e:%c-%k:%t:%m
+ cexpr ["Xfile1:10-20:1-2:E:msg1", "Xfile1:20-30:2-3:W:msg2",]
+ let output = split(execute('clist'), "\n")
+ call assert_equal([
+ \ ' 1 Xfile1:10-20 col 1-2 error: msg1',
+ \ ' 2 Xfile1:20-30 col 2-3 warning: msg2'], output)
+
+ " multiple lines
+ set efm=%A%n)%m,%Z%f:%l-%e:%c-%k
+ cexpr ["1)msg1", "Xfile1:14-24:1-2",
+ \ "2)msg2", "Xfile1:24-34:3-4"]
+ let output = split(execute('clist'), "\n")
+ call assert_equal([
+ \ ' 1 Xfile1:14-24 col 1-2 error 1: msg1',
+ \ ' 2 Xfile1:24-34 col 3-4 error 2: msg2'], output)
+ let &efm = save_efm
+endfunc
+
func XquickfixChangedByAutocmd(cchar)
call s:setup_commands(a:cchar)
if a:cchar == 'c'
@@ -1914,6 +1937,7 @@ func Test_switchbuf()
" If opening a file changes 'switchbuf', then the new value should be
" retained.
+ set modeline&vim
call writefile(["vim: switchbuf=split"], 'Xqftestfile1')
enew | only
set switchbuf&vim
@@ -2715,7 +2739,7 @@ func Test_cwindow_jump()
call assert_true(winnr('$') == 2)
call assert_true(winnr() == 1)
- " Jumping to a file from the location list window should find a usuable
+ " Jumping to a file from the location list window should find a usable
" window by wrapping around the window list.
enew | only
call setloclist(0, [], 'f')
@@ -5027,6 +5051,52 @@ func Test_qfbuf_update()
call Xqfbuf_update('l')
endfunc
+" Test for the :vimgrep 'f' flag (fuzzy match)
+func Xvimgrep_fuzzy_match(cchar)
+ call s:setup_commands(a:cchar)
+
+ Xvimgrep /three one/f Xfile*
+ let l = g:Xgetlist()
+ call assert_equal(2, len(l))
+ call assert_equal(['Xfile1', 1, 9, 'one two three'],
+ \ [bufname(l[0].bufnr), l[0].lnum, l[0].col, l[0].text])
+ call assert_equal(['Xfile2', 2, 1, 'three one two'],
+ \ [bufname(l[1].bufnr), l[1].lnum, l[1].col, l[1].text])
+
+ Xvimgrep /the/f Xfile*
+ let l = g:Xgetlist()
+ call assert_equal(3, len(l))
+ call assert_equal(['Xfile1', 1, 9, 'one two three'],
+ \ [bufname(l[0].bufnr), l[0].lnum, l[0].col, l[0].text])
+ call assert_equal(['Xfile2', 2, 1, 'three one two'],
+ \ [bufname(l[1].bufnr), l[1].lnum, l[1].col, l[1].text])
+ call assert_equal(['Xfile2', 4, 4, 'aaathreeaaa'],
+ \ [bufname(l[2].bufnr), l[2].lnum, l[2].col, l[2].text])
+
+ Xvimgrep /aaa/fg Xfile*
+ let l = g:Xgetlist()
+ call assert_equal(4, len(l))
+ call assert_equal(['Xfile1', 2, 1, 'aaaaaa'],
+ \ [bufname(l[0].bufnr), l[0].lnum, l[0].col, l[0].text])
+ call assert_equal(['Xfile1', 2, 4, 'aaaaaa'],
+ \ [bufname(l[1].bufnr), l[1].lnum, l[1].col, l[1].text])
+ call assert_equal(['Xfile2', 4, 1, 'aaathreeaaa'],
+ \ [bufname(l[2].bufnr), l[2].lnum, l[2].col, l[2].text])
+ call assert_equal(['Xfile2', 4, 9, 'aaathreeaaa'],
+ \ [bufname(l[3].bufnr), l[3].lnum, l[3].col, l[3].text])
+
+ call assert_fails('Xvimgrep /xyz/fg Xfile*', 'E480:')
+endfunc
+
+func Test_vimgrep_fuzzy_match()
+ call writefile(['one two three', 'aaaaaa'], 'Xfile1')
+ call writefile(['one', 'three one two', 'two', 'aaathreeaaa'], 'Xfile2')
+ call Xvimgrep_fuzzy_match('c')
+ call Xvimgrep_fuzzy_match('l')
+ call delete('Xfile1')
+ call delete('Xfile2')
+endfunc
+
" Test for getting a specific item from a quickfix list
func Xtest_getqflist_by_idx(cchar)
call s:setup_commands(a:cchar)
diff --git a/src/nvim/testdir/test_random.vim b/src/nvim/testdir/test_random.vim
new file mode 100644
index 0000000000..6d3f7dcfd9
--- /dev/null
+++ b/src/nvim/testdir/test_random.vim
@@ -0,0 +1,51 @@
+" Tests for srand() and rand()
+
+func Test_Rand()
+ let r = srand(123456789)
+ call assert_equal([1573771921, 319883699, 2742014374, 1324369493], r)
+ call assert_equal(4284103975, rand(r))
+ call assert_equal(1001954530, rand(r))
+ call assert_equal(2701803082, rand(r))
+ call assert_equal(2658065534, rand(r))
+ call assert_equal(3104308804, rand(r))
+
+ " Nvim does not support test_settime
+ " call test_settime(12341234)
+ let s = srand()
+ if !has('win32') && filereadable('/dev/urandom')
+ " using /dev/urandom
+ call assert_notequal(s, srand())
+ " else
+ " " using time()
+ " call assert_equal(s, srand())
+ " call test_settime(12341235)
+ " call assert_notequal(s, srand())
+ endif
+
+ " Nvim does not support test_srand_seed
+ " call test_srand_seed(123456789)
+ " call assert_equal(4284103975, rand())
+ " call assert_equal(1001954530, rand())
+ " call test_srand_seed()
+
+ if has('float')
+ call assert_fails('echo srand(1.2)', 'E805:')
+ endif
+ call assert_fails('echo srand([1])', 'E745:')
+ call assert_fails('echo rand("burp")', 'E475:')
+ call assert_fails('echo rand([1, 2, 3])', 'E475:')
+ call assert_fails('echo rand([[1], 2, 3, 4])', 'E475:')
+ call assert_fails('echo rand([1, [2], 3, 4])', 'E475:')
+ call assert_fails('echo rand([1, 2, [3], 4])', 'E475:')
+ call assert_fails('echo rand([1, 2, 3, [4]])', 'E475:')
+
+ " call test_settime(0)
+endfunc
+
+func Test_issue_5587()
+ call rand()
+ call garbagecollect()
+ call rand()
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_regexp_latin.vim b/src/nvim/testdir/test_regexp_latin.vim
index 712f1e6025..a92f7e1192 100644
--- a/src/nvim/testdir/test_regexp_latin.vim
+++ b/src/nvim/testdir/test_regexp_latin.vim
@@ -787,4 +787,12 @@ func Test_regexp_error()
set re&
endfunc
+func Test_using_mark_position()
+ " this was using freed memory
+ new
+ norm O0
+ call assert_fails("s/\\%')", 'E486:')
+ bwipe!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_regexp_utf8.vim b/src/nvim/testdir/test_regexp_utf8.vim
index d8d5797dcf..b640a6d043 100644
--- a/src/nvim/testdir/test_regexp_utf8.vim
+++ b/src/nvim/testdir/test_regexp_utf8.vim
@@ -152,9 +152,6 @@ func s:classes_test()
if has('win32')
let identchars_ok = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§µÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ'
let kwordchars_ok = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyzµÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
- elseif has('ebcdic')
- let identchars_ok = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz€ŒŽœž¬®µº¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
- let kwordchars_ok = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz€ŒŽœž¬®µº¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
else
let identchars_ok = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyzµÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
let kwordchars_ok = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyzµÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
@@ -166,8 +163,6 @@ func s:classes_test()
let fnamechars_ok = '$+,-./0123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
elseif has('vms')
let fnamechars_ok = '#$%+,-./0123456789:;<>ABCDEFGHIJKLMNOPQRSTUVWXYZ[]_abcdefghijklmnopqrstuvwxyz~ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
- elseif has('ebcdic')
- let fnamechars_ok = '#$%+,-./=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
else
let fnamechars_ok = '#$%+,-./0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
endif
@@ -590,4 +585,12 @@ func Test_match_char_class_upper()
bwipe!
endfunc
+func Test_match_invalid_byte()
+ call writefile(0z630a.765d30aa0a.2e0a.790a.4030, 'Xinvalid')
+ new
+ source Xinvalid
+ bwipe!
+ call delete('Xinvalid')
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_registers.vim b/src/nvim/testdir/test_registers.vim
index 84a5aca3d5..23e39eba35 100644
--- a/src/nvim/testdir/test_registers.vim
+++ b/src/nvim/testdir/test_registers.vim
@@ -1,6 +1,4 @@
-"
" Tests for register operations
-"
source check.vim
source view_util.vim
@@ -13,6 +11,8 @@ func Test_aaa_empty_reg_test()
call assert_fails('normal @!', 'E354:')
call assert_fails('normal @:', 'E30:')
call assert_fails('normal @.', 'E29:')
+ call assert_fails('put /', 'E35:')
+ call assert_fails('put .', 'E29:')
endfunc
func Test_yank_shows_register()
@@ -119,6 +119,17 @@ func Test_recording_esc_sequence()
endif
endfunc
+func Test_recording_with_select_mode()
+ new
+ call feedkeys("qacc12345\<Esc>gH98765\<Esc>q", "tx")
+ call assert_equal("98765", getline(1))
+ call assert_equal("cc12345\<Esc>gH98765\<Esc>", @a)
+ call setline(1, 'asdf')
+ normal! @a
+ call assert_equal("98765", getline(1))
+ bwipe!
+endfunc
+
" Test for executing the last used register (@)
func Test_last_used_exec_reg()
" Test for the @: command
@@ -141,6 +152,14 @@ func Test_last_used_exec_reg()
normal @@
call assert_equal('EditEdit', a)
+ " Test for repeating the last command-line in visual mode
+ call append(0, 'register')
+ normal gg
+ let @r = ''
+ call feedkeys("v:yank R\<CR>", 'xt')
+ call feedkeys("v@:", 'xt')
+ call assert_equal("\nregister\nregister\n", @r)
+
enew!
endfunc
@@ -164,6 +183,19 @@ func Test_get_register()
call assert_equal('', getregtype('!'))
+ " Test for inserting an invalid register content
+ call assert_beeps('exe "normal i\<C-R>!"')
+
+ " Test for inserting a register with multiple lines
+ call deletebufline('', 1, '$')
+ call setreg('r', ['a', 'b'])
+ exe "normal i\<C-R>r"
+ call assert_equal(['a', 'b', ''], getline(1, '$'))
+
+ " Test for inserting a multi-line register in the command line
+ call feedkeys(":\<C-R>r\<Esc>", 'xt')
+ call assert_equal("a\rb", histget(':', -1)) " Modified because of #6137
+
enew!
endfunc
@@ -187,9 +219,200 @@ func Test_set_register()
call setreg('=', 'b', 'a')
call assert_equal('regwrite', getreg('='))
+ " Test for setting a list of lines to special registers
+ call setreg('/', [])
+ call assert_equal('', @/)
+ call setreg('=', [])
+ call assert_equal('', @=)
+ call assert_fails("call setreg('/', ['a', 'b'])", 'E883:')
+ call assert_fails("call setreg('=', ['a', 'b'])", 'E883:')
+ call assert_equal(0, setreg('_', ['a', 'b']))
+
+ " Test for recording to a invalid register
+ call assert_beeps('normal q$')
+
+ " Appending to a register when recording
+ call append(0, "text for clipboard test")
+ normal gg
+ call feedkeys('qrllq', 'xt')
+ call feedkeys('qRhhq', 'xt')
+ call assert_equal('llhh', getreg('r'))
+
+ " Appending a list of characters to a register from different lines
+ let @r = ''
+ call append(0, ['abcdef', '123456'])
+ normal gg"ry3l
+ call cursor(2, 4)
+ normal "Ry3l
+ call assert_equal('abc456', @r)
+
+ " Test for gP with multiple lines selected using characterwise motion
+ %delete
+ call append(0, ['vim editor', 'vim editor'])
+ let @r = ''
+ exe "normal ggwy/vim /e\<CR>gP"
+ call assert_equal(['vim editor', 'vim editor', 'vim editor'], getline(1, 3))
+
+ " Test for gP with . register
+ %delete
+ normal iabc
+ normal ".gp
+ call assert_equal('abcabc', getline(1))
+ normal 0".gP
+ call assert_equal('abcabcabc', getline(1))
+
enew!
endfunc
+" Test for clipboard registers (* and +)
+func Test_clipboard_regs()
+ throw 'skipped: needs clipboard=autoselect,autoselectplus'
+
+ CheckNotGui
+ CheckFeature clipboard_working
+
+ new
+ call append(0, "text for clipboard test")
+ normal gg"*yiw
+ call assert_equal('text', getreg('*'))
+ normal gg2w"+yiw
+ call assert_equal('clipboard', getreg('+'))
+
+ " Test for replacing the clipboard register contents
+ set clipboard=unnamed
+ let @* = 'food'
+ normal ggviw"*p
+ call assert_equal('text', getreg('*'))
+ call assert_equal('food for clipboard test', getline(1))
+ normal ggviw"*p
+ call assert_equal('food', getreg('*'))
+ call assert_equal('text for clipboard test', getline(1))
+
+ " Test for replacing the selection register contents
+ set clipboard=unnamedplus
+ let @+ = 'food'
+ normal ggviw"+p
+ call assert_equal('text', getreg('+'))
+ call assert_equal('food for clipboard test', getline(1))
+ normal ggviw"+p
+ call assert_equal('food', getreg('+'))
+ call assert_equal('text for clipboard test', getline(1))
+
+ " Test for auto copying visually selected text to clipboard register
+ call setline(1, "text for clipboard test")
+ let @* = ''
+ set clipboard=autoselect
+ normal ggwwviwy
+ call assert_equal('clipboard', @*)
+
+ " Test for auto copying visually selected text to selection register
+ let @+ = ''
+ set clipboard=autoselectplus
+ normal ggwviwy
+ call assert_equal('for', @+)
+
+ set clipboard&vim
+ bwipe!
+endfunc
+
+" Test for restarting the current mode (insert or virtual replace) after
+" executing the contents of a register
+func Test_put_reg_restart_mode()
+ new
+ call append(0, 'editor')
+ normal gg
+ let @r = "ivim \<Esc>"
+ call feedkeys("i\<C-O>@r\<C-R>=mode()\<CR>", 'xt')
+ call assert_equal('vimi editor', getline(1))
+
+ call setline(1, 'editor')
+ normal gg
+ call feedkeys("gR\<C-O>@r\<C-R>=mode()\<CR>", 'xt')
+ call assert_equal('vimReditor', getline(1))
+
+ bwipe!
+endfunc
+
+" Test for getting register info
+func Test_get_reginfo()
+ enew
+ call setline(1, ['foo', 'bar'])
+
+ exe 'norm! "zyy'
+ let info = getreginfo('"')
+ call assert_equal('z', info.points_to)
+ call setreg('y', 'baz')
+ call assert_equal('z', getreginfo('').points_to)
+ call setreg('y', { 'isunnamed': v:true })
+ call assert_equal('y', getreginfo('"').points_to)
+
+ exe '$put'
+ call assert_equal(getreg('y'), getline(3))
+ call setreg('', 'qux')
+ call assert_equal('0', getreginfo('').points_to)
+ call setreg('x', 'quux')
+ call assert_equal('0', getreginfo('').points_to)
+
+ let info = getreginfo('')
+ call assert_equal(getreg('', 1, 1), info.regcontents)
+ call assert_equal(getregtype(''), info.regtype)
+
+ exe "norm! 0\<c-v>e" .. '"zy'
+ let info = getreginfo('z')
+ call assert_equal(getreg('z', 1, 1), info.regcontents)
+ call assert_equal(getregtype('z'), info.regtype)
+ call assert_equal(1, +info.isunnamed)
+
+ let info = getreginfo('"')
+ call assert_equal('z', info.points_to)
+
+ bwipe!
+endfunc
+
+" Test for restoring register with dict from getreginfo
+func Test_set_register_dict()
+ enew!
+
+ call setreg('"', #{ regcontents: ['one', 'two'],
+ \ regtype: 'V', points_to: 'z' })
+ call assert_equal(['one', 'two'], getreg('"', 1, 1))
+ let info = getreginfo('"')
+ call assert_equal('z', info.points_to)
+ call assert_equal('V', info.regtype)
+ call assert_equal(1, +getreginfo('z').isunnamed)
+
+ call setreg('x', #{ regcontents: ['three', 'four'],
+ \ regtype: 'v', isunnamed: v:true })
+ call assert_equal(['three', 'four'], getreg('"', 1, 1))
+ let info = getreginfo('"')
+ call assert_equal('x', info.points_to)
+ call assert_equal('v', info.regtype)
+ call assert_equal(1, +getreginfo('x').isunnamed)
+
+ call setreg('y', #{ regcontents: 'five',
+ \ regtype: "\<c-v>", isunnamed: v:false })
+ call assert_equal("\<c-v>4", getreginfo('y').regtype)
+ call assert_equal(0, +getreginfo('y').isunnamed)
+ call assert_equal(['three', 'four'], getreg('"', 1, 1))
+ call assert_equal('x', getreginfo('"').points_to)
+
+ call setreg('"', #{ regcontents: 'six' })
+ call assert_equal('0', getreginfo('"').points_to)
+ call assert_equal(1, +getreginfo('0').isunnamed)
+ call assert_equal(['six'], getreginfo('0').regcontents)
+ call assert_equal(['six'], getreginfo('"').regcontents)
+
+ let @x = 'one'
+ call setreg('x', {})
+ call assert_equal(1, len(split(execute('reg x'), '\n')))
+
+ call assert_fails("call setreg('0', #{regtype: 'V'}, 'v')", 'E118:')
+ call assert_fails("call setreg('0', #{regtype: 'X'})", 'E475:')
+ call assert_fails("call setreg('0', #{regtype: 'vy'})", 'E475:')
+
+ bwipe!
+endfunc
+
func Test_v_register()
enew
call setline(1, 'nothing')
@@ -288,84 +511,25 @@ func Test_insert_small_delete()
bwipe!
endfunc
-" Test for getting register info
-func Test_get_reginfo()
- enew
- call setline(1, ['foo', 'bar'])
-
- exe 'norm! "zyy'
- let info = getreginfo('"')
- call assert_equal('z', info.points_to)
- call setreg('y', 'baz')
- call assert_equal('z', getreginfo('').points_to)
- call setreg('y', { 'isunnamed': v:true })
- call assert_equal('y', getreginfo('"').points_to)
-
- exe '$put'
- call assert_equal(getreg('y'), getline(3))
- call setreg('', 'qux')
- call assert_equal('0', getreginfo('').points_to)
- call setreg('x', 'quux')
- call assert_equal('0', getreginfo('').points_to)
-
- let info = getreginfo('')
- call assert_equal(getreg('', 1, 1), info.regcontents)
- call assert_equal(getregtype(''), info.regtype)
-
- exe "norm! 0\<c-v>e" .. '"zy'
- let info = getreginfo('z')
- call assert_equal(getreg('z', 1, 1), info.regcontents)
- call assert_equal(getregtype('z'), info.regtype)
- call assert_equal(1, +info.isunnamed)
-
- let info = getreginfo('"')
- call assert_equal('z', info.points_to)
+func Test_record_in_select_mode()
+ new
+ call setline(1, 'text')
+ sil norm q00
+ sil norm q
+ call assert_equal('0ext', getline(1))
+
+ %delete
+ let @r = ''
+ call setline(1, ['abc', 'abc', 'abc'])
+ smap <F2> <Right><Right>,
+ call feedkeys("qrgh\<F2>Dk\<Esc>q", 'xt')
+ call assert_equal("gh\<F2>Dk\<Esc>", @r)
+ norm j0@rj0@@
+ call assert_equal([',Dk', ',Dk', ',Dk'], getline(1, 3))
+ sunmap <F2>
bwipe!
endfunc
-" Test for restoring register with dict from getreginfo
-func Test_set_register_dict()
- enew!
-
- call setreg('"', #{ regcontents: ['one', 'two'],
- \ regtype: 'V', points_to: 'z' })
- call assert_equal(['one', 'two'], getreg('"', 1, 1))
- let info = getreginfo('"')
- call assert_equal('z', info.points_to)
- call assert_equal('V', info.regtype)
- call assert_equal(1, +getreginfo('z').isunnamed)
-
- call setreg('x', #{ regcontents: ['three', 'four'],
- \ regtype: 'v', isunnamed: v:true })
- call assert_equal(['three', 'four'], getreg('"', 1, 1))
- let info = getreginfo('"')
- call assert_equal('x', info.points_to)
- call assert_equal('v', info.regtype)
- call assert_equal(1, +getreginfo('x').isunnamed)
-
- call setreg('y', #{ regcontents: 'five',
- \ regtype: "\<c-v>", isunnamed: v:false })
- call assert_equal("\<c-v>4", getreginfo('y').regtype)
- call assert_equal(0, +getreginfo('y').isunnamed)
- call assert_equal(['three', 'four'], getreg('"', 1, 1))
- call assert_equal('x', getreginfo('"').points_to)
-
- call setreg('"', #{ regcontents: 'six' })
- call assert_equal('0', getreginfo('"').points_to)
- call assert_equal(1, +getreginfo('0').isunnamed)
- call assert_equal(['six'], getreginfo('0').regcontents)
- call assert_equal(['six'], getreginfo('"').regcontents)
-
- let @x = 'one'
- call setreg('x', {})
- call assert_equal(1, len(split(execute('reg x'), '\n')))
-
- call assert_fails("call setreg('0', #{regtype: 'V'}, 'v')", 'E118:')
- call assert_fails("call setreg('0', #{regtype: 'X'})", 'E475:')
- call assert_fails("call setreg('0', #{regtype: 'vy'})", 'E475:')
-
- bwipe!
-endfunc
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_rename.vim b/src/nvim/testdir/test_rename.vim
index 3887fcfabf..5359b84923 100644
--- a/src/nvim/testdir/test_rename.vim
+++ b/src/nvim/testdir/test_rename.vim
@@ -1,5 +1,7 @@
" Test rename()
+source shared.vim
+
func Test_rename_file_to_file()
call writefile(['foo'], 'Xrename1')
@@ -81,7 +83,7 @@ func Test_rename_copy()
call assert_equal(0, rename('Xrenamedir/Xrenamefile', 'Xrenamefile'))
- if !has('win32')
+ if !has('win32') && !IsRoot()
" On Windows, the source file is removed despite
" its directory being made not writable.
call assert_equal(['foo'], readfile('Xrenamedir/Xrenamefile'))
diff --git a/src/nvim/testdir/test_retab.vim b/src/nvim/testdir/test_retab.vim
index f11a32bade..1650a03876 100644
--- a/src/nvim/testdir/test_retab.vim
+++ b/src/nvim/testdir/test_retab.vim
@@ -69,9 +69,33 @@ func Test_retab()
call assert_equal(" a b c ", Retab('!', 3))
call assert_equal(" a b c ", Retab('', 5))
call assert_equal(" a b c ", Retab('!', 5))
+
+ set tabstop& expandtab&
endfunc
func Test_retab_error()
call assert_fails('retab -1', 'E487:')
call assert_fails('retab! -1', 'E487:')
+ call assert_fails('ret -1000', 'E487:')
+ call assert_fails('ret 10000', 'E475:')
+ call assert_fails('ret 80000000000000000000', 'E475:')
endfunc
+
+func Test_retab_endless()
+ new
+ call setline(1, "\t0\t")
+ let caught = 'no'
+ try
+ while 1
+ set ts=4000
+ retab 4
+ endwhile
+ catch /E1240/
+ let caught = 'yes'
+ endtry
+ bwipe!
+ set tabstop&
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_scriptnames.vim b/src/nvim/testdir/test_scriptnames.vim
index fc6c910bfa..44ec146666 100644
--- a/src/nvim/testdir/test_scriptnames.vim
+++ b/src/nvim/testdir/test_scriptnames.vim
@@ -23,4 +23,10 @@ func Test_scriptnames()
bwipe
call delete('Xscripting')
+
+ let msgs = execute('messages')
+ scriptnames
+ call assert_equal(msgs, execute('messages'))
endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim
index c796f1f676..a3d5ca96a1 100644
--- a/src/nvim/testdir/test_search.vim
+++ b/src/nvim/testdir/test_search.vim
@@ -292,15 +292,84 @@ endfunc
func Test_searchpair()
new
- call setline(1, ['other code here', '', '[', '" cursor here', ']'])
+ call setline(1, ['other code', 'here [', ' [', ' " cursor here', ' ]]'])
+
+ 4
+ call assert_equal(3, searchpair('\[', '', ']', 'bW'))
+ call assert_equal([0, 3, 2, 0], getpos('.'))
+ 4
+ call assert_equal(2, searchpair('\[', '', ']', 'bWr'))
+ call assert_equal([0, 2, 6, 0], getpos('.'))
+ 4
+ call assert_equal(1, searchpair('\[', '', ']', 'bWm'))
+ call assert_equal([0, 3, 2, 0], getpos('.'))
+ 4|norm ^
+ call assert_equal(5, searchpair('\[', '', ']', 'Wn'))
+ call assert_equal([0, 4, 2, 0], getpos('.'))
+ 4
+ call assert_equal(2, searchpair('\[', '', ']', 'bW',
+ \ 'getline(".") =~ "^\\s*\["'))
+ call assert_equal([0, 2, 6, 0], getpos('.'))
+ set nomagic
+ 4
+ call assert_equal(3, searchpair('\[', '', ']', 'bW'))
+ call assert_equal([0, 3, 2, 0], getpos('.'))
+ set magic
+ 4|norm ^
+ call assert_equal(0, searchpair('{', '', '}', 'bW'))
+ call assert_equal([0, 4, 2, 0], getpos('.'))
+
+ %d
+ call setline(1, ['if 1', ' if 2', ' else', ' endif 2', 'endif 1'])
+
+ /\<if 1
+ call assert_equal(5, searchpair('\<if\>', '\<else\>', '\<endif\>', 'W'))
+ call assert_equal([0, 5, 1, 0], getpos('.'))
+ /\<if 2
+ call assert_equal(3, searchpair('\<if\>', '\<else\>', '\<endif\>', 'W'))
+ call assert_equal([0, 3, 3, 0], getpos('.'))
+
+ q!
+endfunc
+
+func Test_searchpairpos()
+ new
+ call setline(1, ['other code', 'here [', ' [', ' " cursor here', ' ]]'])
+
+ 4
+ call assert_equal([3, 2], searchpairpos('\[', '', ']', 'bW'))
+ call assert_equal([0, 3, 2, 0], getpos('.'))
4
- let a = searchpair('\[','',']','bW')
- call assert_equal(3, a)
+ call assert_equal([2, 6], searchpairpos('\[', '', ']', 'bWr'))
+ call assert_equal([0, 2, 6, 0], getpos('.'))
+ 4|norm ^
+ call assert_equal([5, 2], searchpairpos('\[', '', ']', 'Wn'))
+ call assert_equal([0, 4, 2, 0], getpos('.'))
+ 4
+ call assert_equal([2, 6], searchpairpos('\[', '', ']', 'bW',
+ \ 'getline(".") =~ "^\\s*\["'))
+ call assert_equal([0, 2, 6, 0], getpos('.'))
+ 4
+ call assert_equal([2, 6], searchpairpos('\[', '', ']', 'bWr'))
+ call assert_equal([0, 2, 6, 0], getpos('.'))
set nomagic
4
- let a = searchpair('\[','',']','bW')
- call assert_equal(3, a)
+ call assert_equal([3, 2], searchpairpos('\[', '', ']', 'bW'))
+ call assert_equal([0, 3, 2, 0], getpos('.'))
set magic
+ 4|norm ^
+ call assert_equal([0, 0], searchpairpos('{', '', '}', 'bW'))
+ call assert_equal([0, 4, 2, 0], getpos('.'))
+
+ %d
+ call setline(1, ['if 1', ' if 2', ' else', ' endif 2', 'endif 1'])
+ /\<if 1
+ call assert_equal([5, 1], searchpairpos('\<if\>', '\<else\>', '\<endif\>', 'W'))
+ call assert_equal([0, 5, 1, 0], getpos('.'))
+ /\<if 2
+ call assert_equal([3, 3], searchpairpos('\<if\>', '\<else\>', '\<endif\>', 'W'))
+ call assert_equal([0, 3, 3, 0], getpos('.'))
+
q!
endfunc
@@ -309,17 +378,29 @@ func Test_searchpair_errors()
call assert_fails("call searchpair('start', {-> 0}, 'end', 'bW', 'skip', 99, 100)", 'E729: using Funcref as a String')
call assert_fails("call searchpair('start', 'middle', {'one': 1}, 'bW', 'skip', 99, 100)", 'E731: using Dictionary as a String')
call assert_fails("call searchpair('start', 'middle', 'end', 'flags', 'skip', 99, 100)", 'E475: Invalid argument: flags')
- call assert_fails("call searchpair('start', 'middle', 'end', 'bW', 0, 99, 100)", 'E475: Invalid argument: 0')
call assert_fails("call searchpair('start', 'middle', 'end', 'bW', 'func', -99, 100)", 'E475: Invalid argument: -99')
call assert_fails("call searchpair('start', 'middle', 'end', 'bW', 'func', 99, -100)", 'E475: Invalid argument: -100')
+ call assert_fails("call searchpair('start', 'middle', 'end', 'e')", 'E475: Invalid argument: e')
+ call assert_fails("call searchpair('start', 'middle', 'end', 'sn')", 'E475: Invalid argument: sn')
+endfunc
+
+func Test_searchpairpos_errors()
+ call assert_fails("call searchpairpos([0], 'middle', 'end', 'bW', 'skip', 99, 100)", 'E730: using List as a String')
+ call assert_fails("call searchpairpos('start', {-> 0}, 'end', 'bW', 'skip', 99, 100)", 'E729: using Funcref as a String')
+ call assert_fails("call searchpairpos('start', 'middle', {'one': 1}, 'bW', 'skip', 99, 100)", 'E731: using Dictionary as a String')
+ call assert_fails("call searchpairpos('start', 'middle', 'end', 'flags', 'skip', 99, 100)", 'E475: Invalid argument: flags')
+ call assert_fails("call searchpairpos('start', 'middle', 'end', 'bW', 'func', -99, 100)", 'E475: Invalid argument: -99')
+ call assert_fails("call searchpairpos('start', 'middle', 'end', 'bW', 'func', 99, -100)", 'E475: Invalid argument: -100')
+ call assert_fails("call searchpairpos('start', 'middle', 'end', 'e')", 'E475: Invalid argument: e')
+ call assert_fails("call searchpairpos('start', 'middle', 'end', 'sn')", 'E475: Invalid argument: sn')
endfunc
func Test_searchpair_skip()
func Zero()
- return 0
+ return 0
endfunc
func Partial(x)
- return a:x
+ return a:x
endfunc
new
call setline(1, ['{', 'foo', 'foo', 'foo', '}'])
@@ -1192,7 +1273,7 @@ endfunc
" This was causing E874. Also causes an invalid read?
func Test_look_behind()
new
- call setline(1, '0\|\&\n\@<=')
+ call setline(1, '0\|\&\n\@<=')
call search(getline("."))
bwipe!
endfunc
@@ -1236,11 +1317,11 @@ endfunc
func Test_search_Ctrl_L_combining()
" Make sure, that Ctrl-L works correctly with combining characters.
" It uses an artificial example of an 'a' with 4 combining chars:
- " 'a' U+0061 Dec:97 LATIN SMALL LETTER A &#x61; /\%u61\Z "\u0061"
+ " 'a' U+0061 Dec:97 LATIN SMALL LETTER A &#x61; /\%u61\Z "\u0061"
" ' ̀' U+0300 Dec:768 COMBINING GRAVE ACCENT &#x300; /\%u300\Z "\u0300"
" ' ́' U+0301 Dec:769 COMBINING ACUTE ACCENT &#x301; /\%u301\Z "\u0301"
" ' ̇' U+0307 Dec:775 COMBINING DOT ABOVE &#x307; /\%u307\Z "\u0307"
- " ' ̣' U+0323 Dec:803 COMBINING DOT BELOW &#x323; /\%u323 "\u0323"
+ " ' ̣' U+0323 Dec:803 COMBINING DOT BELOW &#x323; /\%u323 "\u0323"
" Those should also appear on the commandline
CheckOption incsearch
diff --git a/src/nvim/testdir/test_search_stat.vim b/src/nvim/testdir/test_search_stat.vim
index 335a51268d..d950626615 100644
--- a/src/nvim/testdir/test_search_stat.vim
+++ b/src/nvim/testdir/test_search_stat.vim
@@ -1,14 +1,15 @@
" Tests for search_stats, when "S" is not in 'shortmess'
-source screendump.vim
source check.vim
+source screendump.vim
func Test_search_stat()
new
set shortmess-=S
" Append 50 lines with text to search for, "foobar" appears 20 times
call append(0, repeat(['foobar', 'foo', 'fooooobar', 'foba', 'foobar'], 10))
- call nvim_win_set_cursor(0, [1, 0])
+
+ call cursor(1, 1)
" searchcount() returns an empty dictionary when previous pattern was not set
call assert_equal({}, searchcount(#{pattern: ''}))
@@ -45,7 +46,6 @@ func Test_search_stat()
\ searchcount(#{pattern: 'fooooobar', maxcount: 1}))
" match at second line
- call cursor(1, 1)
let messages_before = execute('messages')
let @/ = 'fo*\(bar\?\)\?'
let g:a = execute(':unsilent :norm! n')
@@ -73,7 +73,6 @@ func Test_search_stat()
let stat = '\[2/50\]'
let g:a = execute(':unsilent :norm! n')
call assert_notmatch(pat .. stat, g:a)
- call writefile(getline(1, '$'), 'sample.txt')
" n does not update search stat
call assert_equal(
\ #{current: 50, exact_match: 1, total: 50, incomplete: 0, maxcount: 99},
@@ -263,6 +262,34 @@ func Test_searchcount_fails()
call assert_fails('echo searchcount("boo!")', 'E715:')
endfunc
+func Test_searchcount_in_statusline()
+ CheckScreendump
+
+ let lines =<< trim END
+ set shortmess-=S
+ call append(0, 'this is something')
+ function TestSearchCount() abort
+ let search_count = searchcount()
+ if !empty(search_count)
+ return '[' . search_count.current . '/' . search_count.total . ']'
+ else
+ return ''
+ endif
+ endfunction
+ set hlsearch
+ set laststatus=2 statusline+=%{TestSearchCount()}
+ END
+ call writefile(lines, 'Xsearchstatusline')
+ let buf = RunVimInTerminal('-S Xsearchstatusline', #{rows: 10})
+ call TermWait(buf)
+ call term_sendkeys(buf, "/something")
+ call VerifyScreenDump(buf, 'Test_searchstat_4', {})
+
+ call term_sendkeys(buf, "\<Esc>")
+ call StopVimInTerminal(buf)
+ call delete('Xsearchstatusline')
+endfunc
+
func Test_search_stat_foldopen()
CheckScreendump
@@ -320,30 +347,29 @@ func! Test_search_stat_screendump()
call delete('Xsearchstat')
endfunc
-func Test_searchcount_in_statusline()
+func Test_search_stat_then_gd()
CheckScreendump
let lines =<< trim END
+ call setline(1, ['int cat;', 'int dog;', 'cat = dog;'])
set shortmess-=S
- call append(0, 'this is something')
- function TestSearchCount() abort
- let search_count = searchcount()
- if !empty(search_count)
- return '[' . search_count.current . '/' . search_count.total . ']'
- else
- return ''
- endif
- endfunction
set hlsearch
- set laststatus=2 statusline+=%{TestSearchCount()}
END
- call writefile(lines, 'Xsearchstatusline')
- let buf = RunVimInTerminal('-S Xsearchstatusline', #{rows: 10})
+ call writefile(lines, 'Xsearchstatgd')
+
+ let buf = RunVimInTerminal('-S Xsearchstatgd', #{rows: 10})
+ call term_sendkeys(buf, "/dog\<CR>")
call TermWait(buf)
- call term_sendkeys(buf, "/something")
- call VerifyScreenDump(buf, 'Test_searchstat_4', {})
+ call VerifyScreenDump(buf, 'Test_searchstatgd_1', {})
+
+ call term_sendkeys(buf, "G0gD")
+ call TermWait(buf)
+ call VerifyScreenDump(buf, 'Test_searchstatgd_2', {})
- call term_sendkeys(buf, "\<Esc>")
call StopVimInTerminal(buf)
- call delete('Xsearchstatusline')
+ call delete('Xsearchstatgd')
endfunc
+
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_selectmode.vim b/src/nvim/testdir/test_selectmode.vim
new file mode 100644
index 0000000000..b483841060
--- /dev/null
+++ b/src/nvim/testdir/test_selectmode.vim
@@ -0,0 +1,57 @@
+" Test for Select-mode
+
+source shared.vim
+
+" Test for selecting a register with CTRL-R
+func Test_selectmode_register()
+ new
+
+ " Default behavior: use unnamed register
+ call setline(1, 'foo')
+ call setreg('"', 'bar')
+ call setreg('a', 'baz')
+ exe ":norm! v\<c-g>a"
+ call assert_equal(getline('.'), 'aoo')
+ call assert_equal('f', getreg('"'))
+ call assert_equal('baz', getreg('a'))
+
+ " Use the black hole register
+ call setline(1, 'foo')
+ call setreg('"', 'bar')
+ call setreg('a', 'baz')
+ exe ":norm! v\<c-g>\<c-r>_a"
+ call assert_equal(getline('.'), 'aoo')
+ call assert_equal('bar', getreg('"'))
+ call assert_equal('baz', getreg('a'))
+
+ " Invalid register: use unnamed register
+ call setline(1, 'foo')
+ call setreg('"', 'bar')
+ call setreg('a', 'baz')
+ exe ":norm! v\<c-g>\<c-r>?a"
+ call assert_equal(getline('.'), 'aoo')
+ call assert_equal('f', getreg('"'))
+ call assert_equal('baz', getreg('a'))
+
+ " Use unnamed register
+ call setline(1, 'foo')
+ call setreg('"', 'bar')
+ call setreg('a', 'baz')
+ exe ":norm! v\<c-g>\<c-r>\"a"
+ call assert_equal(getline('.'), 'aoo')
+ call assert_equal('f', getreg('"'))
+ call assert_equal('baz', getreg('a'))
+
+ " use specicifed register, unnamed register is also written
+ call setline(1, 'foo')
+ call setreg('"', 'bar')
+ call setreg('a', 'baz')
+ exe ":norm! v\<c-g>\<c-r>aa"
+ call assert_equal(getline('.'), 'aoo')
+ call assert_equal('f', getreg('"'))
+ call assert_equal('f', getreg('a'))
+
+ bw!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_source_utf8.vim b/src/nvim/testdir/test_source_utf8.vim
index e93ea29dff..66fabe0442 100644
--- a/src/nvim/testdir/test_source_utf8.vim
+++ b/src/nvim/testdir/test_source_utf8.vim
@@ -1,4 +1,5 @@
" Test the :source! command
+source check.vim
func Test_source_utf8()
" check that sourcing a script with 0x80 as second byte works
@@ -31,24 +32,24 @@ endfunc
" Test for sourcing a file with CTRL-V's at the end of the line
func Test_source_ctrl_v()
- call writefile(['map __1 afirst',
- \ 'map __2 asecond',
- \ 'map __3 athird',
- \ 'map __4 afourth',
- \ 'map __5 afifth',
- \ "map __1 asd\<C-V>",
- \ "map __2 asd\<C-V>\<C-V>",
- \ "map __3 asd\<C-V>\<C-V>",
- \ "map __4 asd\<C-V>\<C-V>\<C-V>",
- \ "map __5 asd\<C-V>\<C-V>\<C-V>",
- \ ], 'Xtestfile')
+ call writefile(['map __1 afirst',
+ \ 'map __2 asecond',
+ \ 'map __3 athird',
+ \ 'map __4 afourth',
+ \ 'map __5 afifth',
+ \ "map __1 asd\<C-V>",
+ \ "map __2 asd\<C-V>\<C-V>",
+ \ "map __3 asd\<C-V>\<C-V>",
+ \ "map __4 asd\<C-V>\<C-V>\<C-V>",
+ \ "map __5 asd\<C-V>\<C-V>\<C-V>",
+ \ ], 'Xtestfile')
source Xtestfile
enew!
exe "normal __1\<Esc>\<Esc>__2\<Esc>__3\<Esc>\<Esc>__4\<Esc>__5\<Esc>"
exe "%s/\<C-J>/0/g"
call assert_equal(['sd',
- \ "map __2 asd\<Esc>secondsd\<Esc>sd0map __5 asd0fifth"],
- \ getline(1, 2))
+ \ "map __2 asd\<Esc>secondsd\<Esc>sd0map __5 asd0fifth"],
+ \ getline(1, 2))
enew!
call delete('Xtestfile')
diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim
index cf0faeee31..1ecb5c8070 100644
--- a/src/nvim/testdir/test_spell.vim
+++ b/src/nvim/testdir/test_spell.vim
@@ -768,6 +768,14 @@ func Test_spell_screendump()
call delete('XtestSpell')
endfunc
+func Test_spell_single_word()
+ new
+ silent! norm 0R00
+ spell!
+ silent 0norm 0r$ Dvz=
+ bwipe!
+endfunc
+
let g:test_data_aff1 = [
\"SET ISO8859-1",
\"TRY esianrtolcdugmphbyfvkwjkqxz-\xEB\xE9\xE8\xEA\xEF\xEE\xE4\xE0\xE2\xF6\xFC\xFB'ESIANRTOLCDUGMPHBYFVKWJKQXZ",
diff --git a/src/nvim/testdir/test_stat.vim b/src/nvim/testdir/test_stat.vim
index 170358e023..d3059664e9 100644
--- a/src/nvim/testdir/test_stat.vim
+++ b/src/nvim/testdir/test_stat.vim
@@ -1,11 +1,13 @@
" Tests for stat functions and checktime
+source check.vim
+
func CheckFileTime(doSleep)
let fnames = ['Xtest1.tmp', 'Xtest2.tmp', 'Xtest3.tmp']
let times = []
let result = 0
- " Use three files istead of localtim(), with a network filesystem the file
+ " Use three files instead of localtim(), with a network filesystem the file
" times may differ at bit
let fl = ['Hello World!']
for fname in fnames
@@ -74,6 +76,44 @@ func Test_checktime()
call delete(fname)
endfunc
+func Test_checktime_fast()
+ CheckFeature nanotime
+
+ let fname = 'Xtest.tmp'
+
+ let fl = ['Hello World!']
+ call writefile(fl, fname)
+ set autoread
+ exec 'e' fname
+ let fl = readfile(fname)
+ let fl[0] .= ' - checktime'
+ sleep 10m " make test less flaky in Nvim
+ call writefile(fl, fname)
+ checktime
+ call assert_equal(fl[0], getline(1))
+
+ call delete(fname)
+endfunc
+
+func Test_autoread_fast()
+ CheckFeature nanotime
+
+ " this is timing sensitive
+ let g:test_is_flaky = 1
+
+ new Xautoread
+ setlocal autoread
+ call setline(1, 'foo')
+ w!
+ sleep 10m
+ call writefile(['bar'], 'Xautoread')
+ sleep 10m
+ checktime
+ call assert_equal('bar', trim(getline(1)))
+
+ call delete('Xautoread')
+endfunc
+
func Test_autoread_file_deleted()
new Xautoread
set autoread
diff --git a/src/nvim/testdir/test_statusline.vim b/src/nvim/testdir/test_statusline.vim
index a3e4dcdd25..fad13e3340 100644
--- a/src/nvim/testdir/test_statusline.vim
+++ b/src/nvim/testdir/test_statusline.vim
@@ -186,7 +186,16 @@ func Test_statusline()
set virtualedit=all
norm 10|
call assert_match('^10,-10\s*$', s:get_statusline())
+ set list
+ call assert_match('^10,-10\s*$', s:get_statusline())
set virtualedit&
+ exe "norm A\<Tab>\<Tab>a\<Esc>"
+ " In list mode a <Tab> is shown as "^I", which is 2-wide.
+ call assert_match('^9,-9\s*$', s:get_statusline())
+ set list&
+ " Now the second <Tab> ends at the 16th screen column.
+ call assert_match('^17,-17\s*$', s:get_statusline())
+ undo
" %w: Preview window flag, text is "[Preview]".
" %W: Preview window flag, text is ",PRV".
@@ -498,5 +507,20 @@ func Test_statusline_after_split_vsplit()
set ls& stl&
endfunc
+" Test using a multibyte character for 'stl' and 'stlnc' items in 'fillchars'
+" with a custom 'statusline'
+func Test_statusline_mbyte_fillchar()
+ only
+ set laststatus=2
+ set fillchars=vert:\|,fold:-,stl:━,stlnc:═
+ set statusline=a%=b
+ call assert_match('^a\+━\+b$', s:get_statusline())
+ vnew
+ call assert_match('^a\+━\+b━a\+═\+b$', s:get_statusline())
+ wincmd w
+ call assert_match('^a\+═\+b═a\+━\+b$', s:get_statusline())
+ set statusline& fillchars& laststatus&
+ %bw!
+endfunc
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim
index 20b760ac15..dbb792d2b0 100644
--- a/src/nvim/testdir/test_substitute.vim
+++ b/src/nvim/testdir/test_substitute.vim
@@ -51,10 +51,12 @@ func Test_substitute_variants()
\ { 'cmd': ':s/t/r/cg', 'exp': 'Tesring srring', 'prompt': 'a' },
\ { 'cmd': ':s/t/r/ci', 'exp': 'resting string', 'prompt': 'y' },
\ { 'cmd': ':s/t/r/cI', 'exp': 'Tesring string', 'prompt': 'y' },
+ \ { 'cmd': ':s/t/r/c', 'exp': 'Testing string', 'prompt': 'n' },
\ { 'cmd': ':s/t/r/cn', 'exp': ln },
\ { 'cmd': ':s/t/r/cp', 'exp': 'Tesring string', 'prompt': 'y' },
\ { 'cmd': ':s/t/r/cl', 'exp': 'Tesring string', 'prompt': 'y' },
\ { 'cmd': ':s/t/r/gc', 'exp': 'Tesring srring', 'prompt': 'a' },
+ \ { 'cmd': ':s/i/I/gc', 'exp': 'TestIng string', 'prompt': 'l' },
\ { 'cmd': ':s/foo/bar/ge', 'exp': ln },
\ { 'cmd': ':s/t/r/g', 'exp': 'Tesring srring' },
\ { 'cmd': ':s/t/r/gi', 'exp': 'resring srring' },
@@ -86,6 +88,7 @@ func Test_substitute_variants()
\ { 'cmd': ':s//r/rp', 'exp': 'Testr string' },
\ { 'cmd': ':s//r/rl', 'exp': 'Testr string' },
\ { 'cmd': ':s//r/r', 'exp': 'Testr string' },
+ \ { 'cmd': ':s/i/I/gc', 'exp': 'Testing string', 'prompt': 'q' },
\]
for var in variants
@@ -105,7 +108,7 @@ func Test_substitute_variants()
call assert_equal(var.exp, getline('.'), msg)
endfor
endfor
-endfunction
+endfunc
" Test the l, p, # flags.
func Test_substitute_flags_lp()
@@ -384,6 +387,10 @@ func Test_substitute_join()
call assert_equal(["foo\tbarbar\<C-H>foo"], getline(1, '$'))
call assert_equal('\n', histget("search", -1))
+ call setline(1, ['foo', 'bar', 'baz', 'qux'])
+ call execute('1,2s/\n//')
+ call assert_equal(['foobarbaz', 'qux'], getline(1, '$'))
+
bwipe!
endfunc
@@ -398,6 +405,11 @@ func Test_substitute_count()
call assert_fails('s/foo/bar/0', 'E939:')
+ call setline(1, ['foo foo', 'foo foo', 'foo foo', 'foo foo', 'foo foo'])
+ 2,4s/foo/bar/ 10
+ call assert_equal(['foo foo', 'foo foo', 'foo foo', 'bar foo', 'bar foo'],
+ \ getline(1, '$'))
+
bwipe!
endfunc
@@ -416,6 +428,10 @@ func Test_substitute_flag_n()
" No substitution should have been done.
call assert_equal(lines, getline(1, '$'))
+ %delete _
+ call setline(1, ['A', 'Bar', 'Baz'])
+ call assert_equal("\n1 match on 1 line", execute('s/\nB\@=//gn'))
+
bwipe!
endfunc
@@ -749,6 +765,45 @@ func Test_sub_beyond_end()
bwipe!
endfunc
+" Test for repeating last substitution using :~ and :&r
+func Test_repeat_last_sub()
+ new
+ call setline(1, ['blue green yellow orange white'])
+ s/blue/red/
+ let @/ = 'yellow'
+ ~
+ let @/ = 'white'
+ :&r
+ let @/ = 'green'
+ s//gray
+ call assert_equal('red gray red orange red', getline(1))
+ close!
+endfunc
+
+" Test for Vi compatible substitution:
+" \/{string}/, \?{string}? and \&{string}&
+func Test_sub_vi_compatibility()
+ new
+ call setline(1, ['blue green yellow orange blue'])
+ let @/ = 'orange'
+ s\/white/
+ let @/ = 'blue'
+ s\?amber?
+ let @/ = 'white'
+ s\&green&
+ call assert_equal('amber green yellow white green', getline(1))
+ close!
+endfunc
+
+" Test for substitute with the new text longer than the original text
+func Test_sub_expand_text()
+ new
+ call setline(1, 'abcabcabcabcabcabcabcabc')
+ s/b/\=repeat('B', 10)/g
+ call assert_equal(repeat('aBBBBBBBBBBc', 8), getline(1))
+ close!
+endfunc
+
func Test_submatch_list_concatenate()
let pat = 'A\(.\)'
let Rep = {-> string([submatch(0, 1)] + [[submatch(1)]])}
diff --git a/src/nvim/testdir/test_suspend.vim b/src/nvim/testdir/test_suspend.vim
index 4b3bd5eadf..bf88bd4453 100644
--- a/src/nvim/testdir/test_suspend.vim
+++ b/src/nvim/testdir/test_suspend.vim
@@ -26,8 +26,8 @@ func Test_suspend()
" Wait for shell prompt.
call WaitForAssert({-> assert_match('[$#] $', term_getline(buf, '.'))})
- call term_sendkeys(buf, v:progpath
- \ . " --clean -X"
+ call term_sendkeys(buf, GetVimCommandClean()
+ \ . " -X"
\ . " -c 'set nu'"
\ . " -c 'call setline(1, \"foo\")'"
\ . " Xfoo\<CR>")
diff --git a/src/nvim/testdir/test_swap.vim b/src/nvim/testdir/test_swap.vim
index b3018b2b0d..b180f27685 100644
--- a/src/nvim/testdir/test_swap.vim
+++ b/src/nvim/testdir/test_swap.vim
@@ -1,6 +1,7 @@
" Tests for the swap feature
source check.vim
+source shared.vim
func s:swapname()
return trim(execute('swapname'))
@@ -198,14 +199,17 @@ func Test_swapfile_delete()
quit
call assert_equal(fnamemodify(swapfile_name, ':t'), fnamemodify(s:swapname, ':t'))
- " Write the swapfile with a modified PID, now it will be automatically
- " deleted. Process one should never be Vim.
- let swapfile_bytes[24:27] = 0z01000000
- call writefile(swapfile_bytes, swapfile_name)
- let s:swapname = ''
- split XswapfileText
- quit
- call assert_equal('', s:swapname)
+ " This test won't work as root because root can successfully run kill(1, 0)
+ if !IsRoot()
+ " Write the swapfile with a modified PID, now it will be automatically
+ " deleted. Process one should never be Vim.
+ let swapfile_bytes[24:27] = 0z01000000
+ call writefile(swapfile_bytes, swapfile_name)
+ let s:swapname = ''
+ split XswapfileText
+ quit
+ call assert_equal('', s:swapname)
+ endif
" Now set the modified flag, the swap file will not be deleted
let swapfile_bytes[28 + 80 + 899] = 0x55
diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim
index 757866f5dc..b047b53b6f 100644
--- a/src/nvim/testdir/test_syntax.vim
+++ b/src/nvim/testdir/test_syntax.vim
@@ -728,6 +728,56 @@ func Test_syntax_foldlevel()
quit!
endfunc
+func Test_search_syntax_skip()
+ new
+ let lines =<< trim END
+
+ /* This is VIM */
+ Another Text for VIM
+ let a = "VIM"
+ END
+ call setline(1, lines)
+ syntax on
+ syntax match Comment "^/\*.*\*/"
+ syntax match String '".*"'
+
+ " Skip argument using string evaluation.
+ 1
+ call search('VIM', 'w', '', 0, 'synIDattr(synID(line("."), col("."), 1), "name") =~? "comment"')
+ call assert_equal('Another Text for VIM', getline('.'))
+ 1
+ call search('VIM', 'w', '', 0, 'synIDattr(synID(line("."), col("."), 1), "name") !~? "string"')
+ call assert_equal(' let a = "VIM"', getline('.'))
+
+ " Skip argument using Lambda.
+ 1
+ call search('VIM', 'w', '', 0, { -> synIDattr(synID(line("."), col("."), 1), "name") =~? "comment"})
+ call assert_equal('Another Text for VIM', getline('.'))
+
+ 1
+ call search('VIM', 'w', '', 0, { -> synIDattr(synID(line("."), col("."), 1), "name") !~? "string"})
+ call assert_equal(' let a = "VIM"', getline('.'))
+
+ " Skip argument using funcref.
+ func InComment()
+ return synIDattr(synID(line("."), col("."), 1), "name") =~? "comment"
+ endfunc
+ func InString()
+ return synIDattr(synID(line("."), col("."), 1), "name") !~? "string"
+ endfunc
+ 1
+ call search('VIM', 'w', '', 0, function('InComment'))
+ call assert_equal('Another Text for VIM', getline('.'))
+
+ 1
+ call search('VIM', 'w', '', 0, function('InString'))
+ call assert_equal(' let a = "VIM"', getline('.'))
+
+ delfunc InComment
+ delfunc InString
+ bwipe!
+endfunc
+
func Test_syn_include_contains_TOP()
let l:case = "TOP in included syntax means its group list name"
new
diff --git a/src/nvim/testdir/test_system.vim b/src/nvim/testdir/test_system.vim
index 1858b48807..18692f42c9 100644
--- a/src/nvim/testdir/test_system.vim
+++ b/src/nvim/testdir/test_system.vim
@@ -46,15 +46,15 @@ func Test_System()
bwipe!
call assert_fails('call system("wc -l", 99999)', 'E86:')
-endfunction
+endfunc
-function! Test_system_exmode()
+func Test_system_exmode()
if has('unix') " echo $? only works on Unix
- let cmd = ' -es --headless -u NONE -c "source Xscript" +q; echo "result=$?"'
+ let cmd = ' -es -c "source Xscript" +q; echo "result=$?"'
" Need to put this in a script, "catch" isn't found after an unknown
" function.
call writefile(['try', 'call doesnotexist()', 'catch', 'endtry'], 'Xscript')
- let a = system(v:progpath . cmd)
+ let a = system(GetVimCommand() . cmd)
call assert_match('result=0', a)
call assert_equal(0, v:shell_error)
endif
@@ -62,33 +62,33 @@ function! Test_system_exmode()
" Error before try does set error flag.
call writefile(['call nosuchfunction()', 'try', 'call doesnotexist()', 'catch', 'endtry'], 'Xscript')
if has('unix') " echo $? only works on Unix
- let a = system(v:progpath . cmd)
+ let a = system(GetVimCommand() . cmd)
call assert_notequal('0', a[0])
endif
- let cmd = ' -es --headless -u NONE -c "source Xscript" +q'
- let a = system(v:progpath . cmd)
+ let cmd = ' -es -c "source Xscript" +q'
+ let a = system(GetVimCommand() . cmd)
call assert_notequal(0, v:shell_error)
call delete('Xscript')
if has('unix') " echo $? only works on Unix
- let cmd = ' -es --headless -u NONE -c "call doesnotexist()" +q; echo $?'
- let a = system(v:progpath. cmd)
+ let cmd = ' -es -c "call doesnotexist()" +q; echo $?'
+ let a = system(GetVimCommand() . cmd)
call assert_notequal(0, a[0])
endif
- let cmd = ' -es --headless -u NONE -c "call doesnotexist()" +q'
- let a = system(v:progpath. cmd)
+ let cmd = ' -es -c "call doesnotexist()" +q'
+ let a = system(GetVimCommand(). cmd)
call assert_notequal(0, v:shell_error)
if has('unix') " echo $? only works on Unix
- let cmd = ' -es --headless -u NONE -c "call doesnotexist()|let a=1" +q; echo $?'
- let a = system(v:progpath. cmd)
+ let cmd = ' -es -c "call doesnotexist()|let a=1" +q; echo $?'
+ let a = system(GetVimCommand() . cmd)
call assert_notequal(0, a[0])
endif
- let cmd = ' -es --headless -u NONE -c "call doesnotexist()|let a=1" +q'
- let a = system(v:progpath. cmd)
+ let cmd = ' -es -c "call doesnotexist()|let a=1" +q'
+ let a = system(GetVimCommand() . cmd)
call assert_notequal(0, v:shell_error)
endfunc
diff --git a/src/nvim/testdir/test_tabline.vim b/src/nvim/testdir/test_tabline.vim
index 117d962d08..3a18206078 100644
--- a/src/nvim/testdir/test_tabline.vim
+++ b/src/nvim/testdir/test_tabline.vim
@@ -86,6 +86,17 @@ func Test_tabline_empty_group()
set tabline=
endfunc
+" When there are exactly 20 tabline format items (the exact size of the
+" initial tabline items array), test that we don't write beyond the size
+" of the array.
+func Test_tabline_20_format_items_no_overrun()
+ set showtabline=2
+
+ let tabline = repeat('%#StatColorHi2#', 20)
+ let &tabline = tabline
+ redrawtabline
+ set showtabline& tabline&
+endfunc
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim
index 2aa04df42a..e0b05edf15 100644
--- a/src/nvim/testdir/test_tagjump.vim
+++ b/src/nvim/testdir/test_tagjump.vim
@@ -641,7 +641,7 @@ func Test_tag_envvar()
endfunc
" Test for :ptag
-func Test_ptag()
+func Test_tag_preview()
call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
\ "second\tXfile1\t2",
\ "third\tXfile1\t3",],
@@ -655,11 +655,19 @@ func Test_ptag()
call assert_equal(2, winnr('$'))
call assert_equal(1, getwinvar(1, '&previewwindow'))
call assert_equal(0, getwinvar(2, '&previewwindow'))
- wincmd w
+ wincmd P
call assert_equal(3, line('.'))
" jump to the tag again
+ wincmd w
ptag third
+ wincmd P
+ call assert_equal(3, line('.'))
+
+ " jump to the newer tag
+ wincmd w
+ ptag
+ wincmd P
call assert_equal(3, line('.'))
" close the preview window
@@ -829,8 +837,8 @@ func Test_tag_last_search_pat()
%bwipe
endfunc
-" Test for jumping to a tag when the tag stack is full
-func Test_tag_stack_full()
+" Tag stack tests
+func Test_tag_stack()
let l = []
for i in range(10, 31)
let l += ["var" .. i .. "\tXfoo\t/^int var" .. i .. ";$/"]
@@ -844,6 +852,7 @@ func Test_tag_stack_full()
endfor
call writefile(l, 'Xfoo')
+ " Jump to a tag when the tag stack is full. Oldest entry should be removed.
enew
for i in range(10, 30)
exe "tag var" .. i
@@ -856,9 +865,15 @@ func Test_tag_stack_full()
call assert_equal('var12', l.items[0].tagname)
call assert_equal('var31', l.items[19].tagname)
- " Jump from the top of the stack
+ " Use tnext with a single match
+ call assert_fails('tnext', 'E427:')
+
+ " Jump to newest entry from the top of the stack
call assert_fails('tag', 'E556:')
+ " Pop with zero count from the top of the stack
+ call assert_fails('0pop', 'E556:')
+
" Pop from an unsaved buffer
enew!
call append(1, "sample text")
@@ -869,6 +884,13 @@ func Test_tag_stack_full()
" Pop all the entries in the tag stack
call assert_fails('30pop', 'E555:')
+ " Pop with a count when already at the bottom of the stack
+ call assert_fails('exe "normal 4\<C-T>"', 'E555:')
+ call assert_equal(1, gettagstack().curidx)
+
+ " Jump to newest entry from the bottom of the stack with zero count
+ call assert_fails('0tag', 'E555:')
+
" Pop the tag stack when it is empty
call settagstack(1, {'items' : []})
call assert_fails('pop', 'E73:')
@@ -895,6 +917,7 @@ func Test_tag_multimatch()
[CODE]
call writefile(code, 'Xfoo')
+ call settagstack(1, {'items' : []})
tag first
tlast
call assert_equal(3, line('.'))
@@ -903,6 +926,151 @@ func Test_tag_multimatch()
call assert_equal(1, line('.'))
call assert_fails('tprev', 'E425:')
+ tlast
+ call feedkeys("5\<CR>", 't')
+ tselect first
+ call assert_equal(2, gettagstack().curidx)
+
+ set ignorecase
+ tag FIRST
+ tnext
+ call assert_equal(2, line('.'))
+ set ignorecase&
+
+ call delete('Xtags')
+ call delete('Xfoo')
+ set tags&
+ %bwipe
+endfunc
+
+" Test for previewing multiple matching tags
+func Test_preview_tag_multimatch()
+ call writefile([
+ \ "!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "first\tXfoo\t1",
+ \ "first\tXfoo\t2",
+ \ "first\tXfoo\t3"],
+ \ 'Xtags')
+ set tags=Xtags
+ let code =<< trim [CODE]
+ int first() {}
+ int first() {}
+ int first() {}
+ [CODE]
+ call writefile(code, 'Xfoo')
+
+ enew | only
+ ptag first
+ ptlast
+ wincmd P
+ call assert_equal(3, line('.'))
+ wincmd w
+ call assert_fails('ptnext', 'E428:')
+ ptprev
+ wincmd P
+ call assert_equal(2, line('.'))
+ wincmd w
+ ptfirst
+ wincmd P
+ call assert_equal(1, line('.'))
+ wincmd w
+ call assert_fails('ptprev', 'E425:')
+ ptnext
+ wincmd P
+ call assert_equal(2, line('.'))
+ wincmd w
+ ptlast
+ call feedkeys("5\<CR>", 't')
+ ptselect first
+ wincmd P
+ call assert_equal(3, line('.'))
+
+ pclose
+
+ call delete('Xtags')
+ call delete('Xfoo')
+ set tags&
+ %bwipe
+endfunc
+
+" Test for jumping to multiple matching tags across multiple :tags commands
+func Test_tnext_multimatch()
+ call writefile([
+ \ "!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "first\tXfoo1\t1",
+ \ "first\tXfoo2\t1",
+ \ "first\tXfoo3\t1"],
+ \ 'Xtags')
+ set tags=Xtags
+ let code =<< trim [CODE]
+ int first() {}
+ [CODE]
+ call writefile(code, 'Xfoo1')
+ call writefile(code, 'Xfoo2')
+ call writefile(code, 'Xfoo3')
+
+ tag first
+ tag first
+ pop
+ tnext
+ tnext
+ call assert_fails('tnext', 'E428:')
+
+ call delete('Xtags')
+ call delete('Xfoo1')
+ call delete('Xfoo2')
+ call delete('Xfoo3')
+ set tags&
+ %bwipe
+endfunc
+
+" Test for jumping to multiple matching tags in non-existing files
+func Test_multimatch_non_existing_files()
+ call writefile([
+ \ "!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "first\tXfoo1\t1",
+ \ "first\tXfoo2\t1",
+ \ "first\tXfoo3\t1"],
+ \ 'Xtags')
+ set tags=Xtags
+
+ call settagstack(1, {'items' : []})
+ call assert_fails('tag first', 'E429:')
+ call assert_equal(3, gettagstack().items[0].matchnr)
+
+ call delete('Xtags')
+ set tags&
+ %bwipe
+endfunc
+
+func Test_tselect_listing()
+ call writefile([
+ \ "!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "first\tXfoo\t1" .. ';"' .. "\tv\ttyperef:typename:int\tfile:",
+ \ "first\tXfoo\t2" .. ';"' .. "\tv\ttyperef:typename:char\tfile:"],
+ \ 'Xtags')
+ set tags=Xtags
+
+ let code =<< trim [CODE]
+ static int first;
+ static char first;
+ [CODE]
+ call writefile(code, 'Xfoo')
+
+ call feedkeys("\<CR>", "t")
+ let l = split(execute("tselect first"), "\n")
+ let expected =<< [DATA]
+ # pri kind tag file
+ 1 FS v first Xfoo
+ typeref:typename:int
+ 1
+ 2 FS v first Xfoo
+ typeref:typename:char
+ 2
+Type number and <Enter> (q or empty cancels):
+[DATA]
+ call assert_equal(expected, l)
+
call delete('Xtags')
call delete('Xfoo')
set tags&
diff --git a/src/nvim/testdir/test_textformat.vim b/src/nvim/testdir/test_textformat.vim
index bf0706a0c2..e9f846af7b 100644
--- a/src/nvim/testdir/test_textformat.vim
+++ b/src/nvim/testdir/test_textformat.vim
@@ -196,6 +196,130 @@ func Test_text_format()
enew!
endfunc
+func Test_format_c_comment()
+ new
+ setl ai cindent tw=40 et fo=croql
+ let text =<< trim END
+ var = 2345; // asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf
+ END
+ call setline(1, text)
+ normal gql
+ let expected =<< trim END
+ var = 2345; // asdf asdf asdf asdf asdf
+ // asdf asdf asdf asdf asdf
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ %del
+ let text =<< trim END
+ var = 2345; // asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf
+ END
+ call setline(1, text)
+ normal gql
+ let expected =<< trim END
+ var = 2345; // asdf asdf asdf asdf asdf
+ // asdf asdf asdf asdf asdf
+ // asdf asdf
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ %del
+ let text =<< trim END
+ #if 0 // This is another long end of
+ // line comment that
+ // wraps.
+ END
+ call setline(1, text)
+ normal gq2j
+ let expected =<< trim END
+ #if 0 // This is another long
+ // end of line comment
+ // that wraps.
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ " Using either "o" or "O" repeats a line comment occupying a whole line.
+ %del
+ let text =<< trim END
+ nop;
+ // This is a comment
+ val = val;
+ END
+ call setline(1, text)
+ normal 2Go
+ let expected =<< trim END
+ nop;
+ // This is a comment
+ //
+ val = val;
+ END
+ call assert_equal(expected, getline(1, '$'))
+ normal 2GO
+ let expected =<< trim END
+ nop;
+ //
+ // This is a comment
+ //
+ val = val;
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ " Using "o" repeats a line comment after a statement, "O" does not.
+ %del
+ let text =<< trim END
+ nop;
+ val = val; // This is a comment
+ END
+ call setline(1, text)
+ normal 2Go
+ let expected =<< trim END
+ nop;
+ val = val; // This is a comment
+ //
+ END
+ call assert_equal(expected, getline(1, '$'))
+ normal 2GO
+ let expected =<< trim END
+ nop;
+
+ val = val; // This is a comment
+ //
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ " Using "o" does not repeat a comment in a string
+ %del
+ let text =<< trim END
+ nop;
+ val = " // This is not a comment";
+ END
+ call setline(1, text)
+ normal 2Gox
+ let expected =<< trim END
+ nop;
+ val = " // This is not a comment";
+ x
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ " Using CTRL-U after "o" fixes the indent
+ %del
+ let text =<< trim END
+ {
+ val = val; // This is a comment
+ END
+ call setline(1, text)
+ exe "normal! 2Go\<C-U>x\<Esc>"
+ let expected =<< trim END
+ {
+ val = val; // This is a comment
+ x
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+endfunc
+
" Tests for :right, :center and :left on text with embedded TAB.
func Test_format_align()
enew!
@@ -433,6 +557,21 @@ func Test_format_align()
call assert_equal("\t\t Vim", getline(1))
q!
+ " align text with 'rightleft'
+ if has('rightleft')
+ new
+ call setline(1, 'Vim')
+ setlocal rightleft
+ left 20
+ setlocal norightleft
+ call assert_equal("\t\t Vim", getline(1))
+ setlocal rightleft
+ right
+ setlocal norightleft
+ call assert_equal("Vim", getline(1))
+ close!
+ endif
+
set tw&
endfunc
diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim
index aae315b2c5..09eed4e10d 100644
--- a/src/nvim/testdir/test_timers.vim
+++ b/src/nvim/testdir/test_timers.vim
@@ -379,4 +379,27 @@ func Test_timer_invalid_callback()
call assert_fails('call timer_start(0, "0")', 'E921')
endfunc
+func Test_timer_using_win_execute_undo_sync()
+ let bufnr1 = bufnr()
+ new
+ let g:bufnr2 = bufnr()
+ let g:winid = win_getid()
+ exe "buffer " .. bufnr1
+ wincmd w
+ call setline(1, ['test'])
+ autocmd InsertEnter * call timer_start(100, { -> win_execute(g:winid, 'buffer ' .. g:bufnr2) })
+ call timer_start(200, { -> feedkeys("\<CR>bbbb\<Esc>") })
+ call feedkeys("Oaaaa", 'x!t')
+ " will hang here until the second timer fires
+ call assert_equal(['aaaa', 'bbbb', 'test'], getline(1, '$'))
+ undo
+ call assert_equal(['test'], getline(1, '$'))
+
+ bwipe!
+ bwipe!
+ unlet g:winid
+ unlet g:bufnr2
+ au! InsertEnter
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_usercommands.vim b/src/nvim/testdir/test_usercommands.vim
index 29e578ac6d..967ad85a64 100644
--- a/src/nvim/testdir/test_usercommands.vim
+++ b/src/nvim/testdir/test_usercommands.vim
@@ -269,10 +269,10 @@ endfunc
func Test_CmdCompletion()
call feedkeys(":com -\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"com -addr bang bar buffer complete count nargs range register', @:)
+ call assert_equal('"com -addr bang bar buffer complete count keepscript nargs range register', @:)
call feedkeys(":com -nargs=0 -\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"com -nargs=0 -addr bang bar buffer complete count nargs range register', @:)
+ call assert_equal('"com -nargs=0 -addr bang bar buffer complete count keepscript nargs range register', @:)
call feedkeys(":com -nargs=\<C-A>\<C-B>\"\<CR>", 'tx')
call assert_equal('"com -nargs=* + 0 1 ?', @:)
@@ -350,7 +350,6 @@ func Test_use_execute_in_completion()
endfunc
func Test_addr_all()
- throw 'skipped: requires patch v8.1.0341 to pass'
command! -addr=lines DoSomething let g:a1 = <line1> | let g:a2 = <line2>
%DoSomething
call assert_equal(1, g:a1)
@@ -551,3 +550,26 @@ func Test_command_list()
call assert_equal("\nNo user-defined commands found", execute(':command Xxx'))
call assert_equal("\nNo user-defined commands found", execute('command'))
endfunc
+
+func Test_delcommand_buffer()
+ command Global echo 'global'
+ command -buffer OneBuffer echo 'one'
+ new
+ command -buffer TwoBuffer echo 'two'
+ call assert_equal(0, exists(':OneBuffer'))
+ call assert_equal(2, exists(':Global'))
+ call assert_equal(2, exists(':TwoBuffer'))
+ delcommand -buffer TwoBuffer
+ call assert_equal(0, exists(':TwoBuffer'))
+ call assert_fails('delcommand -buffer Global', 'E1237:')
+ call assert_fails('delcommand -buffer OneBuffer', 'E1237:')
+ bwipe!
+ call assert_equal(2, exists(':OneBuffer'))
+ delcommand -buffer OneBuffer
+ call assert_equal(0, exists(':OneBuffer'))
+ call assert_fails('delcommand -buffer Global', 'E1237:')
+ delcommand Global
+ call assert_equal(0, exists(':Global'))
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_utf8.vim b/src/nvim/testdir/test_utf8.vim
index 0818c2e4b0..9b010a5dbc 100644
--- a/src/nvim/testdir/test_utf8.vim
+++ b/src/nvim/testdir/test_utf8.vim
@@ -1,5 +1,6 @@
" Tests for Unicode manipulations
+source check.vim
source view_util.vim
" Visual block Insert adjusts for multi-byte char
@@ -7,7 +8,7 @@ func Test_visual_block_insert()
new
call setline(1, ["aaa", "あああ", "bbb"])
exe ":norm! gg0l\<C-V>jjIx\<Esc>"
- call assert_equal(['axaa', 'xあああ', 'bxbb'], getline(1, '$'))
+ call assert_equal(['axaa', ' xあああ', 'bxbb'], getline(1, '$'))
bwipeout!
endfunc
@@ -148,4 +149,55 @@ func Test_print_overlong()
bwipe!
endfunc
+func Test_recording_with_select_mode_utf8()
+ call Run_test_recording_with_select_mode_utf8()
+endfunc
+
+func Run_test_recording_with_select_mode_utf8()
+ new
+
+ " No escaping
+ call feedkeys("qacc12345\<Esc>gH哦\<Esc>q", "tx")
+ call assert_equal("哦", getline(1))
+ call assert_equal("cc12345\<Esc>gH哦\<Esc>", @a)
+ call setline(1, 'asdf')
+ normal! @a
+ call assert_equal("哦", getline(1))
+
+ " 固 is 0xE5 0x9B 0xBA where 0x9B is CSI
+ call feedkeys("qacc12345\<Esc>gH固\<Esc>q", "tx")
+ call assert_equal("固", getline(1))
+ call assert_equal("cc12345\<Esc>gH固\<Esc>", @a)
+ call setline(1, 'asdf')
+ normal! @a
+ call assert_equal("固", getline(1))
+
+ " 四 is 0xE5 0x9B 0x9B where 0x9B is CSI
+ call feedkeys("qacc12345\<Esc>gH四\<Esc>q", "tx")
+ call assert_equal("四", getline(1))
+ call assert_equal("cc12345\<Esc>gH四\<Esc>", @a)
+ call setline(1, 'asdf')
+ normal! @a
+ call assert_equal("四", getline(1))
+
+ " 倒 is 0xE5 0x80 0x92 where 0x80 is K_SPECIAL
+ call feedkeys("qacc12345\<Esc>gH倒\<Esc>q", "tx")
+ call assert_equal("倒", getline(1))
+ call assert_equal("cc12345\<Esc>gH倒\<Esc>", @a)
+ call setline(1, 'asdf')
+ normal! @a
+ call assert_equal("倒", getline(1))
+
+ bwipe!
+endfunc
+
+" This must be done as one of the last tests, because it starts the GUI, which
+" cannot be undone.
+func Test_zz_recording_with_select_mode_utf8_gui()
+ CheckCanRunGui
+
+ gui -f
+ call Run_test_recording_with_select_mode_utf8()
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_utf8_comparisons.vim b/src/nvim/testdir/test_utf8_comparisons.vim
index fdf9d80802..f3c86b44fb 100644
--- a/src/nvim/testdir/test_utf8_comparisons.vim
+++ b/src/nvim/testdir/test_utf8_comparisons.vim
@@ -1,12 +1,12 @@
" Tests for case-insensitive UTF-8 comparisons (utf_strnicmp() in mbyte.c)
" Also test "g~ap".
-function! Ch(a, op, b, expected)
+func Ch(a, op, b, expected)
call assert_equal(eval(printf('"%s" %s "%s"', a:a, a:op, a:b)), a:expected,
\ printf('"%s" %s "%s" should return %d', a:a, a:op, a:b, a:expected))
-endfunction
+endfunc
-function! Chk(a, b, result)
+func Chk(a, b, result)
if a:result == 0
call Ch(a:a, '==?', a:b, 1)
call Ch(a:a, '!=?', a:b, 0)
@@ -86,6 +86,9 @@ endfunc
" test that g~ap changes one paragraph only.
func Test_gap()
new
- call feedkeys("iabcd\n\ndefggg0g~ap", "tx")
+ " setup text
+ call feedkeys("iabcd\<cr>\<cr>defg", "tx")
+ " modify only first line
+ call feedkeys("gg0g~ap", "tx")
call assert_equal(["ABCD", "", "defg"], getline(1,3))
endfunc
diff --git a/src/nvim/testdir/test_vartabs.vim b/src/nvim/testdir/test_vartabs.vim
index 46e0d62313..017bb6675d 100644
--- a/src/nvim/testdir/test_vartabs.vim
+++ b/src/nvim/testdir/test_vartabs.vim
@@ -135,7 +135,17 @@ func Test_vartabs()
bwipeout!
endfunc
-func! Test_vartabs_breakindent()
+func Test_retab_invalid_arg()
+ new
+ call setline(1, "\ttext")
+ retab 0
+ call assert_fails("retab -8", 'E487: Argument must be positive')
+ call assert_fails("retab 10000", 'E475:')
+ call assert_fails("retab 720575940379279360", 'E475:')
+ bwipe!
+endfunc
+
+func Test_vartabs_breakindent()
if !exists("+breakindent")
return
endif
diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim
index 34733f127f..f93eb6e274 100644
--- a/src/nvim/testdir/test_vimscript.vim
+++ b/src/nvim/testdir/test_vimscript.vim
@@ -1,6 +1,9 @@
" Test various aspects of the Vim script language.
" Most of this was formerly in test49.
+source check.vim
+source shared.vim
+
"-------------------------------------------------------------------------------
" Test environment {{{1
"-------------------------------------------------------------------------------
@@ -25,7 +28,7 @@ com! -nargs=1 Xout call Xout(<args>)
" in the variable argument list. This function is useful if similar tests are
" to be made for a ":return" from a function call or a ":finish" in a script
" file.
-function! MakeScript(funcname, ...)
+func MakeScript(funcname, ...)
let script = tempname()
execute "redir! >" . script
execute "function" a:funcname
@@ -1156,6 +1159,82 @@ func Test_type()
call assert_equal(v:t_list, type(v:_null_list))
call assert_equal(v:t_dict, type(v:_null_dict))
call assert_equal(v:t_blob, type(v:_null_blob))
+
+ call assert_equal(0, 0 + v:false)
+ call assert_equal(1, 0 + v:true)
+ " call assert_equal(0, 0 + v:none)
+ call assert_equal(0, 0 + v:null)
+
+ call assert_equal('false', '' . v:false)
+ call assert_equal('true', '' . v:true)
+ " call assert_equal('none', '' . v:none)
+ call assert_equal('null', '' . v:null)
+
+ call assert_true(v:false == 0)
+ call assert_false(v:false != 0)
+ call assert_true(v:true == 1)
+ call assert_false(v:true != 1)
+ call assert_false(v:true == v:false)
+ call assert_true(v:true != v:false)
+
+ call assert_true(v:null == 0)
+ call assert_false(v:null != 0)
+ " call assert_true(v:none == 0)
+ " call assert_false(v:none != 0)
+
+ call assert_true(v:false is v:false)
+ call assert_true(v:true is v:true)
+ " call assert_true(v:none is v:none)
+ call assert_true(v:null is v:null)
+
+ call assert_false(v:false isnot v:false)
+ call assert_false(v:true isnot v:true)
+ " call assert_false(v:none isnot v:none)
+ call assert_false(v:null isnot v:null)
+
+ call assert_false(v:false is 0)
+ call assert_false(v:true is 1)
+ call assert_false(v:true is v:false)
+ " call assert_false(v:none is 0)
+ call assert_false(v:null is 0)
+ " call assert_false(v:null is v:none)
+
+ call assert_true(v:false isnot 0)
+ call assert_true(v:true isnot 1)
+ call assert_true(v:true isnot v:false)
+ " call assert_true(v:none isnot 0)
+ call assert_true(v:null isnot 0)
+ " call assert_true(v:null isnot v:none)
+
+ call assert_equal(v:false, eval(string(v:false)))
+ call assert_equal(v:true, eval(string(v:true)))
+ " call assert_equal(v:none, eval(string(v:none)))
+ call assert_equal(v:null, eval(string(v:null)))
+
+ call assert_equal(v:false, copy(v:false))
+ call assert_equal(v:true, copy(v:true))
+ " call assert_equal(v:none, copy(v:none))
+ call assert_equal(v:null, copy(v:null))
+
+ call assert_equal([v:false], deepcopy([v:false]))
+ call assert_equal([v:true], deepcopy([v:true]))
+ " call assert_equal([v:none], deepcopy([v:none]))
+ call assert_equal([v:null], deepcopy([v:null]))
+
+ call assert_true(empty(v:false))
+ call assert_false(empty(v:true))
+ call assert_true(empty(v:null))
+ " call assert_true(empty(v:none))
+
+ func ChangeYourMind()
+ try
+ return v:true
+ finally
+ return 'something else'
+ endtry
+ endfunc
+
+ call ChangeYourMind()
endfunc
"-------------------------------------------------------------------------------
@@ -1668,7 +1747,7 @@ func Test_function_defined_line()
[CODE]
call writefile(lines, 'Xtest.vim')
- let res = system(v:progpath .. ' --clean -es -X -S Xtest.vim')
+ let res = system(GetVimCommandClean() .. ' -es -X -S Xtest.vim')
call assert_equal(0, v:shell_error)
let m = matchstr(res, 'function F1()[^[:print:]]*[[:print:]]*')
@@ -1692,6 +1771,26 @@ func Test_function_defined_line()
call delete('Xtest.vim')
endfunc
+func Test_for_over_string()
+ let res = ''
+ for c in 'aéc̀d'
+ let res ..= c .. '-'
+ endfor
+ call assert_equal('a-é-c̀-d-', res)
+
+ let res = ''
+ for c in ''
+ let res ..= c .. '-'
+ endfor
+ call assert_equal('', res)
+
+ let res = ''
+ for c in v:_null_string
+ let res ..= c .. '-'
+ endfor
+ call assert_equal('', res)
+endfunc
+
"-------------------------------------------------------------------------------
" Modelines {{{1
" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
diff --git a/src/nvim/testdir/test_virtualedit.vim b/src/nvim/testdir/test_virtualedit.vim
index 8f992f7501..250b896532 100644
--- a/src/nvim/testdir/test_virtualedit.vim
+++ b/src/nvim/testdir/test_virtualedit.vim
@@ -81,6 +81,133 @@ func Test_edit_change()
normal Cx
call assert_equal('x', getline(1))
bwipe!
+ set virtualedit=
+endfunc
+
+" Tests for pasting at the beginning, end and middle of a tab character
+" in virtual edit mode.
+func Test_paste_in_tab()
+ new
+ call append(0, '')
+ set virtualedit=all
+
+ " Tests for pasting a register with characterwise mode type
+ call setreg('"', 'xyz', 'c')
+
+ " paste (p) unnamed register at the beginning of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 0)
+ normal p
+ call assert_equal('a xyz b', getline(1))
+
+ " paste (P) unnamed register at the beginning of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 0)
+ normal P
+ call assert_equal("axyz\tb", getline(1))
+
+ " paste (p) unnamed register at the end of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 6)
+ normal p
+ call assert_equal("a\txyzb", getline(1))
+
+ " paste (P) unnamed register at the end of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 6)
+ normal P
+ call assert_equal('a xyz b', getline(1))
+
+ " Tests for pasting a register with blockwise mode type
+ call setreg('"', 'xyz', 'b')
+
+ " paste (p) unnamed register at the beginning of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 0)
+ normal p
+ call assert_equal('a xyz b', getline(1))
+
+ " paste (P) unnamed register at the beginning of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 0)
+ normal P
+ call assert_equal("axyz\tb", getline(1))
+
+ " paste (p) unnamed register at the end of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 6)
+ normal p
+ call assert_equal("a\txyzb", getline(1))
+
+ " paste (P) unnamed register at the end of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 6)
+ normal P
+ call assert_equal('a xyz b', getline(1))
+
+ " Tests for pasting with gp and gP in virtual edit mode
+
+ " paste (gp) unnamed register at the beginning of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 0)
+ normal gp
+ call assert_equal('a xyz b', getline(1))
+ call assert_equal([0, 1, 12, 0, 12], getcurpos())
+
+ " paste (gP) unnamed register at the beginning of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 0)
+ normal gP
+ call assert_equal("axyz\tb", getline(1))
+ call assert_equal([0, 1, 5, 0, 5], getcurpos())
+
+ " paste (gp) unnamed register at the end of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 6)
+ normal gp
+ call assert_equal("a\txyzb", getline(1))
+ call assert_equal([0, 1, 6, 0, 12], getcurpos())
+
+ " paste (gP) unnamed register at the end of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 6)
+ normal gP
+ call assert_equal('a xyz b', getline(1))
+ call assert_equal([0, 1, 12, 0, 12], getcurpos())
+
+ " Tests for pasting a named register
+ let @r = 'xyz'
+
+ " paste (gp) named register in the middle of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 2)
+ normal "rgp
+ call assert_equal('a xyz b', getline(1))
+ call assert_equal([0, 1, 8, 0, 8], getcurpos())
+
+ " paste (gP) named register in the middle of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 2)
+ normal "rgP
+ call assert_equal('a xyz b', getline(1))
+ call assert_equal([0, 1, 7, 0, 7], getcurpos())
+
+ bwipe!
+ set virtualedit=
+endfunc
+
+" Test for yanking a few spaces within a tab to a register
+func Test_yank_in_tab()
+ new
+ let @r = ''
+ call setline(1, "a\tb")
+ set virtualedit=all
+ call cursor(1, 2, 2)
+ normal "ry5l
+ call assert_equal(' ', @r)
+
+ bwipe!
+ set virtualedit=
endfunc
" Insert "keyword keyw", ESC, C CTRL-N, shows "keyword ykeyword".
@@ -216,4 +343,139 @@ func Test_yank_paste_small_del_reg()
set virtualedit=
endfunc
+" After calling s:TryVirtualeditReplace(), line 1 will contain one of these
+" two strings, depending on whether virtual editing is on or off.
+let s:result_ve_on = 'a x'
+let s:result_ve_off = 'x'
+
+" Utility function for Test_global_local_virtualedit()
+func s:TryVirtualeditReplace()
+ call setline(1, 'a')
+ normal gg7l
+ normal rx
+endfunc
+
+" Test for :set and :setlocal
+func Test_global_local_virtualedit()
+ new
+
+ " Verify that 'virtualedit' is initialized to empty, can be set globally to
+ " all and to empty, and can be set locally to all and to empty.
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+ set ve=all
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_on, getline(1))
+ set ve=
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+ setlocal ve=all
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_on, getline(1))
+ setlocal ve=
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+
+ " Verify that :set affects multiple windows.
+ split
+ set ve=all
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_on, getline(1))
+ wincmd p
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_on, getline(1))
+ set ve=
+ wincmd p
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+ bwipe!
+
+ " Verify that :setlocal affects only the current window.
+ new
+ split
+ setlocal ve=all
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_on, getline(1))
+ wincmd p
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+ bwipe!
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+
+ " Verify that the buffer 'virtualedit' state follows the global value only
+ " when empty and that "none" works as expected.
+ "
+ " 'virtualedit' State
+ " +--------+--------------------------+
+ " | Local | Global |
+ " | | |
+ " +--------+--------+--------+--------+
+ " | | "" | "all" | "none" |
+ " +--------+--------+--------+--------+
+ " | "" | off | on | off |
+ " | "all" | on | on | on |
+ " | "none" | off | off | off |
+ " +--------+--------+--------+--------+
+ new
+
+ setglobal ve=
+ setlocal ve=
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+ setlocal ve=all
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_on, getline(1))
+ setlocal ve=none
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+
+ setglobal ve=all
+ setlocal ve=
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_on, getline(1))
+ setlocal ve=all
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_on, getline(1))
+ setlocal ve=none
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+ setlocal ve=NONE
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+
+ setglobal ve=none
+ setlocal ve=
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+ setlocal ve=all
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_on, getline(1))
+ setlocal ve=none
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+
+ bwipe!
+
+ " Verify that the 'virtualedit' state is copied to new windows.
+ new
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+ split
+ setlocal ve=all
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_on, getline(1))
+ split
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_on, getline(1))
+ setlocal ve=
+ split
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+ bwipe!
+
+ setlocal virtualedit&
+ set virtualedit&
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim
index 8344598486..ae8f9ba70b 100644
--- a/src/nvim/testdir/test_visual.vim
+++ b/src/nvim/testdir/test_visual.vim
@@ -22,6 +22,14 @@ func Test_block_shift_overflow()
q!
endfunc
+func Test_dotregister_paste()
+ new
+ exe "norm! ihello world\<esc>"
+ norm! 0ve".p
+ call assert_equal('hello world world', getline(1))
+ q!
+endfunc
+
func Test_Visual_ctrl_o()
new
call setline(1, ['one', 'two', 'three'])
@@ -42,14 +50,6 @@ func Test_Visual_vapo()
bwipe!
endfunc
-func Test_dotregister_paste()
- new
- exe "norm! ihello world\<esc>"
- norm! 0ve".p
- call assert_equal('hello world world', getline(1))
- q!
-endfunc
-
func Test_Visual_inner_quote()
new
normal oxX
@@ -57,9 +57,37 @@ func Test_Visual_inner_quote()
bwipe!
endfunc
+" Test for Visual mode not being reset causing E315 error.
+func TriggerTheProblem()
+ " At this point there is no visual selection because :call reset it.
+ " Let's restore the selection:
+ normal gv
+ '<,'>del _
+ try
+ exe "normal \<Esc>"
+ catch /^Vim\%((\a\+)\)\=:E315/
+ echom 'Snap! E315 error!'
+ let g:msg = 'Snap! E315 error!'
+ endtry
+endfunc
+
+func Test_visual_mode_reset()
+ enew
+ let g:msg = "Everything's fine."
+ enew
+ setl buftype=nofile
+ call append(line('$'), 'Delete this line.')
+
+ " NOTE: this has to be done by a call to a function because executing :del
+ " the ex-way will require the colon operator which resets the visual mode
+ " thus preventing the problem:
+ exe "normal! GV:call TriggerTheProblem()\<CR>"
+ call assert_equal("Everything's fine.", g:msg)
+endfunc
+
" Test for visual block shift and tab characters.
func Test_block_shift_tab()
- enew!
+ new
call append(0, repeat(['one two three'], 5))
call cursor(1,1)
exe "normal i\<C-G>u"
@@ -68,7 +96,7 @@ func Test_block_shift_tab()
call assert_equal('on1 two three', getline(2))
call assert_equal('on1 two three', getline(5))
- enew!
+ %d _
call append(0, repeat(['abcdefghijklmnopqrstuvwxyz'], 5))
call cursor(1,1)
exe "normal \<C-V>4jI \<Esc>j<<11|D"
@@ -93,12 +121,26 @@ func Test_block_shift_tab()
call assert_equal(" abc\<Tab>\<Tab>defghijklmnopqrstuvwxyz", getline(4))
call assert_equal(" abc\<Tab> defghijklmnopqrstuvwxyz", getline(5))
- enew!
+ " Test for block shift with space characters at the beginning and with
+ " 'noexpandtab' and 'expandtab'
+ %d _
+ call setline(1, [" 1", " 2", " 3"])
+ setlocal shiftwidth=2 noexpandtab
+ exe "normal gg\<C-V>3j>"
+ call assert_equal(["\t1", "\t2", "\t3"], getline(1, '$'))
+ %d _
+ call setline(1, [" 1", " 2", " 3"])
+ setlocal shiftwidth=2 expandtab
+ exe "normal gg\<C-V>3j>"
+ call assert_equal([" 1", " 2", " 3"], getline(1, '$'))
+ setlocal shiftwidth&
+
+ bw!
endfunc
" Tests Blockwise Visual when there are TABs before the text.
func Test_blockwise_visual()
- enew!
+ new
call append(0, ['123456',
\ '234567',
\ '345678',
@@ -120,26 +162,31 @@ func Test_blockwise_visual()
\ "\t\tsomext",
\ "\t\ttesext"], getline(1, 7))
- enew!
+ bw!
endfunc
" Test swapping corners in blockwise visual mode with o and O
func Test_blockwise_visual_o_O()
- enew!
+ new
exe "norm! 10i.\<Esc>Y4P3lj\<C-V>4l2jr "
exe "norm! gvO\<Esc>ra"
exe "norm! gvO\<Esc>rb"
exe "norm! gvo\<C-c>rc"
exe "norm! gvO\<C-c>rd"
+ set selection=exclusive
+ exe "norm! gvOo\<C-c>re"
+ call assert_equal('...a be.', getline(4))
+ exe "norm! gvOO\<C-c>rf"
+ set selection&
call assert_equal(['..........',
\ '...c d..',
\ '... ..',
- \ '...a b..',
+ \ '...a bf.',
\ '..........'], getline(1, '$'))
- enew!
+ bw!
endfun
" Test Virtual replace mode.
@@ -242,35 +289,6 @@ func Test_virtual_replace2()
set bs&vim
endfunc
-" Test for Visual mode not being reset causing E315 error.
-func TriggerTheProblem()
- " At this point there is no visual selection because :call reset it.
- " Let's restore the selection:
- normal gv
- '<,'>del _
- try
- exe "normal \<Esc>"
- catch /^Vim\%((\a\+)\)\=:E315/
- echom 'Snap! E315 error!'
- let g:msg = 'Snap! E315 error!'
- endtry
-endfunc
-
-func Test_visual_mode_reset()
- enew
- let g:msg = "Everything's fine."
- enew
- setl buftype=nofile
- call append(line('$'), 'Delete this line.')
-
- " NOTE: this has to be done by a call to a function because executing :del
- " the ex-way will require the colon operator which resets the visual mode
- " thus preventing the problem:
- exe "normal! GV:call TriggerTheProblem()\<CR>"
- call assert_equal("Everything's fine.", g:msg)
-
-endfunc
-
func Test_Visual_word_textobject()
new
call setline(1, ['First sentence. Second sentence.'])
@@ -349,17 +367,6 @@ func Test_Visual_sentence_textobject()
bwipe!
endfunc
-func Test_curswant_not_changed()
- new
- call setline(1, ['one', 'two'])
- au InsertLeave * call getcurpos()
- call feedkeys("gg0\<C-V>jI123 \<Esc>j", 'xt')
- call assert_equal([0, 2, 1, 0, 1], getcurpos())
-
- bwipe!
- au! InsertLeave
-endfunc
-
func Test_Visual_paragraph_textobject()
new
call setline(1, ['First line.',
@@ -409,6 +416,17 @@ func Test_Visual_paragraph_textobject()
bwipe!
endfunc
+func Test_curswant_not_changed()
+ new
+ call setline(1, ['one', 'two'])
+ au InsertLeave * call getcurpos()
+ call feedkeys("gg0\<C-V>jI123 \<Esc>j", 'xt')
+ call assert_equal([0, 2, 1, 0, 1], getcurpos())
+
+ bwipe!
+ au! InsertLeave
+endfunc
+
" Tests for "vaBiB", end could be wrong.
func Test_Visual_Block()
new
@@ -433,13 +451,15 @@ func Test_Visual_Block()
close!
endfunc
-func Test_visual_put_in_block()
+" Test for 'p'ut in visual block mode
+func Test_visual_block_put()
new
- call setline(1, ['xxxx', 'y∞yy', 'zzzz'])
- normal 1G2yl
- exe "normal 1G2l\<C-V>jjlp"
- call assert_equal(['xxxx', 'y∞xx', 'zzxx'], getline(1, 3))
- bwipe!
+ call append(0, ['One', 'Two', 'Three'])
+ normal gg
+ yank
+ call feedkeys("jl\<C-V>ljp", 'xt')
+ call assert_equal(['One', 'T', 'Tee', 'One', ''], getline(1, '$'))
+ bw!
endfunc
" Visual modes (v V CTRL-V) followed by an operator; count; repeating
@@ -610,6 +630,17 @@ func Test_characterwise_visual_mode()
normal Gkvj$d
call assert_equal(['', 'a', ''], getline(1, '$'))
+ " characterwise visual mode: replace a single character line and the eol
+ %d _
+ call setline(1, "a")
+ normal v$rx
+ call assert_equal(['x'], getline(1, '$'))
+
+ " replace a character with composing characters
+ call setline(1, "xã̳x")
+ normal gg0lvrb
+ call assert_equal("xbx", getline(1))
+
bwipe!
endfunc
@@ -645,6 +676,16 @@ func Test_characterwise_select_mode()
exe "normal Gkgh\<Down>\<End>\<Del>"
call assert_equal(['', 'a', ''], getline(1, '$'))
+ " CTRL-H in select mode behaves like 'x'
+ call setline(1, 'abcdef')
+ exe "normal! gggh\<Right>\<Right>\<Right>\<C-H>"
+ call assert_equal('ef', getline(1))
+
+ " CTRL-O in select mode switches to visual mode for one command
+ call setline(1, 'abcdef')
+ exe "normal! gggh\<C-O>3lm"
+ call assert_equal('mef', getline(1))
+
sunmap <lt>End>
sunmap <lt>Down>
sunmap <lt>Del>
@@ -744,8 +785,7 @@ endfunc
func Test_visual_block_mode()
new
call append(0, '')
- call setline(1, ['abcdefghijklm', 'abcdefghijklm', 'abcdefghijklm',
- \ 'abcdefghijklm', 'abcdefghijklm'])
+ call setline(1, repeat(['abcdefghijklm'], 5))
call cursor(1, 1)
" Test shift-right of a block
@@ -764,6 +804,76 @@ func Test_visual_block_mode()
\ 'axyzqqqqefgmnoklm',
\ 'abcdqqqqijklm'], getline(1, 5))
+ " Test 'C' to change till the end of the line
+ call cursor(3, 4)
+ exe "normal! \<C-V>j3lCooo"
+ call assert_equal(['axyooo', 'axyooo'], getline(3, 4))
+
+ " Test 'D' to delete till the end of the line
+ call cursor(3, 3)
+ exe "normal! \<C-V>j2lD"
+ call assert_equal(['ax', 'ax'], getline(3, 4))
+
+ " Test block insert with a short line that ends before the block
+ %d _
+ call setline(1, [" one", "a", " two"])
+ exe "normal gg\<C-V>2jIx"
+ call assert_equal([" xone", "a", " xtwo"], getline(1, '$'))
+
+ " Test block append at EOL with '$' and without '$'
+ %d _
+ call setline(1, ["one", "a", "two"])
+ exe "normal gg$\<C-V>2jAx"
+ call assert_equal(["onex", "ax", "twox"], getline(1, '$'))
+ %d _
+ call setline(1, ["one", "a", "two"])
+ exe "normal gg3l\<C-V>2jAx"
+ call assert_equal(["onex", "a x", "twox"], getline(1, '$'))
+
+ " Test block replace with an empty line in the middle and use $ to jump to
+ " the end of the line.
+ %d _
+ call setline(1, ['one', '', 'two'])
+ exe "normal gg$\<C-V>2jrx"
+ call assert_equal(["onx", "", "twx"], getline(1, '$'))
+
+ " Test block replace with an empty line in the middle and move cursor to the
+ " end of the line
+ %d _
+ call setline(1, ['one', '', 'two'])
+ exe "normal gg2l\<C-V>2jrx"
+ call assert_equal(["onx", "", "twx"], getline(1, '$'))
+
+ " Replace odd number of characters with a multibyte character
+ %d _
+ call setline(1, ['abcd', 'efgh'])
+ exe "normal ggl\<C-V>2ljr\u1100"
+ call assert_equal(["a\u1100 ", "e\u1100 "], getline(1, '$'))
+
+ " During visual block append, if the cursor moved outside of the selected
+ " range, then the edit should not be applied to the block.
+ %d _
+ call setline(1, ['aaa', 'bbb', 'ccc'])
+ exe "normal 2G\<C-V>jAx\<Up>"
+ call assert_equal(['aaa', 'bxbb', 'ccc'], getline(1, '$'))
+
+ " During visual block append, if the cursor is moved before the start of the
+ " block, then the new text should be appended there.
+ %d _
+ call setline(1, ['aaa', 'bbb', 'ccc'])
+ exe "normal $\<C-V>2jA\<Left>x"
+ call assert_equal(['aaxa', 'bbxb', 'ccxc'], getline(1, '$'))
+ " Repeat the previous test but use 'l' to move the cursor instead of '$'
+ call setline(1, ['aaa', 'bbb', 'ccc'])
+ exe "normal! gg2l\<C-V>2jA\<Left>x"
+ call assert_equal(['aaxa', 'bbxb', 'ccxc'], getline(1, '$'))
+
+ " Change a characterwise motion to a blockwise motion using CTRL-V
+ %d _
+ call setline(1, ['123', '456', '789'])
+ exe "normal ld\<C-V>j"
+ call assert_equal(['13', '46', '789'], getline(1, '$'))
+
" Test from ':help v_b_I_example'
%d _
setlocal tabstop=8 shiftwidth=4
@@ -995,6 +1105,15 @@ func Test_block_insert_replace_tabs()
bwipe!
endfunc
+func Test_visual_put_in_block()
+ new
+ call setline(1, ['xxxx', 'y∞yy', 'zzzz'])
+ normal 1G2yl
+ exe "normal 1G2l\<C-V>jjlp"
+ call assert_equal(['xxxx', 'y∞xx', 'zzxx'], getline(1, 3))
+ bwipe!
+endfunc
+
func Test_visual_put_in_block_using_zp()
new
" paste using zP
@@ -1090,6 +1209,13 @@ func Test_visual_put_blockedit_zy_and_zp()
bw!
endfunc
+func Test_visual_block_yank_zy()
+ new
+ " this was reading before the start of the line
+ exe "norm o\<C-T>\<Esc>\<C-V>zy"
+ bwipe!
+endfunc
+
func Test_visual_block_with_virtualedit()
CheckScreendump
@@ -1104,11 +1230,154 @@ func Test_visual_block_with_virtualedit()
call term_sendkeys(buf, "\<C-V>gg$")
call VerifyScreenDump(buf, 'Test_visual_block_with_virtualedit', {})
+ call term_sendkeys(buf, "\<Esc>gg\<C-V>G$")
+ call VerifyScreenDump(buf, 'Test_visual_block_with_virtualedit2', {})
+
" clean up
call term_sendkeys(buf, "\<Esc>")
call StopVimInTerminal(buf)
- call delete('XTest_beval')
+ call delete('XTest_block')
+endfunc
+
+func Test_visual_block_ctrl_w_f()
+ " Emtpy block selected in new buffer should not result in an error.
+ au! BufNew foo sil norm f
+ edit foo
+
+ au! BufNew
+endfunc
+
+func Test_visual_block_append_invalid_char()
+ " this was going over the end of the line
+ new
+ call setline(1, [' let xxx', 'xxxxxˆ', 'xxxxxxxxxxx'])
+ exe "normal 0\<C-V>jjA-\<Esc>"
+ call assert_equal([' - let xxx', 'xxxxx -ˆ', 'xxxxxxxx-xxx'], getline(1, 3))
+ bwipe!
+endfunc
+
+func Test_visual_reselect_with_count()
+ " this was causing an illegal memory access
+ let lines =<< trim END
+
+
+
+ :
+ r<sfile>
+ exe "%norm e3\<c-v>kr\t"
+ :
+
+ :
+ END
+ call writefile(lines, 'XvisualReselect')
+ source XvisualReselect
+
+ bwipe!
+ call delete('XvisualReselect')
+endfunc
+
+func Test_visual_block_insert_round_off()
+ new
+ " The number of characters are tuned to fill a 4096 byte allocated block,
+ " so that valgrind reports going over the end.
+ call setline(1, ['xxxxx', repeat('0', 1350), "\t", repeat('x', 60)])
+ exe "normal gg0\<C-V>GI" .. repeat('0', 1320) .. "\<Esc>"
+ bwipe!
endfunc
+" this was causing an ml_get error
+func Test_visual_exchange_windows()
+ enew!
+ new
+ call setline(1, ['foo', 'bar'])
+ exe "normal G\<C-V>gg\<C-W>\<C-X>OO\<Esc>"
+ bwipe!
+ bwipe!
+endfunc
+
+" this was leaving the end of the Visual area beyond the end of a line
+func Test_visual_ex_copy_line()
+ new
+ call setline(1, ["aaa", "bbbbbbbbbxbb"])
+ /x
+ exe "normal ggvjfxO"
+ t0
+ normal gNU
+ bwipe!
+endfunc
+
+" This was leaving the end of the Visual area beyond the end of a line.
+" Set 'undolevels' to start a new undo block.
+func Test_visual_undo_deletes_last_line()
+ new
+ call setline(1, ["aaa", "ccc", "dyd"])
+ set undolevels=100
+ exe "normal obbbbbbbbbxbb\<Esc>"
+ set undolevels=100
+ /y
+ exe "normal ggvjfxO"
+ undo
+ normal gNU
+
+ bwipe!
+endfunc
+
+func Test_visual_paste()
+ new
+
+ " v_p overwrites unnamed register.
+ call setline(1, ['xxxx'])
+ call setreg('"', 'foo')
+ call setreg('-', 'bar')
+ normal gg0vp
+ call assert_equal('x', @")
+ call assert_equal('x', @-)
+ call assert_equal('fooxxx', getline(1))
+ normal $vp
+ call assert_equal('x', @")
+ call assert_equal('x', @-)
+ call assert_equal('fooxxx', getline(1))
+ " Test with a different register as unnamed register.
+ call setline(2, ['baz'])
+ normal 2gg0"rD
+ call assert_equal('baz', @")
+ normal gg0vp
+ call assert_equal('f', @")
+ call assert_equal('f', @-)
+ call assert_equal('bazooxxx', getline(1))
+ normal $vp
+ call assert_equal('x', @")
+ call assert_equal('x', @-)
+ call assert_equal('bazooxxf', getline(1))
+
+ if has('clipboard')
+ " v_P does not overwrite unnamed register.
+ call setline(1, ['xxxx'])
+ call setreg('"', 'foo')
+ call setreg('-', 'bar')
+ normal gg0vP
+ call assert_equal('foo', @")
+ call assert_equal('x', @-)
+ call assert_equal('fooxxx', getline(1))
+ normal $vP
+ call assert_equal('foo', @")
+ call assert_equal('x', @-)
+ call assert_equal('fooxxfoo', getline(1))
+ " Test with a different register as unnamed register.
+ call setline(2, ['baz'])
+ normal 2gg0"rD
+ call assert_equal('baz', @")
+ normal gg0vP
+ call assert_equal('baz', @")
+ call assert_equal('f', @-)
+ call assert_equal('bazooxxfoo', getline(1))
+ normal $vP
+ call assert_equal('baz', @")
+ call assert_equal('o', @-)
+ call assert_equal('bazooxxfobaz', getline(1))
+ endif
+
+ bwipe!
+endfunc
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_winbuf_close.vim b/src/nvim/testdir/test_winbuf_close.vim
index 7f5b80e8d3..f4878c2397 100644
--- a/src/nvim/testdir/test_winbuf_close.vim
+++ b/src/nvim/testdir/test_winbuf_close.vim
@@ -194,3 +194,22 @@ func Test_tabwin_close()
call assert_true(v:true)
%bwipe!
endfunc
+
+" Test when closing a split window (above/below) restores space to the window
+" below when 'noequalalways' and 'splitright' are set.
+func Test_window_close_splitright_noequalalways()
+ set noequalalways
+ set splitright
+ new
+ let w1 = win_getid()
+ new
+ let w2 = win_getid()
+ execute "normal \<c-w>b"
+ let h = winheight(0)
+ let w = win_getid()
+ new
+ q
+ call assert_equal(h, winheight(0), "Window height does not match eight before opening and closing another window")
+ call assert_equal(w, win_getid(), "Did not return to original window after opening and closing a window")
+endfunc
+
diff --git a/src/nvim/testdir/test_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim
index a200bf7d42..ef6dec580f 100644
--- a/src/nvim/testdir/test_window_cmd.vim
+++ b/src/nvim/testdir/test_window_cmd.vim
@@ -575,7 +575,7 @@ func Test_winrestcmd()
only
endfunc
-function! Fun_RenewFile()
+func Fun_RenewFile()
" Need to wait a bit for the timestamp to be older.
let old_ftime = getftime("tmp.txt")
while getftime("tmp.txt") == old_ftime
@@ -585,7 +585,7 @@ function! Fun_RenewFile()
sp
wincmd p
edit! tmp.txt
-endfunction
+endfunc
func Test_window_prevwin()
" Can we make this work on MS-Windows?
@@ -939,4 +939,124 @@ func Test_window_resize()
%bwipe!
endfunc
+func Test_win_move_separator()
+ edit a
+ leftabove vsplit b
+ let w = winwidth(0)
+ " check win_move_separator from left window on left window
+ call assert_equal(1, winnr())
+ for offset in range(5)
+ call assert_true(win_move_separator(0, offset))
+ call assert_equal(w + offset, winwidth(0))
+ call assert_true(0->win_move_separator(-offset))
+ call assert_equal(w, winwidth(0))
+ endfor
+ " check win_move_separator from right window on left window number
+ wincmd l
+ call assert_notequal(1, winnr())
+ for offset in range(5)
+ call assert_true(1->win_move_separator(offset))
+ call assert_equal(w + offset, winwidth(1))
+ call assert_true(win_move_separator(1, -offset))
+ call assert_equal(w, winwidth(1))
+ endfor
+ " check win_move_separator from right window on left window ID
+ let id = win_getid(1)
+ for offset in range(5)
+ call assert_true(win_move_separator(id, offset))
+ call assert_equal(w + offset, winwidth(id))
+ call assert_true(id->win_move_separator(-offset))
+ call assert_equal(w, winwidth(id))
+ endfor
+ " check win_move_separator from right window on right window is no-op
+ let w0 = winwidth(0)
+ call assert_true(win_move_separator(0, 1))
+ call assert_equal(w0, winwidth(0))
+ call assert_true(win_move_separator(0, -1))
+ call assert_equal(w0, winwidth(0))
+ " check that win_move_separator doesn't error with offsets beyond moving
+ " possibility
+ call assert_true(win_move_separator(id, 5000))
+ call assert_true(winwidth(id) > w)
+ call assert_true(win_move_separator(id, -5000))
+ call assert_true(winwidth(id) < w)
+ " check that win_move_separator returns false for an invalid window
+ wincmd =
+ let w = winwidth(0)
+ call assert_false(win_move_separator(-1, 1))
+ call assert_equal(w, winwidth(0))
+ " check that win_move_separator returns false for a floating window
+ let id = nvim_open_win(
+ \ 0, 0, #{relative: 'editor', row: 2, col: 2, width: 5, height: 3})
+ let w = winwidth(id)
+ call assert_false(win_move_separator(id, 1))
+ call assert_equal(w, winwidth(id))
+ call nvim_win_close(id, 1)
+ %bwipe!
+endfunc
+
+func Test_win_move_statusline()
+ redraw " This test fails in Nvim without a redraw to clear messages.
+ edit a
+ leftabove split b
+ let h = winheight(0)
+ " check win_move_statusline from top window on top window
+ call assert_equal(1, winnr())
+ for offset in range(5)
+ call assert_true(win_move_statusline(0, offset))
+ call assert_equal(h + offset, winheight(0))
+ call assert_true(0->win_move_statusline(-offset))
+ call assert_equal(h, winheight(0))
+ endfor
+ " check win_move_statusline from bottom window on top window number
+ wincmd j
+ call assert_notequal(1, winnr())
+ for offset in range(5)
+ call assert_true(1->win_move_statusline(offset))
+ call assert_equal(h + offset, winheight(1))
+ call assert_true(win_move_statusline(1, -offset))
+ call assert_equal(h, winheight(1))
+ endfor
+ " check win_move_statusline from bottom window on bottom window
+ let h0 = winheight(0)
+ for offset in range(5)
+ call assert_true(0->win_move_statusline(-offset))
+ call assert_equal(h0 - offset, winheight(0))
+ call assert_equal(1 + offset, &cmdheight)
+ call assert_true(win_move_statusline(0, offset))
+ call assert_equal(h0, winheight(0))
+ call assert_equal(1, &cmdheight)
+ endfor
+ call assert_true(win_move_statusline(0, 1))
+ call assert_equal(h0, winheight(0))
+ call assert_equal(1, &cmdheight)
+ " check win_move_statusline from bottom window on top window ID
+ let id = win_getid(1)
+ for offset in range(5)
+ call assert_true(win_move_statusline(id, offset))
+ call assert_equal(h + offset, winheight(id))
+ call assert_true(id->win_move_statusline(-offset))
+ call assert_equal(h, winheight(id))
+ endfor
+ " check that win_move_statusline doesn't error with offsets beyond moving
+ " possibility
+ call assert_true(win_move_statusline(id, 5000))
+ call assert_true(winheight(id) > h)
+ call assert_true(win_move_statusline(id, -5000))
+ call assert_true(winheight(id) < h)
+ " check that win_move_statusline returns false for an invalid window
+ wincmd =
+ let h = winheight(0)
+ call assert_false(win_move_statusline(-1, 1))
+ call assert_equal(h, winheight(0))
+ " check that win_move_statusline returns false for a floating window
+ let id = nvim_open_win(
+ \ 0, 0, #{relative: 'editor', row: 2, col: 2, width: 5, height: 3})
+ let h = winheight(id)
+ call assert_false(win_move_statusline(id, 1))
+ call assert_equal(h, winheight(id))
+ call nvim_win_close(id, 1)
+ %bwipe!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_writefile.vim b/src/nvim/testdir/test_writefile.vim
index aa7882d129..b42665c9b5 100644
--- a/src/nvim/testdir/test_writefile.vim
+++ b/src/nvim/testdir/test_writefile.vim
@@ -43,7 +43,7 @@ func Test_writefile_fails_gently()
endfunc
func Test_writefile_fails_conversion()
- if !has('iconv') || system('uname -s') =~ 'SunOS'
+ if !has('iconv') || has('sun')
return
endif
" Without a backup file the write won't happen if there is a conversion
@@ -169,9 +169,7 @@ endfunc
" Test for ':w !<cmd>' to pipe lines from the current buffer to an external
" command.
func Test_write_pipe_to_cmd()
- if !has('unix')
- return
- endif
+ CheckUnix
new
call setline(1, ['L1', 'L2', 'L3', 'L4'])
2,3w !cat > Xfile
@@ -371,6 +369,154 @@ func Test_write_file_encoding()
%bw!
endfunc
+" Test for writing and reading a file starting with a BOM.
+" Byte Order Mark (BOM) character for various encodings is below:
+" UTF-8 : EF BB BF
+" UTF-16 (BE): FE FF
+" UTF-16 (LE): FF FE
+" UTF-32 (BE): 00 00 FE FF
+" UTF-32 (LE): FF FE 00 00
+func Test_readwrite_file_with_bom()
+ let utf8_bom = "\xEF\xBB\xBF"
+ let utf16be_bom = "\xFE\xFF"
+ let utf16le_bom = "\xFF\xFE"
+ let utf32be_bom = "\n\n\xFE\xFF"
+ let utf32le_bom = "\xFF\xFE\n\n"
+ let save_fileencoding = &fileencoding
+ set cpoptions+=S
+
+ " Check that editing a latin1 file doesn't see a BOM
+ call writefile(["\xFE\xFElatin-1"], 'Xtest1')
+ edit Xtest1
+ call assert_equal('latin1', &fileencoding)
+ call assert_equal(0, &bomb)
+ set fenc=latin1
+ write Xfile2
+ call assert_equal(["\xFE\xFElatin-1", ''], readfile('Xfile2', 'b'))
+ set bomb fenc=latin1
+ write Xtest3
+ call assert_equal(["\xFE\xFElatin-1", ''], readfile('Xtest3', 'b'))
+ set bomb&
+
+ " Check utf-8 BOM
+ %bw!
+ call writefile([utf8_bom .. "utf-8"], 'Xtest1')
+ edit! Xtest1
+ call assert_equal('utf-8', &fileencoding)
+ call assert_equal(1, &bomb)
+ call assert_equal('utf-8', getline(1))
+ set fenc=latin1
+ write! Xfile2
+ call assert_equal(['utf-8', ''], readfile('Xfile2', 'b'))
+ set fenc=utf-8
+ w! Xtest3
+ call assert_equal([utf8_bom .. "utf-8", ''], readfile('Xtest3', 'b'))
+
+ " Check utf-8 with an error (will fall back to latin-1)
+ %bw!
+ call writefile([utf8_bom .. "utf-8\x80err"], 'Xtest1')
+ edit! Xtest1
+ call assert_equal('latin1', &fileencoding)
+ call assert_equal(0, &bomb)
+ call assert_equal("\xC3\xAF\xC2\xBB\xC2\xBFutf-8\xC2\x80err", getline(1))
+ set fenc=latin1
+ write! Xfile2
+ call assert_equal([utf8_bom .. "utf-8\x80err", ''], readfile('Xfile2', 'b'))
+ set fenc=utf-8
+ w! Xtest3
+ call assert_equal(["\xC3\xAF\xC2\xBB\xC2\xBFutf-8\xC2\x80err", ''],
+ \ readfile('Xtest3', 'b'))
+
+ " Check ucs-2 BOM
+ %bw!
+ call writefile([utf16be_bom .. "\nu\nc\ns\n-\n2\n"], 'Xtest1')
+ edit! Xtest1
+ call assert_equal('utf-16', &fileencoding)
+ call assert_equal(1, &bomb)
+ call assert_equal('ucs-2', getline(1))
+ set fenc=latin1
+ write! Xfile2
+ call assert_equal(["ucs-2", ''], readfile('Xfile2', 'b'))
+ set fenc=ucs-2
+ w! Xtest3
+ call assert_equal([utf16be_bom .. "\nu\nc\ns\n-\n2\n", ''],
+ \ readfile('Xtest3', 'b'))
+
+ " Check ucs-2le BOM
+ %bw!
+ call writefile([utf16le_bom .. "u\nc\ns\n-\n2\nl\ne\n"], 'Xtest1')
+ " Need to add a NUL byte after the NL byte
+ call writefile(0z00, 'Xtest1', 'a')
+ edit! Xtest1
+ call assert_equal('utf-16le', &fileencoding)
+ call assert_equal(1, &bomb)
+ call assert_equal('ucs-2le', getline(1))
+ set fenc=latin1
+ write! Xfile2
+ call assert_equal(["ucs-2le", ''], readfile('Xfile2', 'b'))
+ set fenc=ucs-2le
+ w! Xtest3
+ call assert_equal([utf16le_bom .. "u\nc\ns\n-\n2\nl\ne\n", "\n"],
+ \ readfile('Xtest3', 'b'))
+
+ " Check ucs-4 BOM
+ %bw!
+ call writefile([utf32be_bom .. "\n\n\nu\n\n\nc\n\n\ns\n\n\n-\n\n\n4\n\n\n"], 'Xtest1')
+ edit! Xtest1
+ call assert_equal('ucs-4', &fileencoding)
+ call assert_equal(1, &bomb)
+ call assert_equal('ucs-4', getline(1))
+ set fenc=latin1
+ write! Xfile2
+ call assert_equal(["ucs-4", ''], readfile('Xfile2', 'b'))
+ set fenc=ucs-4
+ w! Xtest3
+ call assert_equal([utf32be_bom .. "\n\n\nu\n\n\nc\n\n\ns\n\n\n-\n\n\n4\n\n\n", ''], readfile('Xtest3', 'b'))
+
+ " Check ucs-4le BOM
+ %bw!
+ call writefile([utf32le_bom .. "u\n\n\nc\n\n\ns\n\n\n-\n\n\n4\n\n\nl\n\n\ne\n\n\n"], 'Xtest1')
+ " Need to add three NUL bytes after the NL byte
+ call writefile(0z000000, 'Xtest1', 'a')
+ edit! Xtest1
+ call assert_equal('ucs-4le', &fileencoding)
+ call assert_equal(1, &bomb)
+ call assert_equal('ucs-4le', getline(1))
+ set fenc=latin1
+ write! Xfile2
+ call assert_equal(["ucs-4le", ''], readfile('Xfile2', 'b'))
+ set fenc=ucs-4le
+ w! Xtest3
+ call assert_equal([utf32le_bom .. "u\n\n\nc\n\n\ns\n\n\n-\n\n\n4\n\n\nl\n\n\ne\n\n\n", "\n\n\n"], readfile('Xtest3', 'b'))
+
+ set cpoptions-=S
+ let &fileencoding = save_fileencoding
+ call delete('Xtest1')
+ call delete('Xfile2')
+ call delete('Xtest3')
+ %bw!
+endfunc
+
+func Test_read_write_bin()
+ " write file missing EOL
+ call writefile(['noeol'], "XNoEolSetEol", 'bS')
+ call assert_equal(0z6E6F656F6C, readfile('XNoEolSetEol', 'B'))
+
+ " when file is read 'eol' is off
+ set nofixeol
+ e! ++ff=unix XNoEolSetEol
+ call assert_equal(0, &eol)
+
+ " writing with 'eol' set adds the newline
+ setlocal eol
+ w
+ call assert_equal(0z6E6F656F6C0A, readfile('XNoEolSetEol', 'B'))
+
+ call delete('XNoEolSetEol')
+ set ff&
+ bwipe! XNoEolSetEol
+endfunc
+
" Check that buffer is written before triggering QuitPre
func Test_wq_quitpre_autocommand()
edit Xsomefile
diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c
index 5fec41f9a5..b262fc6c54 100644
--- a/src/nvim/tui/input.c
+++ b/src/nvim/tui/input.c
@@ -5,7 +5,7 @@
#include "nvim/api/private/helpers.h"
#include "nvim/api/vim.h"
#include "nvim/ascii.h"
-#include "nvim/aucmd.h"
+#include "nvim/autocmd.h"
#include "nvim/charset.h"
#include "nvim/ex_docmd.h"
#include "nvim/macros.h"
@@ -227,7 +227,7 @@ static void forward_modified_utf8(TermInput *input, TermKeyKey *key)
&& !(key->modifiers & TERMKEY_KEYMOD_SHIFT)
&& ASCII_ISUPPER(key->code.codepoint)) {
assert(len <= 62);
- // Make remove for the S-
+ // Make room for the S-
memmove(buf + 3, buf + 1, len - 1);
buf[1] = 'S';
buf[2] = '-';
@@ -315,7 +315,6 @@ static TermKeyResult tk_getkey(TermKey *tk, TermKeyKey *key, bool force)
return force ? termkey_getkey_force(tk, key) : termkey_getkey(tk, key);
}
-static void tinput_timer_cb(TimeWatcher *watcher, void *data);
static void tk_getkeys(TermInput *input, bool force)
{
@@ -379,7 +378,7 @@ static bool handle_focus_event(TermInput *input)
bool focus_gained = *rbuffer_get(input->read_stream.buffer, 2) == 'I';
// Advance past the sequence
rbuffer_consumed(input->read_stream.buffer, 3);
- aucmd_schedule_focusgained(focus_gained);
+ autocmd_schedule_focusgained(focus_gained);
return true;
}
return false;
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index e7a60aca49..58061f020d 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -1877,7 +1877,7 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, const char *col
"\x1b[?c");
} else if (konsolev > 0 && konsolev < 180770) {
// Konsole before version 18.07.70: set up a nonce profile. This has
- // side-effects on temporary font resizing. #6798
+ // side effects on temporary font resizing. #6798
data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(ut, "Ss",
TMUX_WRAP(tmux,
"\x1b]50;CursorShape=%?"
diff --git a/src/nvim/types.h b/src/nvim/types.h
index 604155c33e..73cd2204d6 100644
--- a/src/nvim/types.h
+++ b/src/nvim/types.h
@@ -32,4 +32,6 @@ typedef enum {
kTrue = 1,
} TriState;
+typedef struct Decoration Decoration;
+
#endif // NVIM_TYPES_H
diff --git a/src/nvim/ui.c b/src/nvim/ui.c
index 1aadaf5c9d..31b9614c34 100644
--- a/src/nvim/ui.c
+++ b/src/nvim/ui.c
@@ -8,7 +8,7 @@
#include <string.h>
#include "nvim/ascii.h"
-#include "nvim/aucmd.h"
+#include "nvim/autocmd.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/cursor_shape.h"
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
index d18f35a43a..2d8df4cad8 100644
--- a/src/nvim/undo.c
+++ b/src/nvim/undo.c
@@ -2633,6 +2633,10 @@ static void u_undo_end(bool did_undo, bool absolute, bool quiet)
}
}
+ if (VIsual_active) {
+ check_pos(curbuf, &VIsual);
+ }
+
smsg_attr_keep(0,
_("%" PRId64 " %s; %s #%" PRId64 " %s"),
u_oldcount < 0 ? (int64_t)-u_oldcount : (int64_t)u_oldcount,
diff --git a/src/nvim/version.c b/src/nvim/version.c
index 5e2a81795a..71cca52773 100644
--- a/src/nvim/version.c
+++ b/src/nvim/version.c
@@ -2093,7 +2093,7 @@ void list_in_columns(char_u **items, int size, int current)
// The rightmost column doesn't need a separator.
// Sacrifice it to fit in one more column if possible.
- int ncol = (int)(Columns + 1) / width;
+ int ncol = (Columns + 1) / width;
int nrow = item_count / ncol + (item_count % ncol ? 1 : 0);
int cur_row = 1;
diff --git a/src/nvim/vim.h b/src/nvim/vim.h
index 2f8ddd1e88..6e0e9922a6 100644
--- a/src/nvim/vim.h
+++ b/src/nvim/vim.h
@@ -143,6 +143,7 @@ enum {
EXPAND_COMPILER,
EXPAND_USER_DEFINED,
EXPAND_USER_LIST,
+ EXPAND_USER_LUA,
EXPAND_SHELLCMD,
EXPAND_CSCOPE,
EXPAND_SIGN,
diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c
index ba6cfab98b..9d1318724e 100644
--- a/src/nvim/viml/parser/expressions.c
+++ b/src/nvim/viml/parser/expressions.c
@@ -1536,7 +1536,7 @@ static inline void east_set_error(const ParserState *const pstate, ExprASTError
/* TODO(ZyX-I): Extend syntax to allow ${expr}. This is needed to */ \
/* handle environment variables like those bash uses for */ \
/* `export -f`: their names consist not only of alphanumeric */ \
- /* characetrs. */ \
+ /* characters. */ \
case kExprNodeComplexIdentifier: \
case kExprNodePlainIdentifier: \
case kExprNodeCurlyBracesIdentifier: { \
@@ -1592,7 +1592,7 @@ typedef struct {
/// string is a regex.
/// @param[in] is_invalid Whether currently processed token is not valid.
static void parse_quoted_string(ParserState *const pstate, ExprASTNode *const node,
- const LexExprToken token, const ExprASTStack ast_stack,
+ const LexExprToken token, const ExprASTStack *ast_stack,
const bool is_invalid)
FUNC_ATTR_NONNULL_ALL
{
@@ -2907,7 +2907,7 @@ viml_pexpr_parse_no_paren_closing_error: {}
? kExprNodeDoubleQuotedString
: kExprNodeSingleQuotedString));
*top_node_p = cur_node;
- parse_quoted_string(pstate, cur_node, cur_token, ast_stack, is_invalid);
+ parse_quoted_string(pstate, cur_node, cur_token, &ast_stack, is_invalid);
want_node = kENodeOperator;
break;
}
diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h
index fe9327b27d..9d0bc9d468 100644
--- a/src/nvim/viml/parser/expressions.h
+++ b/src/nvim/viml/parser/expressions.h
@@ -57,7 +57,7 @@ typedef enum {
} LexExprTokenType;
typedef enum {
- kExprCmpEqual, ///< Equality, unequality.
+ kExprCmpEqual, ///< Equality, inequality.
kExprCmpMatches, ///< Matches regex, not matches regex.
kExprCmpGreater, ///< `>` or `<=`
kExprCmpGreaterOrEqual, ///< `>=` or `<`.
diff --git a/src/nvim/window.c b/src/nvim/window.c
index c711f462d1..83048d911f 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -304,7 +304,7 @@ newwindow:
newtab = curtab;
goto_tabpage_tp(oldtab, true, true);
if (curwin == wp) {
- win_close(curwin, false);
+ win_close(curwin, false, false);
}
if (valid_tabpage(newtab)) {
goto_tabpage_tp(newtab, true, true);
@@ -449,7 +449,7 @@ wingotofile:
RESET_BINDING(curwin);
if (do_ecmd(0, ptr, NULL, NULL, ECMD_LASTL, ECMD_HIDE, NULL) == FAIL) {
// Failed to open the file, close the window opened for it.
- win_close(curwin, false);
+ win_close(curwin, false, false);
goto_tabpage_win(oldtab, oldwin);
} else if (nchar == 'F' && lnum >= 0) {
curwin->w_cursor.lnum = lnum;
@@ -577,18 +577,19 @@ static void cmd_with_count(char *cmd, char_u *bufp, size_t bufsize, int64_t Pren
void win_set_buf(Window window, Buffer buffer, bool noautocmd, Error *err)
{
- win_T *win = find_window_by_handle(window, err), *save_curwin = curwin;
+ win_T *win = find_window_by_handle(window, err);
buf_T *buf = find_buffer_by_handle(buffer, err);
- tabpage_T *tab = win_find_tabpage(win), *save_curtab = curtab;
+ tabpage_T *tab = win_find_tabpage(win);
if (!win || !buf) {
return;
}
-
if (noautocmd) {
block_autocmds();
}
- if (switch_win_noblock(&save_curwin, &save_curtab, win, tab, false) == FAIL) {
+
+ switchwin_T switchwin;
+ if (switch_win_noblock(&switchwin, win, tab, false) == FAIL) {
api_set_error(err,
kErrorTypeException,
"Failed to switch to window %d",
@@ -608,7 +609,7 @@ void win_set_buf(Window window, Buffer buffer, bool noautocmd, Error *err)
// So do it now.
validate_cursor();
- restore_win_noblock(save_curwin, save_curtab, false);
+ restore_win_noblock(&switchwin, false);
if (noautocmd) {
unblock_autocmds();
}
@@ -957,6 +958,11 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
int wmh1;
bool did_set_fraction = false;
+ // aucmd_win should always remain floating
+ if (new_wp != NULL && new_wp == aucmd_win) {
+ return FAIL;
+ }
+
if (flags & WSP_TOP) {
oldwin = firstwin;
} else if (flags & WSP_BOT || curwin->w_floating) {
@@ -1481,8 +1487,6 @@ static void win_init(win_T *newp, win_T *oldp, int flags)
copyFoldingState(oldp, newp);
win_init_some(newp, oldp);
-
- didset_window_options(newp);
}
/*
@@ -1591,7 +1595,7 @@ int make_windows(int count, bool vertical)
int todo;
if (vertical) {
- // Each windows needs at least 'winminwidth' lines and a separator
+ // Each window needs at least 'winminwidth' lines and a separator
// column.
maxcount = (curwin->w_width + curwin->w_vsep_width
- (p_wiw - p_wmw)) / (p_wmw + 1);
@@ -1728,6 +1732,12 @@ static void win_exchange(long Prenum)
(void)win_comp_pos(); // recompute window positions
+ if (wp->w_buffer != curbuf) {
+ reset_VIsual_and_resel();
+ } else if (VIsual_active) {
+ wp->w_cursor = curwin->w_cursor;
+ }
+
win_enter(wp, true);
redraw_later(curwin, NOT_VALID);
redraw_later(wp, NOT_VALID);
@@ -1828,6 +1838,9 @@ static void win_totop(int size, int flags)
beep_flush();
return;
}
+ if (curwin == aucmd_win) {
+ return;
+ }
if (curwin->w_floating) {
ui_comp_remove_grid(&curwin->w_grid_alloc);
@@ -2119,7 +2132,7 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int
m = frame_minheight(topfr, next_curwin);
room = height - m;
if (room < 0) {
- // The room is less then 'winheight', use all space for the
+ // The room is less than 'winheight', use all space for the
// current window.
next_curwin_size = p_wh + room;
room = 0;
@@ -2289,7 +2302,7 @@ void close_windows(buf_T *buf, int keep_curwin)
for (win_T *wp = firstwin; wp != NULL && !ONE_WINDOW;) {
if (wp->w_buffer == buf && (!keep_curwin || wp != curwin)
&& !(wp->w_closing || wp->w_buffer->b_locked > 0)) {
- if (win_close(wp, false) == FAIL) {
+ if (win_close(wp, false, false) == FAIL) {
// If closing the window fails give up, to avoid looping forever.
break;
}
@@ -2367,6 +2380,22 @@ bool last_nonfloat(win_T *wp) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
return wp != NULL && firstwin == wp && !(wp->w_next && !wp->w_floating);
}
+/// Check if floating windows can be closed.
+///
+/// @return true if all floating windows can be closed
+static bool can_close_floating_windows(tabpage_T *tab)
+{
+ FOR_ALL_WINDOWS_IN_TAB(wp, tab) {
+ buf_T *buf = wp->w_buffer;
+ int need_hide = (bufIsChanged(buf) && buf->b_nwindows <= 1);
+
+ if (need_hide && !buf_hide(buf)) {
+ return false;
+ }
+ }
+ return true;
+}
+
/// Close the possibly last window in a tab page.
///
/// @param win window to close
@@ -2431,7 +2460,7 @@ static bool close_last_window_tabpage(win_T *win, bool free_buf, tabpage_T *prev
//
// Called by :quit, :close, :xit, :wq and findtag().
// Returns FAIL when the window was not closed.
-int win_close(win_T *win, bool free_buf)
+int win_close(win_T *win, bool free_buf, bool force)
{
win_T *wp;
bool other_buffer = false;
@@ -2461,9 +2490,18 @@ int win_close(win_T *win, bool free_buf)
}
if ((firstwin == win && lastwin_nofloating() == win)
&& lastwin->w_floating) {
- // TODO(bfredl): we might close the float also instead
- emsg(e_floatonly);
- return FAIL;
+ if (force || can_close_floating_windows(curtab)) {
+ win_T *nextwp;
+ for (win_T *wpp = firstwin; wpp != NULL; wpp = nextwp) {
+ nextwp = wpp->w_next;
+ if (wpp->w_floating) {
+ win_close(wpp, free_buf, force);
+ }
+ }
+ } else {
+ emsg(e_floatonly);
+ return FAIL;
+ }
}
// When closing the last window in a tab page first go to another tab page
@@ -3055,9 +3093,21 @@ static frame_T *win_altframe(win_T *win, tabpage_T *tp)
return frp->fr_prev;
}
+ // By default the next window will get the space that was abandoned by this
+ // window
frame_T *target_fr = frp->fr_next;
frame_T *other_fr = frp->fr_prev;
- if (p_spr || p_sb) {
+
+ // If this is part of a column of windows and 'splitbelow' is true then the
+ // previous window will get the space.
+ if (frp->fr_parent != NULL && frp->fr_parent->fr_layout == FR_COL && p_sb) {
+ target_fr = frp->fr_prev;
+ other_fr = frp->fr_next;
+ }
+
+ // If this is part of a row of windows, and 'splitright' is true then the
+ // previous window will get the space.
+ if (frp->fr_parent != NULL && frp->fr_parent->fr_layout == FR_ROW && p_spr) {
target_fr = frp->fr_prev;
other_fr = frp->fr_next;
}
@@ -3610,7 +3660,9 @@ void close_others(int message, int forceit)
continue;
}
}
- win_close(wp, !buf_hide(wp->w_buffer) && !bufIsChanged(wp->w_buffer));
+ win_close(wp,
+ !buf_hide(wp->w_buffer) && !bufIsChanged(wp->w_buffer),
+ false);
}
if (message && !ONE_WINDOW) {
@@ -4644,20 +4696,29 @@ void fix_current_dir(void)
globaldir = (char_u *)xstrdup(cwd);
}
}
+ bool dir_differs = pathcmp(new_dir, cwd, -1) != 0;
+ if (!p_acd && dir_differs) {
+ do_autocmd_dirchanged(new_dir, curwin->w_localdir ? kCdScopeWindow : kCdScopeTabpage,
+ kCdCauseWindow, true);
+ }
if (os_chdir(new_dir) == 0) {
- if (!p_acd && pathcmp(new_dir, cwd, -1) != 0) {
- do_autocmd_dirchanged(new_dir, curwin->w_localdir
- ? kCdScopeWindow : kCdScopeTabpage, kCdCauseWindow);
+ if (!p_acd && dir_differs) {
+ do_autocmd_dirchanged(new_dir, curwin->w_localdir ? kCdScopeWindow : kCdScopeTabpage,
+ kCdCauseWindow, false);
}
- last_chdir_reason = NULL;
- shorten_fnames(true);
}
+ last_chdir_reason = NULL;
+ shorten_fnames(true);
} else if (globaldir != NULL) {
// Window doesn't have a local directory and we are not in the global
// directory: Change to the global directory.
+ bool dir_differs = pathcmp((char *)globaldir, cwd, -1) != 0;
+ if (!p_acd && dir_differs) {
+ do_autocmd_dirchanged((char *)globaldir, kCdScopeGlobal, kCdCauseWindow, true);
+ }
if (os_chdir((char *)globaldir) == 0) {
- if (!p_acd && pathcmp((char *)globaldir, cwd, -1) != 0) {
- do_autocmd_dirchanged((char *)globaldir, kCdScopeGlobal, kCdCauseWindow);
+ if (!p_acd && dir_differs) {
+ do_autocmd_dirchanged((char *)globaldir, kCdScopeGlobal, kCdCauseWindow, false);
}
}
XFREE_CLEAR(globaldir);
@@ -5795,7 +5856,6 @@ void win_drag_vsep_line(win_T *dragwin, int offset)
}
fr = curfr; // put fr at window that grows
}
- assert(fr);
// Not enough room
if (room < offset) {
@@ -5808,7 +5868,9 @@ void win_drag_vsep_line(win_T *dragwin, int offset)
}
if (fr == NULL) {
- return; // Safety check, should not happen.
+ // This can happen when calling win_move_separator() on the rightmost
+ // window. Just don't do anything.
+ return;
}
// grow frame fr by offset lines
@@ -6630,20 +6692,27 @@ static win_T *get_snapshot_focus(int idx)
/// triggered, another tabpage access is limited.
///
/// @return FAIL if switching to "win" failed.
-int switch_win(win_T **save_curwin, tabpage_T **save_curtab, win_T *win, tabpage_T *tp,
- bool no_display)
+int switch_win(switchwin_T *switchwin, win_T *win, tabpage_T *tp, bool no_display)
{
block_autocmds();
- return switch_win_noblock(save_curwin, save_curtab, win, tp, no_display);
+ return switch_win_noblock(switchwin, win, tp, no_display);
}
// As switch_win() but without blocking autocommands.
-int switch_win_noblock(win_T **save_curwin, tabpage_T **save_curtab, win_T *win, tabpage_T *tp,
- bool no_display)
+int switch_win_noblock(switchwin_T *switchwin, win_T *win, tabpage_T *tp, bool no_display)
{
- *save_curwin = curwin;
+ memset(switchwin, 0, sizeof(switchwin_T));
+ switchwin->sw_curwin = curwin;
+ if (win == curwin) {
+ switchwin->sw_same_win = true;
+ } else {
+ // Disable Visual selection, because redrawing may fail.
+ switchwin->sw_visual_active = VIsual_active;
+ VIsual_active = false;
+ }
+
if (tp != NULL) {
- *save_curtab = curtab;
+ switchwin->sw_curtab = curtab;
if (no_display) {
curtab->tp_firstwin = firstwin;
curtab->tp_lastwin = lastwin;
@@ -6665,28 +6734,33 @@ int switch_win_noblock(win_T **save_curwin, tabpage_T **save_curtab, win_T *win,
// Restore current tabpage and window saved by switch_win(), if still valid.
// When "no_display" is true the display won't be affected, no redraw is
// triggered.
-void restore_win(win_T *save_curwin, tabpage_T *save_curtab, bool no_display)
+void restore_win(switchwin_T *switchwin, bool no_display)
{
- restore_win_noblock(save_curwin, save_curtab, no_display);
+ restore_win_noblock(switchwin, no_display);
unblock_autocmds();
}
// As restore_win() but without unblocking autocommands.
-void restore_win_noblock(win_T *save_curwin, tabpage_T *save_curtab, bool no_display)
+void restore_win_noblock(switchwin_T *switchwin, bool no_display)
{
- if (save_curtab != NULL && valid_tabpage(save_curtab)) {
+ if (switchwin->sw_curtab != NULL && valid_tabpage(switchwin->sw_curtab)) {
if (no_display) {
curtab->tp_firstwin = firstwin;
curtab->tp_lastwin = lastwin;
- curtab = save_curtab;
+ curtab = switchwin->sw_curtab;
firstwin = curtab->tp_firstwin;
lastwin = curtab->tp_lastwin;
} else {
- goto_tabpage_tp(save_curtab, false, false);
+ goto_tabpage_tp(switchwin->sw_curtab, false, false);
}
}
- if (win_valid(save_curwin)) {
- curwin = save_curwin;
+
+ if (!switchwin->sw_same_win) {
+ VIsual_active = switchwin->sw_visual_active;
+ }
+
+ if (win_valid(switchwin->sw_curwin)) {
+ curwin = switchwin->sw_curwin;
curbuf = curwin->w_buffer;
}
// If called by win_execute() and executing the command changed the
diff --git a/src/nvim/window.h b/src/nvim/window.h
index 7e465a9f08..e2fd2c515d 100644
--- a/src/nvim/window.h
+++ b/src/nvim/window.h
@@ -4,6 +4,7 @@
#include <stdbool.h>
#include "nvim/buffer_defs.h"
+#include "nvim/mark.h"
// Values for file_name_in_line()
#define FNAME_MESS 1 // give error message
@@ -32,6 +33,38 @@
#define MIN_COLUMNS 12 // minimal columns for screen
#define MIN_LINES 2 // minimal lines for screen
+/// Structure used by switch_win() to pass values to restore_win()
+typedef struct {
+ win_T *sw_curwin;
+ tabpage_T *sw_curtab;
+ bool sw_same_win; ///< VIsual_active was not reset
+ bool sw_visual_active;
+} switchwin_T;
+
+/// Execute a block of code in the context of window `wp` in tabpage `tp`.
+/// Ensures the status line is redrawn and cursor position is valid if it is moved.
+#define WIN_EXECUTE(wp, tp, block) \
+ do { \
+ win_T *const wp_ = (wp); \
+ const pos_T curpos_ = wp_->w_cursor; \
+ switchwin_T switchwin_; \
+ if (switch_win_noblock(&switchwin_, wp_, (tp), true) == OK) { \
+ check_cursor(); \
+ block; \
+ } \
+ restore_win_noblock(&switchwin_, true); \
+ /* Update the status line if the cursor moved. */ \
+ if (win_valid(wp_) && !equalpos(curpos_, wp_->w_cursor)) { \
+ wp_->w_redr_status = true; \
+ } \
+ /* In case the command moved the cursor or changed the Visual area, */ \
+ /* check it is valid. */ \
+ check_cursor(); \
+ if (VIsual_active) { \
+ check_pos(curbuf, &VIsual); \
+ } \
+ } while (false)
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "window.h.generated.h"
#endif