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.txt3
-rw-r--r--src/nvim/api/autocmd.c669
-rw-r--r--src/nvim/api/autocmd.h11
-rw-r--r--src/nvim/api/buffer.c119
-rw-r--r--src/nvim/api/keysets.lua30
-rw-r--r--src/nvim/api/private/dispatch.c20
-rw-r--r--src/nvim/api/private/helpers.c81
-rw-r--r--src/nvim/api/private/helpers.h17
-rw-r--r--src/nvim/api/vim.c8
-rw-r--r--src/nvim/api/window.c3
-rw-r--r--src/nvim/aucmd.c123
-rw-r--r--src/nvim/aucmd.h11
-rw-r--r--src/nvim/autocmd.c1347
-rw-r--r--src/nvim/autocmd.h44
-rw-r--r--src/nvim/buffer.c11
-rw-r--r--src/nvim/buffer_defs.h2
-rw-r--r--src/nvim/charset.c4
-rw-r--r--src/nvim/diff.c9
-rw-r--r--src/nvim/eval.c12
-rw-r--r--src/nvim/eval/funcs.c5
-rw-r--r--src/nvim/eval/typval.c19
-rw-r--r--src/nvim/eval/typval.h3
-rw-r--r--src/nvim/ex_cmds.c2
-rw-r--r--src/nvim/ex_cmds_defs.h30
-rw-r--r--src/nvim/ex_docmd.c26
-rw-r--r--src/nvim/ex_getln.c4
-rw-r--r--src/nvim/fileio.c2
-rw-r--r--src/nvim/getchar.c47
-rw-r--r--src/nvim/globals.h20
-rw-r--r--src/nvim/hardcopy.c43
-rw-r--r--src/nvim/highlight.c23
-rw-r--r--src/nvim/keymap.h3
-rw-r--r--src/nvim/lua/converter.c22
-rw-r--r--src/nvim/lua/executor.c505
-rw-r--r--src/nvim/lua/executor.h24
-rw-r--r--src/nvim/lua/stdlib.c86
-rw-r--r--src/nvim/lua/vim.lua47
-rw-r--r--src/nvim/main.c8
-rw-r--r--src/nvim/map.c1
-rw-r--r--src/nvim/map.h1
-rw-r--r--src/nvim/option.c4
-rw-r--r--src/nvim/os/fs.c34
-rw-r--r--src/nvim/runtime.c54
-rw-r--r--src/nvim/screen.c33
-rwxr-xr-xsrc/nvim/testdir/runnvim.sh6
-rw-r--r--src/nvim/testdir/test_alot.vim3
-rw-r--r--src/nvim/testdir/test_autocmd.vim6
-rw-r--r--src/nvim/testdir/test_breakindent.vim2
-rw-r--r--src/nvim/testdir/test_cindent.vim9
-rw-r--r--src/nvim/testdir/test_digraph.vim4
-rw-r--r--src/nvim/testdir/test_edit.vim2
-rw-r--r--src/nvim/testdir/test_filetype.vim38
-rw-r--r--src/nvim/testdir/test_functions.vim4
-rw-r--r--src/nvim/testdir/test_mapping.vim78
-rw-r--r--src/nvim/testdir/test_normal.vim8
-rw-r--r--src/nvim/testdir/test_profile.vim31
-rw-r--r--src/nvim/testdir/test_quickfix.vim2
-rw-r--r--src/nvim/testdir/test_registers.vim2
-rw-r--r--src/nvim/testdir/test_stat.vim2
-rw-r--r--src/nvim/testdir/test_suspend.vim4
-rw-r--r--src/nvim/testdir/test_system.vim26
-rw-r--r--src/nvim/testdir/test_vimscript.vim5
-rw-r--r--src/nvim/tui/input.c4
-rw-r--r--src/nvim/ui.c2
-rw-r--r--src/nvim/viml/parser/expressions.c4
-rw-r--r--src/nvim/window.c8
68 files changed, 2920 insertions, 922 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 dcc20194f0..3ba3923a82 100644
--- a/src/nvim/CMakeLists.txt
+++ b/src/nvim/CMakeLists.txt
@@ -63,6 +63,7 @@ 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(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")
@@ -336,6 +337,7 @@ add_custom_command(
${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
DEPENDS
${CHAR_BLOB_GENERATOR}
${LUA_VIM_MODULE_SOURCE}
@@ -344,6 +346,7 @@ add_custom_command(
${LUA_F_MODULE_SOURCE}
${LUA_META_MODULE_SOURCE}
${LUA_FILETYPE_MODULE_SOURCE}
+ ${LUA_LOAD_PACKAGE_MODULE_SOURCE}
VERBATIM
)
diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c
new file mode 100644
index 0000000000..deb8ec8cf3
--- /dev/null
+++ b/src/nvim/api/autocmd.c
@@ -0,0 +1,669 @@
+#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
+
+// Check whether every item in the array is a kObjectTypeString
+#define CHECK_STRING_ARRAY(__array, k, v, goto_name) \
+ for (size_t j = 0; j < __array.size; j++) { \
+ Object item = __array.items[j]; \
+ if (item.type != kObjectTypeString) { \
+ api_set_error(err, \
+ kErrorTypeValidation, \
+ "All entries in '%s' must be strings", \
+ k); \
+ goto goto_name; \
+ } \
+ }
+
+// Copy string or array of strings into an empty array.
+#define UNPACK_STRING_OR_ARRAY(__array, k, v, goto_name) \
+ if (v->type == kObjectTypeString) { \
+ ADD(__array, copy_object(*v)); \
+ } else if (v->type == kObjectTypeArray) { \
+ CHECK_STRING_ARRAY(__array, k, v, goto_name); \
+ __array = copy_array(v->data.array); \
+ } else { \
+ api_set_error(err, \
+ kErrorTypeValidation, \
+ "'%s' must be an array or a string.", \
+ k); \
+ goto goto_name; \
+ }
+
+// 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}.
+/// group
+/// event
+/// pattern
+///
+/// -- @param {string} event - event or events to match against
+/// vim.api.nvim_get_autocmds({ event = "FileType" })
+///
+Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
+ FUNC_API_SINCE(9)
+{
+ 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, "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;
+}
+
+/// Define an autocmd.
+/// @param opts Dictionary
+/// Required keys:
+/// event: string | ArrayOf(string)
+/// event = "pat1,pat2,pat3",
+/// event = "pat1"
+/// event = {"pat1"}
+/// event = {"pat1", "pat2", "pat3"}
+///
+///
+/// -- @param {string} name - augroup name
+/// -- @param {string | table} event - event or events to match against
+/// -- @param {string | table} pattern - pattern or patterns to match against
+/// -- @param {string | function} callback - function or string to execute on autocmd
+/// -- @param {string} command - optional, vimscript command
+/// Eg. command = "let g:value_set = v:true"
+/// -- @param {boolean} once - optional, defaults to false
+///
+/// -- pattern = comma delimited list of patterns | pattern | { pattern, ... }
+///
+/// pattern = "*.py,*.pyi"
+/// pattern = "*.py"
+/// pattern = {"*.py"}
+/// pattern = { "*.py", "*.pyi" }
+///
+/// -- not supported
+/// pattern = {"*.py,*.pyi"}
+///
+/// -- event = string | string[]
+/// event = "FileType,CursorHold"
+/// event = "BufPreWrite"
+/// event = {"BufPostWrite"}
+/// event = {"CursorHold", "BufPreWrite", "BufPostWrite"}
+Integer nvim_create_autocmd(uint64_t channel_id, 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 (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;
+ }
+
+ if (opts->event.type != kObjectTypeNil) {
+ UNPACK_STRING_OR_ARRAY(event_array, "event", (&opts->event), 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);
+
+ // TOOD: 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) {
+ CHECK_STRING_ARRAY(patterns, "pattern", v, 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.
+///
+/// @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 = name, clear = false });
+/// </pre>
+///
+/// @param opts Parameters
+/// - name (string): The name of the augroup
+/// - clear (bool): Whether to clear existing commands or not.
+// Defaults to true.
+/// See |autocmd-groups|
+Integer nvim_create_augroup(uint64_t channel_id, Dict(create_augroup) *opts, Error *err)
+ FUNC_API_SINCE(9)
+{
+ bool clear_autocmds = api_object_to_bool(opts->clear, "clear", true, err);
+
+ if (opts->name.type != kObjectTypeString) {
+ api_set_error(err, kErrorTypeValidation, "'name' is required and must be a string");
+ return -1;
+ }
+ char *name = opts->name.data.string.data;
+
+ int augroup = -1;
+ WITH_SCRIPT_CONTEXT(channel_id, {
+ augroup = augroup_add(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;
+}
+
+/// 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);
+}
+
+/// 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);
+}
+
+/// -- @param {string} group - autocmd group name
+/// -- @param {number} buffer - buffer number
+/// -- @param {string | table} event - event or events to match against
+/// -- @param {string | table} pattern - optional, defaults to "*".
+/// vim.api.nvim_do_autcmd({ group, buffer, pattern, event, modeline })
+void nvim_do_autocmd(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 (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;
+ }
+
+ if (opts->event.type != kObjectTypeNil) {
+ UNPACK_STRING_OR_ARRAY(event_array, "event", (&opts->event), cleanup)
+ }
+
+ if (opts->modeline.type != kObjectTypeNil) {
+ 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);
+}
+
+
+#undef UNPACK_STRING_OR_ARRAY
+#undef CHECK_STRING_ARRAY
+#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 2d5403d4b8..922d288da1 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.
@@ -1386,11 +1487,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/keysets.lua b/src/nvim/api/keysets.lua
index f6dce1905e..be71c446b1 100644
--- a/src/nvim/api/keysets.lua
+++ b/src/nvim/api/keysets.lua
@@ -97,6 +97,7 @@ return {
"special"; "sp";
"link";
"fallback";
+ "blend";
"temp";
};
highlight_cterm = {
@@ -109,5 +110,34 @@ return {
"reverse";
"nocombine";
};
+ -- Autocmds
+ create_autocmd = {
+ "buffer";
+ "callback";
+ "command";
+ "desc";
+ "event";
+ "group";
+ "once";
+ "nested";
+ "pattern";
+ };
+ do_autocmd = {
+ "buffer";
+ "event";
+ "group";
+ "modeline";
+ "pattern";
+ };
+ get_autocmds = {
+ "event";
+ "group";
+ "id";
+ "pattern";
+ };
+ create_augroup = {
+ "clear";
+ "name";
+ };
}
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 2b107a3f27..3d4a04f096 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -396,22 +396,16 @@ 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;
-
- 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;
+ WITH_SCRIPT_CONTEXT(channel_id, {
+ 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);
+ });
}
-
buf_T *find_buffer_by_handle(Buffer buffer, Error *err)
{
if (buffer == 0) {
@@ -758,6 +752,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)
{
@@ -1569,3 +1609,16 @@ 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;
+ }
+}
diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h
index 6d0aec9c90..6969994c3b 100644
--- a/src/nvim/api/private/helpers.h
+++ b/src/nvim/api/private/helpers.h
@@ -138,10 +138,27 @@ 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) \
+ 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;
+
#endif // NVIM_API_PRIVATE_HELPERS_H
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 11bb1750e4..302dccbde7 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -124,6 +124,10 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Error *err)
/// Set a highlight group.
///
+/// 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.
@@ -1951,7 +1955,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;
}
@@ -2411,6 +2415,8 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
/// 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>|
diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c
index fc7823a070..9c473ff724 100644
--- a/src/nvim/api/window.c
+++ b/src/nvim/api/window.c
@@ -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
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/autocmd.c b/src/nvim/autocmd.c
index 9117dde089..a9e1978ac0 100644
--- a/src/nvim/autocmd.c
+++ b/src/nvim/autocmd.c
@@ -2,10 +2,15 @@
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
// autocmd.c: Autocommand related functions
+#include <signal.h>
+#include "nvim/autocmd.h"
+
+// #include "nvim/api/private/handle.h"
+
+#include "lauxlib.h"
#include "nvim/api/private/helpers.h"
#include "nvim/ascii.h"
-#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
@@ -13,8 +18,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 +36,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 +83,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 +104,33 @@ 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
+
+/// Handles grabbing arguments from `:autocmd` such as ++once and ++nested
+#define ARG_GET_FLAG(errored, cmd, flag, pattern, len) \
+ if (STRNCMP(cmd, pattern, len) == 0 && ascii_iswhite((cmd)[len])) { \
+ if (flag) { \
+ semsg(_(e_duparg2), pattern); \
+ (errored) = true; \
+ } \
+ (flag) = true; \
+ (cmd) = skipwhite((cmd) + (len)); \
+ }
+
+// 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 +140,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 +149,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 +171,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 +187,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 +284,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 +303,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 +320,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 +346,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 +367,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 +384,61 @@ void aubuflocal_remove(buf_T *buf)
au_cleanup();
}
-// Add an autocmd group name.
+// Add an autocmd group name or return existing group matching name.
// Return its ID.
-static int au_new_group(char_u *name)
+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.
+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 +447,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,20 +520,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
- current_augroup = au_new_group(arg);
+ 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();
}
@@ -396,25 +549,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;
@@ -457,33 +615,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'.
///
@@ -601,12 +732,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;
}
@@ -643,37 +774,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);
- }
+ ARG_GET_FLAG(invalid_flags, cmd, once, "++once", 6);
+ ARG_GET_FLAG(invalid_flags, cmd, nested, "++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.
+ ARG_GET_FLAG(invalid_flags, cmd, nested, "nested", 6);
}
}
+ if (invalid_flags) {
+ return;
+ }
+
// Find the start of the commands.
// Expand <sfile> in it.
if (*cmd != NUL) {
@@ -685,36 +801,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 {
- for (event_T event = (event_T)0; event < NUM_EVENTS;
- event = (event_T)(event + 1)) {
+ 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 {
+ 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) {
@@ -723,30 +840,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.
@@ -756,216 +857,293 @@ 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;
+ 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;
///
@@ -981,7 +1159,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"));
@@ -990,7 +1168,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;
}
@@ -1378,8 +1556,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;
@@ -1402,7 +1580,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.
@@ -1814,6 +1992,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;
@@ -1826,7 +2009,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 {
@@ -1856,14 +2040,32 @@ 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);
+
+ // 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 woudl 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;
@@ -1928,19 +2130,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
@@ -1952,7 +2147,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])) {
@@ -1993,16 +2188,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
@@ -2045,7 +2249,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.
@@ -2109,3 +2313,346 @@ 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;
+}
+
+
+// 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 63c5abd4f8..0a1f036183 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 {
@@ -18,22 +23,23 @@ typedef struct {
} 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; // TODO(tjdevries): Explain
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
@@ -41,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
@@ -75,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 aada11bc9e..9e82b4e80b 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -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,6 +1454,7 @@ void set_curbuf(buf_T *buf, int action)
}
if (bufref_valid(&prevbufref) && !aborting()) {
win_T *previouswin = curwin;
+
// Do not sync when in Insert mode and the buffer is open in
// another window, might be a timer doing something in another
// window.
@@ -5494,11 +5495,19 @@ static int buf_signcols_inner(buf_T *buf, int maximum)
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 7ae5df164f..1e0c837056 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -864,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
@@ -1353,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/charset.c b/src/nvim/charset.c
index 583a040ed1..f4882e57e1 100644
--- a/src/nvim/charset.c
+++ b/src/nvim/charset.c
@@ -1439,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,
@@ -1585,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/diff.c b/src/nvim/diff.c
index 233753839b..80bd3229c6 100644
--- a/src/nvim/diff.c
+++ b/src/nvim/diff.c
@@ -85,9 +85,9 @@ typedef struct {
// used for recording hunks from xdiff
typedef struct {
linenr_T lnum_orig;
- long count_orig;
+ long count_orig;
linenr_T lnum_new;
- long count_new;
+ long count_new;
} diffhunk_T;
// two diff inputs and one result
@@ -1285,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");
}
}
@@ -3159,8 +3159,7 @@ static int parse_diff_unified(char_u *line, diffhunk_T *hunk)
/// Callback function for the xdl_diff() function.
/// Stores the diff output in a grow array.
///
-static int xdiff_out(long start_a, long count_a, long start_b, long count_b,
- void *priv)
+static int xdiff_out(long start_a, long count_a, long start_b, long count_b, void *priv)
{
diffout_T *dout = (diffout_T *)priv;
diffhunk_T *p = xmalloc(sizeof(*p));
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 07f0799bc4..0322898827 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"
@@ -3258,7 +3259,7 @@ char_u *get_user_var_name(expand_T *xp, int idx)
// b: variables
// In cmdwin, the alternative buffer should be used.
hashtab_T *ht
- = is_in_cmdwin() ? &prevwin->w_buffer->b_vars->dv_hashtab : &curbuf->b_vars->dv_hashtab;
+ = 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;
@@ -7746,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) {
@@ -7775,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;
@@ -7786,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;
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index c6baa105b0..49dde537c3 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -894,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 {
@@ -3225,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;
@@ -4242,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;
}
diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c
index 6f8b032d41..fbda7fbc3c 100644
--- a/src/nvim/eval/typval.c
+++ b/src/nvim/eval/typval.c
@@ -28,11 +28,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 +1123,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 +1144,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 +1154,12 @@ void callback_free(Callback *callback)
callback->data.funcref = NULL;
}
+/// Check if callback is freed
+bool callback_is_freed(Callback callback)
+{
+ return false;
+}
+
/// Copy a callback into a typval_T.
void callback_put(Callback *cb, typval_T *tv)
FUNC_ATTR_NONNULL_ALL
@@ -1164,6 +1175,9 @@ 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): I'm not even sure if this is strictly necessary?
+ abort();
default:
tv->v_type = VAR_SPECIAL;
tv->vval.v_special = kSpecialVarNull;
@@ -1185,6 +1199,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;
diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h
index ad01c01499..40dc819754 100644
--- a/src/nvim/eval/typval.h
+++ b/src/nvim/eval/typval.h
@@ -72,15 +72,18 @@ 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_INIT { .type = kCallbackNone }
#define CALLBACK_NONE ((Callback)CALLBACK_INIT)
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 49bf9972b1..c7910e148d 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -1943,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);
diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h
index eaf5f627b6..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"
@@ -91,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.
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index e8b8dc799c..152a41c407 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -5802,6 +5802,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>
*/
@@ -7506,7 +7530,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)
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index ed4475eb1a..30287cd6f2 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -5049,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 },
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index f5824d83be..965aa8749d 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -3796,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);
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index 741fc6d803..f249cde9a0 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -1710,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.
@@ -1725,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)))
@@ -1813,7 +1822,7 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth)
break;
}
}
- if (n >= 0) {
+ if (!is_plug_map && n >= 0) {
continue;
}
@@ -1949,8 +1958,11 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth)
// 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;
@@ -1960,6 +1972,32 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth)
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 {
@@ -2455,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!!!!
///
@@ -3490,6 +3528,7 @@ static void showmap(mapblock_T *mp, bool local)
if (p_verbose > 0) {
last_set_msg(mp->m_script_ctx);
}
+ msg_clr_eos();
ui_flush(); // show one line at a time
}
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index 5aa564623f..35ad57906b 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -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 });
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 8b998ff62e..e43a56086f 100644
--- a/src/nvim/highlight.c
+++ b/src/nvim/highlight.c
@@ -800,6 +800,7 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e
{
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;
@@ -847,6 +848,20 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e
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;
+ }
+
if (HAS_KEY(dict->link)) {
if (link_id) {
*link_id = object_to_hl_id(dict->link, "link", err);
@@ -908,6 +923,7 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e
hlattrs.rgb_bg_color = bg;
hlattrs.rgb_fg_color = fg;
hlattrs.rgb_sp_color = sp;
+ 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;
@@ -927,10 +943,11 @@ int object_to_color(Object val, char *key, bool rgb, Error *err)
} 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 (!str.size) {
- color = 0;
- } else if (rgb) {
+ if (rgb) {
color = name_to_color(str.data);
} else {
color = name_to_ctermcolor(str.data);
diff --git a/src/nvim/keymap.h b/src/nvim/keymap.h
index ae2ec7835e..4bf0617dfa 100644
--- a/src/nvim/keymap.h
+++ b/src/nvim/keymap.h
@@ -245,6 +245,7 @@ enum key_extra {
KE_EVENT = 102, // event
KE_LUA = 103, // lua special key
KE_COMMAND = 104, // <Cmd> special key
+ KE_AUCMD_SPECIAL = 105,
};
/*
@@ -443,6 +444,8 @@ enum key_extra {
#define K_COMMAND TERMCAP2KEY(KS_EXTRA, KE_COMMAND)
#define K_LUA TERMCAP2KEY(KS_EXTRA, KE_LUA)
+#define K_AUCMD_SPECIAL TERMCAP2KEY(KS_EXTRA, KE_AUCMD_SPECIAL)
+
// Bits for modifier mask
// 0x01 cannot be used, because the modifier must be 0x02 or higher
#define MOD_MASK_SHIFT 0x02
diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c
index 0fbd56ed53..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)
@@ -734,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);
}
}
@@ -782,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: {
@@ -1199,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) {
@@ -1240,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;
}
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index 029e7eb660..18abf04ff6 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -45,6 +45,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;
@@ -65,11 +67,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.
@@ -122,8 +129,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);
}
@@ -148,7 +168,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
@@ -162,12 +182,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"));
}
@@ -184,7 +304,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);
@@ -302,6 +422,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.
@@ -362,80 +628,22 @@ 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")
- || nlua_pcall(lstate, 0, 1)) {
- nlua_error(lstate, _("E5106: Error while creating inspect module: %.*s\n"));
- return 1;
- }
- // [package, loaded, inspect]
- lua_setfield(lstate, -2, "vim.inspect"); // [package, loaded]
-
- 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 1;
- }
- // [package, loaded, module]
- lua_setfield(lstate, -2, "vim.F"); // [package, loaded]
-
- code = (char *)&lua_filetype_module[0];
+ 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 vim.filetype module: %.*s"));
@@ -495,9 +703,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)
{
@@ -505,26 +764,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];
@@ -602,9 +865,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 });
@@ -613,10 +885,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);
}
@@ -806,40 +1080,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
@@ -861,7 +1148,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;
}
@@ -1063,6 +1350,16 @@ 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);
+ 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)
@@ -1391,6 +1688,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)
@@ -1407,7 +1711,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);
}
@@ -1457,7 +1761,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);
@@ -1520,8 +1824,31 @@ void nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap)
lua_pushinteger(lstate, eap->line2);
lua_setfield(lstate, -2, "line2");
+ lua_newtable(lstate); // f-args table
lua_pushstring(lstate, (const char *)eap->arg);
- lua_setfield(lstate, -2, "args");
+ 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");
diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h
index bf78f7ec5e..47ac51dadb 100644
--- a/src/nvim/lua/executor.h
+++ b/src/nvim/lua/executor.h
@@ -4,6 +4,7 @@
#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"
@@ -14,18 +15,14 @@
// 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 { \
@@ -39,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/stdlib.c b/src/nvim/lua/stdlib.c
index 55b23cf0c8..c2ce899a74 100644
--- a/src/nvim/lua/stdlib.c
+++ b/src/nvim/lua/stdlib.c
@@ -471,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);
@@ -526,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/vim.lua b/src/nvim/lua/vim.lua
index f5993c3f55..68de4f960c 100644
--- a/src/nvim/lua/vim.lua
+++ b/src/nvim/lua/vim.lua
@@ -43,53 +43,6 @@ assert(vim.inspect)
vim.filetype = package.loaded['vim.filetype']
assert(vim.filetype)
-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
-
- 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
-
--- Insert vim._load_package after the preloader at position 2
-table.insert(package.loaders, 2, vim._load_package)
-
-- These are for loading runtime modules lazily since they aren't available in
-- the nvim binary as specified in executor.c
setmetatable(vim, {
diff --git a/src/nvim/main.c b/src/nvim/main.c
index 8991ae7f00..b02ebbe030 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.
diff --git a/src/nvim/map.c b/src/nvim/map.c
index 77ebc2a387..091d653046 100644
--- a/src/nvim/map.c
+++ b/src/nvim/map.c
@@ -177,6 +177,7 @@ MAP_IMPL(handle_T, ptr_t, DEFAULT_INITIALIZER)
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 5e56f4dd65..c9c89bf2fd 100644
--- a/src/nvim/map.h
+++ b/src/nvim/map.h
@@ -46,6 +46,7 @@ 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/option.c b/src/nvim/option.c
index c8e50d4494..4ec6ee3148 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -4277,7 +4277,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);
@@ -8165,7 +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, needed_signcols);
+ int ret = MAX(minimum, MIN(maximum, needed_signcols));
assert(ret <= SIGN_SHOW_MAX);
return ret;
}
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/runtime.c b/src/nvim/runtime.c
index 1c04cb16b3..1ec3e40abe 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)
@@ -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
{
@@ -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 15fb6901cc..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.
- win_signcol_count(wp);
-
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
@@ -1846,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));
@@ -2792,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);
@@ -2814,9 +2821,8 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc
// number.
if (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u'
&& num_signs > 0 && sign_get_attr(SIGN_TEXT, sattrs, 0, 1)) {
- int count = win_signcol_count(wp);
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);
@@ -4674,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;
diff --git a/src/nvim/testdir/runnvim.sh b/src/nvim/testdir/runnvim.sh
index fdd3f3144b..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 "$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 ""
- 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/test_alot.vim b/src/nvim/testdir/test_alot.vim
index c0ac4393c4..5a3d1d56bb 100644
--- a/src/nvim/testdir/test_alot.vim
+++ b/src/nvim/testdir/test_alot.vim
@@ -39,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_autocmd.vim b/src/nvim/testdir/test_autocmd.vim
index 0146c06109..c39546b9ea 100644
--- a/src/nvim/testdir/test_autocmd.vim
+++ b/src/nvim/testdir/test_autocmd.vim
@@ -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')
diff --git a/src/nvim/testdir/test_breakindent.vim b/src/nvim/testdir/test_breakindent.vim
index b619f2adb6..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')
diff --git a/src/nvim/testdir/test_cindent.vim b/src/nvim/testdir/test_cindent.vim
index 5dc54111e7..4b702bf2b8 100644
--- a/src/nvim/testdir/test_cindent.vim
+++ b/src/nvim/testdir/test_cindent.vim
@@ -815,7 +815,7 @@ func Test_cindent_1()
}
}
- public: // <-- this was incoreectly indented before!!
+ public: // <-- this was incorrectly indented before!!
void testfall();
protected:
void testfall();
@@ -1792,7 +1792,7 @@ func Test_cindent_1()
}
}
- public: // <-- this was incoreectly indented before!!
+ public: // <-- this was incorrectly indented before!!
void testfall();
protected:
void testfall();
@@ -5302,9 +5302,12 @@ 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
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_edit.vim b/src/nvim/testdir/test_edit.vim
index a1f6a84a99..360b3aaaa0 100644
--- a/src/nvim/testdir/test_edit.vim
+++ b/src/nvim/testdir/test_edit.vim
@@ -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)
diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim
index c2cfac6fe3..5f4a7dac6e 100644
--- a/src/nvim/testdir/test_filetype.vim
+++ b/src/nvim/testdir/test_filetype.vim
@@ -132,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'],
@@ -154,6 +155,7 @@ let s:filename_checks = {
\ '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'],
@@ -802,6 +804,42 @@ func Test_bas_file()
filetype off
endfunc
+func Test_d_file()
+ filetype on
+
+ call writefile(['looks like D'], 'Xfile.d')
+ split Xfile.d
+ call assert_equal('d', &filetype)
+ bwipe!
+
+ 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!
+
+ filetype off
+endfunc
+
func Test_dep3patch_file()
filetype on
diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim
index 6e36f4e3d2..994d74601a 100644
--- a/src/nvim/testdir/test_functions.vim
+++ b/src/nvim/testdir/test_functions.vim
@@ -1451,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
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_normal.vim b/src/nvim/testdir/test_normal.vim
index e8eebb3fdd..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)
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_quickfix.vim b/src/nvim/testdir/test_quickfix.vim
index c4d70fb1de..6852f53ea8 100644
--- a/src/nvim/testdir/test_quickfix.vim
+++ b/src/nvim/testdir/test_quickfix.vim
@@ -2739,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')
diff --git a/src/nvim/testdir/test_registers.vim b/src/nvim/testdir/test_registers.vim
index 2e92d9aa2f..23e39eba35 100644
--- a/src/nvim/testdir/test_registers.vim
+++ b/src/nvim/testdir/test_registers.vim
@@ -219,7 +219,7 @@ func Test_set_register()
call setreg('=', 'b', 'a')
call assert_equal('regwrite', getreg('='))
- " Test for settting a list of lines to special registers
+ " Test for setting a list of lines to special registers
call setreg('/', [])
call assert_equal('', @/)
call setreg('=', [])
diff --git a/src/nvim/testdir/test_stat.vim b/src/nvim/testdir/test_stat.vim
index b44f3e9b94..d3059664e9 100644
--- a/src/nvim/testdir/test_stat.vim
+++ b/src/nvim/testdir/test_stat.vim
@@ -7,7 +7,7 @@ func CheckFileTime(doSleep)
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
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_system.vim b/src/nvim/testdir/test_system.vim
index 5b8079d7b6..18692f42c9 100644
--- a/src/nvim/testdir/test_system.vim
+++ b/src/nvim/testdir/test_system.vim
@@ -50,11 +50,11 @@ endfunc
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 @@ func 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_vimscript.vim b/src/nvim/testdir/test_vimscript.vim
index 75a965f16d..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
"-------------------------------------------------------------------------------
@@ -1744,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:]]*')
diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c
index 6b889cf97c..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"
@@ -378,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/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/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c
index 8a14710351..9d1318724e 100644
--- a/src/nvim/viml/parser/expressions.c
+++ b/src/nvim/viml/parser/expressions.c
@@ -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/window.c b/src/nvim/window.c
index 43667377c5..83048d911f 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -958,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) {
@@ -1833,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);