aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rwxr-xr-xsrc/clint.py69
-rw-r--r--src/nvim/api/private/helpers.c78
-rw-r--r--src/nvim/api/vim.c2
-rw-r--r--src/nvim/auevents.lua3
-rw-r--r--src/nvim/buffer.c65
-rw-r--r--src/nvim/eval.c413
-rw-r--r--src/nvim/eval.lua3
-rw-r--r--src/nvim/eval/encode.c323
-rw-r--r--src/nvim/eval/typval_encode.c.h802
-rw-r--r--src/nvim/eval/typval_encode.h594
-rw-r--r--src/nvim/event/libuv_process.c3
-rw-r--r--src/nvim/event/process.c3
-rw-r--r--src/nvim/ex_cmds.c14
-rw-r--r--src/nvim/ex_cmds.lua6
-rw-r--r--src/nvim/ex_docmd.c69
-rw-r--r--src/nvim/ex_docmd.h15
-rw-r--r--src/nvim/ex_getln.c2
-rw-r--r--src/nvim/file_search.c56
-rw-r--r--src/nvim/fileio.c267
-rw-r--r--src/nvim/globals.h31
-rw-r--r--src/nvim/hashtab.c12
-rw-r--r--src/nvim/hashtab.h9
-rw-r--r--src/nvim/keymap.c6
-rw-r--r--src/nvim/mark.c27
-rw-r--r--src/nvim/memline.c9
-rw-r--r--src/nvim/memory.c49
-rw-r--r--src/nvim/memory.h34
-rw-r--r--src/nvim/message.c31
-rw-r--r--src/nvim/ops.c6
-rw-r--r--src/nvim/option.c31
-rw-r--r--src/nvim/options.lua6
-rw-r--r--src/nvim/os/env.c25
-rw-r--r--src/nvim/os/fs.c12
-rw-r--r--src/nvim/os/stdpaths.c28
-rw-r--r--src/nvim/os/time.c39
-rw-r--r--src/nvim/path.c47
-rw-r--r--src/nvim/po/uk.po4
-rw-r--r--src/nvim/screen.c20
-rw-r--r--src/nvim/search.c50
-rw-r--r--src/nvim/shada.c110
-rw-r--r--src/nvim/spell.c8
-rw-r--r--src/nvim/testdir/Makefile3
-rw-r--r--src/nvim/testdir/runtest.vim6
-rw-r--r--src/nvim/testdir/setup.vim7
-rw-r--r--src/nvim/testdir/test17.in14
-rw-r--r--src/nvim/testdir/test53.in59
-rw-r--r--src/nvim/testdir/test53.ok26
-rw-r--r--src/nvim/testdir/test73.in11
-rw-r--r--src/nvim/testdir/test_alot.vim1
-rw-r--r--src/nvim/testdir/test_autocmd.vim136
-rw-r--r--src/nvim/testdir/test_execute_func.vim55
-rw-r--r--src/nvim/testdir/test_expr.vim83
-rw-r--r--src/nvim/testdir/test_gn.vim93
-rw-r--r--src/nvim/testdir/test_goto.vim15
-rw-r--r--src/nvim/testdir/test_syn_attr.vim2
-rw-r--r--src/nvim/testdir/test_usercommands.vim64
-rw-r--r--src/nvim/types.h1
-rw-r--r--src/nvim/version.c55
-rw-r--r--src/nvim/window.c42
59 files changed, 2661 insertions, 1393 deletions
diff --git a/src/clint.py b/src/clint.py
index efc5f18378..0c9f55c71e 100755
--- a/src/clint.py
+++ b/src/clint.py
@@ -2516,6 +2516,10 @@ def CheckSpacing(filename, clean_lines, linenum, nesting_state, error):
cast_line = re.sub(r'^# *define +\w+\([^)]*\)', '', line)
match = Search(r'(?<!\bkvec_t)'
+ r'(?<!\bkvec_withinit_t)'
+ r'(?<!\bklist_t)'
+ r'(?<!\bkliter_t)'
+ r'(?<!\bkhash_t)'
r'\((?:const )?(?:struct )?[a-zA-Z_]\w*(?: *\*(?:const)?)*\)'
r' +'
r'-?(?:\*+|&)?(?:\w+|\+\+|--|\()', cast_line)
@@ -2560,22 +2564,43 @@ def CheckBraces(filename, clean_lines, linenum, error):
line = clean_lines.elided[linenum] # get rid of comments and strings
- if not (filename.endswith('.c') or filename.endswith('.h')):
- if Match(r'\s*{\s*$', line):
- # We allow an open brace to start a line in the case where someone
- # is using braces in a block to explicitly create a new scope, which
- # is commonly used to control the lifetime of stack-allocated
- # variables. Braces are also used for brace initializers inside
- # function calls. We don't detect this perfectly: we just don't
- # complain if the last non-whitespace character on the previous
- # non-blank line is ',', ';', ':', '(', '{', or '}', or if the
- # previous line starts a preprocessor block.
- prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0]
- if (not Search(r'[,;:}{(]\s*$', prevline) and
- not Match(r'\s*#', prevline)):
- error(filename, linenum, 'whitespace/braces', 4,
- '{ should almost always be at the end'
- ' of the previous line')
+ if Match(r'\s+{\s*$', line):
+ # We allow an open brace to start a line in the case where someone
+ # is using braces in a block to explicitly create a new scope, which
+ # is commonly used to control the lifetime of stack-allocated
+ # variables. Braces are also used for brace initializers inside
+ # function calls. We don't detect this perfectly: we just don't
+ # complain if the last non-whitespace character on the previous
+ # non-blank line is ',', ';', ':', '(', '{', or '}', or if the
+ # previous line starts a preprocessor block.
+ prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0]
+ if (not Search(r'[,;:}{(]\s*$', prevline) and
+ not Match(r'\s*#', prevline)):
+ error(filename, linenum, 'whitespace/braces', 4,
+ '{ should almost always be at the end'
+ ' of the previous line')
+
+ # Brace must appear after function signature, but on the *next* line
+ if Match(r'^(?:\w+(?: ?\*+)? )+\w+\(', line):
+ pos = line.find('(')
+ (endline, end_linenum, endpos) = CloseExpression(
+ clean_lines, linenum, pos)
+ if endline.endswith('{'):
+ error(filename, end_linenum, 'readability/braces', 5,
+ 'Brace starting function body must be placed on its own line')
+ else:
+ func_start_linenum = end_linenum + 1
+ while not clean_lines.lines[func_start_linenum] == '{':
+ if not Match(r'^(?:\s*\b(?:FUNC_ATTR|REAL_FATTR)_\w+\b(?:\(\d+(, \d+)*\))?)+$',
+ clean_lines.lines[func_start_linenum]):
+ if clean_lines.lines[func_start_linenum].endswith('{'):
+ error(filename, func_start_linenum,
+ 'readability/braces', 5,
+ 'Brace starting function body must be placed '
+ 'after the function signature')
+ break
+ else:
+ func_start_linenum += 1
# An else clause should be on the same line as the preceding closing brace.
# If there is no preceding closing brace, there should be one.
@@ -3001,9 +3026,10 @@ def CheckIncludeLine(filename, clean_lines, linenum, include_state, error):
include = match.group(2)
is_system = (match.group(1) == '<')
if include in include_state:
- error(filename, linenum, 'build/include', 4,
- '"%s" already included at %s:%s' %
- (include, filename, include_state[include]))
+ if is_system or not include.endswith('.c.h'):
+ error(filename, linenum, 'build/include', 4,
+ '"%s" already included at %s:%s' %
+ (include, filename, include_state[include]))
else:
include_state[include] = linenum
@@ -3408,8 +3434,9 @@ def ProcessFile(filename, vlevel, extra_check_functions=[]):
# When reading from stdin, the extension is unknown, so no cpplint tests
# should rely on the extension.
if filename != '-' and file_extension not in _valid_extensions:
- sys.stderr.write('Ignoring %s; not a valid file name '
- '(%s)\n' % (filename, ', '.join(_valid_extensions)))
+ sys.stderr.write('Ignoring {}; only linting {} files\n'.format(
+ filename,
+ ', '.join('.{}'.format(ext) for ext in _valid_extensions)))
else:
ProcessFileData(filename, file_extension, lines, Error,
extra_check_functions)
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index b004cfc7a1..7daa4d7207 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -19,7 +19,6 @@
#include "nvim/option.h"
#include "nvim/option_defs.h"
#include "nvim/version.h"
-#include "nvim/eval/typval_encode.h"
#include "nvim/lib/kvec.h"
/// Helper structure for vim_to_object
@@ -327,21 +326,21 @@ void set_option_to(void *to, int type, String name, Object value, Error *err)
#define TYPVAL_ENCODE_ALLOW_SPECIALS false
-#define TYPVAL_ENCODE_CONV_NIL() \
+#define TYPVAL_ENCODE_CONV_NIL(tv) \
kv_push(edata->stack, NIL)
-#define TYPVAL_ENCODE_CONV_BOOL(num) \
+#define TYPVAL_ENCODE_CONV_BOOL(tv, num) \
kv_push(edata->stack, BOOLEAN_OBJ((Boolean)(num)))
-#define TYPVAL_ENCODE_CONV_NUMBER(num) \
+#define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \
kv_push(edata->stack, INTEGER_OBJ((Integer)(num)))
#define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER TYPVAL_ENCODE_CONV_NUMBER
-#define TYPVAL_ENCODE_CONV_FLOAT(flt) \
+#define TYPVAL_ENCODE_CONV_FLOAT(tv, flt) \
kv_push(edata->stack, FLOATING_OBJ((Float)(flt)))
-#define TYPVAL_ENCODE_CONV_STRING(str, len) \
+#define TYPVAL_ENCODE_CONV_STRING(tv, str, len) \
do { \
const size_t len_ = (size_t)(len); \
const char *const str_ = (const char *)(str); \
@@ -354,19 +353,23 @@ void set_option_to(void *to, int type, String name, Object value, Error *err)
#define TYPVAL_ENCODE_CONV_STR_STRING TYPVAL_ENCODE_CONV_STRING
-#define TYPVAL_ENCODE_CONV_EXT_STRING(str, len, type) \
- TYPVAL_ENCODE_CONV_NIL()
+#define TYPVAL_ENCODE_CONV_EXT_STRING(tv, str, len, type) \
+ TYPVAL_ENCODE_CONV_NIL(tv)
-#define TYPVAL_ENCODE_CONV_FUNC(fun) \
- TYPVAL_ENCODE_CONV_NIL()
+#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \
+ do { \
+ TYPVAL_ENCODE_CONV_NIL(tv); \
+ goto typval_encode_stop_converting_one_item; \
+ } while (0)
-#define TYPVAL_ENCODE_CONV_PARTIAL(partial) \
- TYPVAL_ENCODE_CONV_NIL()
+#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(tv, len)
+#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, len)
+#define TYPVAL_ENCODE_CONV_FUNC_END(tv)
-#define TYPVAL_ENCODE_CONV_EMPTY_LIST() \
+#define TYPVAL_ENCODE_CONV_EMPTY_LIST(tv) \
kv_push(edata->stack, ARRAY_OBJ(((Array) { .capacity = 0, .size = 0 })))
-#define TYPVAL_ENCODE_CONV_EMPTY_DICT() \
+#define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \
kv_push(edata->stack, \
DICTIONARY_OBJ(((Dictionary) { .capacity = 0, .size = 0 })))
@@ -381,9 +384,11 @@ static inline void typval_encode_list_start(EncodedData *const edata,
})));
}
-#define TYPVAL_ENCODE_CONV_LIST_START(len) \
+#define TYPVAL_ENCODE_CONV_LIST_START(tv, len) \
typval_encode_list_start(edata, (size_t)(len))
+#define TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, mpsv)
+
static inline void typval_encode_between_list_items(EncodedData *const edata)
FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL
{
@@ -394,7 +399,7 @@ static inline void typval_encode_between_list_items(EncodedData *const edata)
list->data.array.items[list->data.array.size++] = item;
}
-#define TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS() \
+#define TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(tv) \
typval_encode_between_list_items(edata)
static inline void typval_encode_list_end(EncodedData *const edata)
@@ -407,7 +412,7 @@ static inline void typval_encode_list_end(EncodedData *const edata)
#endif
}
-#define TYPVAL_ENCODE_CONV_LIST_END() \
+#define TYPVAL_ENCODE_CONV_LIST_END(tv) \
typval_encode_list_end(edata)
static inline void typval_encode_dict_start(EncodedData *const edata,
@@ -421,10 +426,12 @@ static inline void typval_encode_dict_start(EncodedData *const edata,
})));
}
-#define TYPVAL_ENCODE_CONV_DICT_START(len) \
+#define TYPVAL_ENCODE_CONV_DICT_START(tv, dict, len) \
typval_encode_dict_start(edata, (size_t)(len))
-#define TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK(label, kv_pair)
+#define TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(tv, dict, mpsv)
+
+#define TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK(label, kv_pair)
static inline void typval_encode_after_key(EncodedData *const edata)
FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL
@@ -443,7 +450,7 @@ static inline void typval_encode_after_key(EncodedData *const edata)
}
}
-#define TYPVAL_ENCODE_CONV_DICT_AFTER_KEY() \
+#define TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(tv, dict) \
typval_encode_after_key(edata)
static inline void typval_encode_between_dict_items(EncodedData *const edata)
@@ -456,7 +463,7 @@ static inline void typval_encode_between_dict_items(EncodedData *const edata)
dict->data.dictionary.items[dict->data.dictionary.size++].value = val;
}
-#define TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS() \
+#define TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(tv, dict) \
typval_encode_between_dict_items(edata)
static inline void typval_encode_dict_end(EncodedData *const edata)
@@ -469,34 +476,44 @@ static inline void typval_encode_dict_end(EncodedData *const edata)
#endif
}
-#define TYPVAL_ENCODE_CONV_DICT_END() \
+#define TYPVAL_ENCODE_CONV_DICT_END(tv, dict) \
typval_encode_dict_end(edata)
#define TYPVAL_ENCODE_CONV_RECURSE(val, conv_type) \
TYPVAL_ENCODE_CONV_NIL()
-// object_convert_one_value()
-// encode_vim_to_object()
-TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(static, object, EncodedData *const, edata)
+#define TYPVAL_ENCODE_SCOPE static
+#define TYPVAL_ENCODE_NAME object
+#define TYPVAL_ENCODE_FIRST_ARG_TYPE EncodedData *const
+#define TYPVAL_ENCODE_FIRST_ARG_NAME edata
+#include "nvim/eval/typval_encode.c.h"
+#undef TYPVAL_ENCODE_SCOPE
+#undef TYPVAL_ENCODE_NAME
+#undef TYPVAL_ENCODE_FIRST_ARG_TYPE
+#undef TYPVAL_ENCODE_FIRST_ARG_NAME
#undef TYPVAL_ENCODE_CONV_STRING
#undef TYPVAL_ENCODE_CONV_STR_STRING
#undef TYPVAL_ENCODE_CONV_EXT_STRING
#undef TYPVAL_ENCODE_CONV_NUMBER
#undef TYPVAL_ENCODE_CONV_FLOAT
-#undef TYPVAL_ENCODE_CONV_FUNC
-#undef TYPVAL_ENCODE_CONV_PARTIAL
+#undef TYPVAL_ENCODE_CONV_FUNC_START
+#undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS
+#undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF
+#undef TYPVAL_ENCODE_CONV_FUNC_END
#undef TYPVAL_ENCODE_CONV_EMPTY_LIST
#undef TYPVAL_ENCODE_CONV_LIST_START
+#undef TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START
#undef TYPVAL_ENCODE_CONV_EMPTY_DICT
#undef TYPVAL_ENCODE_CONV_NIL
#undef TYPVAL_ENCODE_CONV_BOOL
#undef TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER
#undef TYPVAL_ENCODE_CONV_DICT_START
+#undef TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START
#undef TYPVAL_ENCODE_CONV_DICT_END
#undef TYPVAL_ENCODE_CONV_DICT_AFTER_KEY
#undef TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS
-#undef TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK
+#undef TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK
#undef TYPVAL_ENCODE_CONV_LIST_END
#undef TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS
#undef TYPVAL_ENCODE_CONV_RECURSE
@@ -510,7 +527,10 @@ TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(static, object, EncodedData *const, edata)
Object vim_to_object(typval_T *obj)
{
EncodedData edata = { .stack = KV_INITIAL_VALUE };
- encode_vim_to_object(&edata, obj, "vim_to_object argument");
+ const int evo_ret = encode_vim_to_object(&edata, obj,
+ "vim_to_object argument");
+ (void)evo_ret;
+ assert(evo_ret == OK);
Object ret = kv_A(edata.stack, 0);
assert(kv_size(edata.stack) == 1);
kv_destroy(edata.stack);
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index b17b59f7a6..f37e996f06 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -315,7 +315,7 @@ void nvim_set_current_dir(String dir, Error *err)
try_start();
- if (vim_chdir((char_u *)string)) {
+ if (vim_chdir((char_u *)string, kCdScopeGlobal)) {
if (!try_end(err)) {
api_set_error(err, Exception, _("Failed to change directory"));
}
diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua
index 8d891effae..68a47c244f 100644
--- a/src/nvim/auevents.lua
+++ b/src/nvim/auevents.lua
@@ -28,6 +28,7 @@ return {
'CursorHoldI', -- idem, in Insert mode
'CursorMoved', -- cursor was moved
'CursorMovedI', -- cursor was moved in Insert mode
+ 'DirChanged', -- directory changed
'EncodingChanged', -- after changing the 'encoding' option
'FileAppendCmd', -- append to a file using command
'FileAppendPost', -- after appending to a file
@@ -89,6 +90,7 @@ return {
'VimLeave', -- before exiting Vim
'VimLeavePre', -- before exiting Vim and writing ShaDa file
'VimResized', -- after Vim window was resized
+ 'WinNew', -- when entering a new window
'WinEnter', -- after entering a window
'WinLeave', -- before leaving a window
},
@@ -101,6 +103,7 @@ return {
-- List of neovim-specific events or aliases for the purpose of generating
-- syntax file
neovim_specific = {
+ DirChanged=true,
TabClosed=true,
TabNew=true,
TabNewEntered=true,
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index ad6fdc32c0..26dbbe8bb5 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -189,14 +189,18 @@ open_buffer (
curwin->w_cursor.lnum = 1;
curwin->w_cursor.col = 0;
- /* Set or reset 'modified' before executing autocommands, so that
- * it can be changed there. */
- if (!readonlymode && !bufempty())
+ // Set or reset 'modified' before executing autocommands, so that
+ // it can be changed there.
+ if (!readonlymode && !bufempty()) {
changed();
- else if (retval != FAIL)
- unchanged(curbuf, FALSE);
- apply_autocmds_retval(EVENT_STDINREADPOST, NULL, NULL, FALSE,
- curbuf, &retval);
+ } else if (retval == OK) {
+ unchanged(curbuf, false);
+ }
+
+ if (retval == OK) {
+ apply_autocmds_retval(EVENT_STDINREADPOST, NULL, NULL, false,
+ curbuf, &retval);
+ }
}
}
@@ -206,22 +210,21 @@ open_buffer (
parse_cino(curbuf);
}
- /*
- * Set/reset the Changed flag first, autocmds may change the buffer.
- * Apply the automatic commands, before processing the modelines.
- * So the modelines have priority over auto commands.
- */
- /* When reading stdin, the buffer contents always needs writing, so set
- * the changed flag. Unless in readonly mode: "ls | nvim -R -".
- * When interrupted and 'cpoptions' contains 'i' set changed flag. */
+ // Set/reset the Changed flag first, autocmds may change the buffer.
+ // Apply the automatic commands, before processing the modelines.
+ // So the modelines have priority over auto commands.
+
+ // When reading stdin, the buffer contents always needs writing, so set
+ // the changed flag. Unless in readonly mode: "ls | nvim -R -".
+ // When interrupted and 'cpoptions' contains 'i' set changed flag.
if ((got_int && vim_strchr(p_cpo, CPO_INTMOD) != NULL)
- || modified_was_set /* ":set modified" used in autocmd */
- || (aborting() && vim_strchr(p_cpo, CPO_INTMOD) != NULL)
- )
+ || modified_was_set // ":set modified" used in autocmd
+ || (aborting() && vim_strchr(p_cpo, CPO_INTMOD) != NULL)) {
changed();
- else if (retval != FAIL && !read_stdin)
- unchanged(curbuf, FALSE);
- save_file_ff(curbuf); /* keep this fileformat */
+ } else if (retval == OK && !read_stdin) {
+ unchanged(curbuf, false);
+ }
+ save_file_ff(curbuf); // keep this fileformat
/* require "!" to overwrite the file, because it wasn't read completely */
if (aborting())
@@ -427,9 +430,6 @@ void close_buffer(win_T *win, buf_T *buf, int action, int abort_if_last)
buf->b_nwindows = nwindows;
buf_freeall(buf, (del_buf ? BFA_DEL : 0) + (wipe_buf ? BFA_WIPE : 0));
- if (win_valid_any_tab(win) && win->w_buffer == buf) {
- win->w_buffer = NULL; // make sure we don't use the buffer now
- }
/* Autocommands may have deleted the buffer. */
if (!buf_valid(buf))
@@ -437,11 +437,6 @@ void close_buffer(win_T *win, buf_T *buf, int action, int abort_if_last)
if (aborting()) /* autocmds may abort script processing */
return;
- /* Autocommands may have opened or closed windows for this buffer.
- * Decrement the count for the close we do here. */
- if (buf->b_nwindows > 0)
- --buf->b_nwindows;
-
/*
* It's possible that autocommands change curbuf to the one being deleted.
* This might cause the previous curbuf to be deleted unexpectedly. But
@@ -452,6 +447,16 @@ void close_buffer(win_T *win, buf_T *buf, int action, int abort_if_last)
if (buf == curbuf && !is_curbuf)
return;
+ if (win_valid_any_tab(win) && win->w_buffer == buf) {
+ win->w_buffer = NULL; // make sure we don't use the buffer now
+ }
+
+ // Autocommands may have opened or closed windows for this buffer.
+ // Decrement the count for the close we do here.
+ if (buf->b_nwindows > 0) {
+ buf->b_nwindows--;
+ }
+
/* Change directories when the 'acd' option is set. */
do_autochdir();
@@ -3462,7 +3467,7 @@ int build_stl_str_hl(
case STL_KEYMAP:
fillable = false;
- if (get_keymap_str(wp, tmp, TMPLEN))
+ if (get_keymap_str(wp, (char_u *)"<%s>", tmp, TMPLEN))
str = tmp;
break;
case STL_PAGENUM:
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index ea42a58cdd..6b14d21da7 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -5162,12 +5162,18 @@ dict_equal (
dictitem_T *item2;
int todo;
- if (d1 == NULL || d2 == NULL)
- return FALSE;
- if (d1 == d2)
- return TRUE;
- if (dict_len(d1) != dict_len(d2))
- return FALSE;
+ if (d1 == NULL && d2 == NULL) {
+ return true;
+ }
+ if (d1 == NULL || d2 == NULL) {
+ return false;
+ }
+ if (d1 == d2) {
+ return true;
+ }
+ if (dict_len(d1) != dict_len(d2)) {
+ return false;
+ }
todo = (int)d1->dv_hashtab.ht_used;
for (hi = d1->dv_hashtab.ht_array; todo > 0; ++hi) {
@@ -6404,8 +6410,8 @@ static void dict_free_contents(dict_T *d) {
while (!QUEUE_EMPTY(&d->watchers)) {
QUEUE *w = QUEUE_HEAD(&d->watchers);
DictWatcher *watcher = dictwatcher_node_data(w);
- dictwatcher_free(watcher);
QUEUE_REMOVE(w);
+ dictwatcher_free(watcher);
}
hash_clear(&d->dv_hashtab);
@@ -6439,7 +6445,7 @@ void dict_free(dict_T *d) {
*/
dictitem_T *dictitem_alloc(char_u *key) FUNC_ATTR_NONNULL_RET
{
- dictitem_T *di = xmalloc(sizeof(dictitem_T) + STRLEN(key));
+ dictitem_T *di = xmalloc(offsetof(dictitem_T, di_key) + STRLEN(key) + 1);
#ifndef __clang_analyzer__
STRCPY(di->di_key, key);
#endif
@@ -6669,9 +6675,12 @@ dictitem_T *dict_find(dict_T *d, char_u *key, int len)
char_u *tofree = NULL;
hashitem_T *hi;
- if (len < 0)
+ if (d == NULL) {
+ return NULL;
+ }
+ if (len < 0) {
akey = key;
- else if (len >= AKEYLEN) {
+ } else if (len >= AKEYLEN) {
tofree = akey = vim_strnsave(key, len);
} else {
/* Avoid a malloc/free by using buf[]. */
@@ -6717,6 +6726,7 @@ static bool get_dict_callback(dict_T *d, char *key, Callback *result)
/// Get a string item from a dictionary.
///
/// @param save whether memory should be allocated for the return value
+/// when false a shared buffer is used, can only be used once!
///
/// @return the entry or NULL if the entry doesn't exist.
char_u *get_dict_string(dict_T *d, char *key, bool save)
@@ -8819,34 +8829,73 @@ static void f_executable(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|| (gettail_dir(name) != name && os_can_exe(name, NULL, false));
}
+static char_u * get_list_line(int c, void *cookie, int indent)
+{
+ listitem_T **p = (listitem_T **)cookie;
+ listitem_T *item = *p;
+ char_u buf[NUMBUFLEN];
+ char_u *s;
+
+ if (item == NULL) {
+ return NULL;
+ }
+ s = get_tv_string_buf_chk(&item->li_tv, buf);
+ *p = item->li_next;
+ return s == NULL ? NULL : vim_strsave(s);
+}
+
// "execute(command)" function
static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
int save_msg_silent = msg_silent;
+ int save_emsg_silent = emsg_silent;
+ bool save_emsg_noredir = emsg_noredir;
garray_T *save_capture_ga = capture_ga;
if (check_secure()) {
return;
}
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ char_u buf[NUMBUFLEN];
+ char_u *s = get_tv_string_buf_chk(&argvars[1], buf);
+
+ if (s == NULL) {
+ return;
+ }
+ if (STRNCMP(s, "silent", 6) == 0) {
+ msg_silent++;
+ }
+ if (STRCMP(s, "silent!") == 0) {
+ emsg_silent = true;
+ emsg_noredir = true;
+ }
+ } else {
+ msg_silent++;
+ }
+
garray_T capture_local;
+ ga_init(&capture_local, (int)sizeof(char), 80);
capture_ga = &capture_local;
- ga_init(capture_ga, (int)sizeof(char), 80);
- msg_silent++;
if (argvars[0].v_type != VAR_LIST) {
do_cmdline_cmd((char *)get_tv_string(&argvars[0]));
} else if (argvars[0].vval.v_list != NULL) {
- for (listitem_T *li = argvars[0].vval.v_list->lv_first;
- li != NULL; li = li->li_next) {
- do_cmdline_cmd((char *)get_tv_string(&li->li_tv));
- }
+ list_T *list = argvars[0].vval.v_list;
+ list->lv_refcount++;
+ listitem_T *item = list->lv_first;
+ do_cmdline(NULL, get_list_line, (void *)&item,
+ DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT|DOCMD_KEYTYPED);
+ list->lv_refcount--;
}
msg_silent = save_msg_silent;
+ emsg_silent = save_emsg_silent;
+ emsg_noredir = save_emsg_noredir;
ga_append(capture_ga, NUL);
rettv->v_type = VAR_STRING;
- rettv->vval.v_string = capture_ga->ga_data;
+ rettv->vval.v_string = vim_strsave(capture_ga->ga_data);
+ ga_clear(capture_ga);
capture_ga = save_capture_ga;
}
@@ -9242,7 +9291,7 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map)
dict_T *d = NULL;
typval_T save_val;
typval_T save_key;
- int rem;
+ int rem = false;
int todo;
char_u *ermsg = (char_u *)(map ? "map()" : "filter()");
char_u *arg_errmsg = (char_u *)(map ? N_("map() argument")
@@ -9525,24 +9574,29 @@ static void f_foldlevel(typval_T *argvars, typval_T *rettv, FunPtr fptr)
*/
static void f_foldtext(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- linenr_T lnum;
+ linenr_T foldstart;
+ linenr_T foldend;
+ char_u *dashes;
+ linenr_T lnum;
char_u *s;
char_u *r;
- int len;
+ int len;
char *txt;
+ long count;
rettv->v_type = VAR_STRING;
rettv->vval.v_string = NULL;
- if ((linenr_T)vimvars[VV_FOLDSTART].vv_nr > 0
- && (linenr_T)vimvars[VV_FOLDEND].vv_nr
- <= curbuf->b_ml.ml_line_count
- && vimvars[VV_FOLDDASHES].vv_str != NULL) {
+
+ foldstart = (linenr_T)get_vim_var_nr(VV_FOLDSTART);
+ foldend = (linenr_T)get_vim_var_nr(VV_FOLDEND);
+ dashes = get_vim_var_str(VV_FOLDDASHES);
+ if (foldstart > 0 && foldend <= curbuf->b_ml.ml_line_count
+ && dashes != NULL) {
/* Find first non-empty line in the fold. */
- lnum = (linenr_T)vimvars[VV_FOLDSTART].vv_nr;
- while (lnum < (linenr_T)vimvars[VV_FOLDEND].vv_nr) {
- if (!linewhite(lnum))
+ for (lnum = foldstart; lnum < foldend; ++lnum) {
+ if (!linewhite(lnum)) {
break;
- ++lnum;
+ }
}
/* Find interesting text in this line. */
@@ -9550,21 +9604,19 @@ static void f_foldtext(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/* skip C comment-start */
if (s[0] == '/' && (s[1] == '*' || s[1] == '/')) {
s = skipwhite(s + 2);
- if (*skipwhite(s) == NUL
- && lnum + 1 < (linenr_T)vimvars[VV_FOLDEND].vv_nr) {
+ if (*skipwhite(s) == NUL && lnum + 1 < foldend) {
s = skipwhite(ml_get(lnum + 1));
if (*s == '*')
s = skipwhite(s + 1);
}
}
+ count = (long)(foldend - foldstart + 1);
txt = _("+-%s%3ld lines: ");
r = xmalloc(STRLEN(txt)
- + STRLEN(vimvars[VV_FOLDDASHES].vv_str) // for %s
- + 20 // for %3ld
- + STRLEN(s)); // concatenated
- sprintf((char *)r, txt, vimvars[VV_FOLDDASHES].vv_str,
- (long)((linenr_T)vimvars[VV_FOLDEND].vv_nr
- - (linenr_T)vimvars[VV_FOLDSTART].vv_nr + 1));
+ + STRLEN(dashes) // for %s
+ + 20 // for %3ld
+ + STRLEN(s)); // concatenated
+ sprintf((char *)r, txt, dashes, count);
len = (int)STRLEN(r);
STRCAT(r, s);
/* remove 'foldmarker' and 'commentstring' */
@@ -10067,7 +10119,7 @@ static void f_getbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
} else if (STRCMP(varname, "changedtick") == 0) {
rettv->v_type = VAR_NUMBER;
rettv->vval.v_number = curbuf->b_changedtick;
- done = TRUE;
+ done = true;
} else {
/* Look up the variable. */
/* Let getbufvar({nr}, "") return the "b:" dictionary. */
@@ -15064,7 +15116,6 @@ static void f_serverstop(typval_T *argvars, typval_T *rettv, FunPtr fptr)
static void f_setbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
buf_T *buf;
- aco_save_T aco;
char_u *varname, *bufvarname;
typval_T *varp;
char_u nbuf[NUMBUFLEN];
@@ -15077,29 +15128,34 @@ static void f_setbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
varp = &argvars[2];
if (buf != NULL && varname != NULL && varp != NULL) {
- /* set curbuf to be our buf, temporarily */
- aucmd_prepbuf(&aco, buf);
-
if (*varname == '&') {
long numval;
char_u *strval;
- int error = FALSE;
+ int error = false;
+ aco_save_T aco;
+
+ // set curbuf to be our buf, temporarily
+ aucmd_prepbuf(&aco, buf);
++varname;
numval = get_tv_number_chk(varp, &error);
strval = get_tv_string_buf_chk(varp, nbuf);
if (!error && strval != NULL)
set_option_value(varname, numval, strval, OPT_LOCAL);
+
+ // reset notion of buffer
+ aucmd_restbuf(&aco);
} else {
+ buf_T *save_curbuf = curbuf;
+
bufvarname = xmalloc(STRLEN(varname) + 3);
+ curbuf = buf;
STRCPY(bufvarname, "b:");
STRCPY(bufvarname + 2, varname);
set_var(bufvarname, varp, TRUE);
xfree(bufvarname);
+ curbuf = save_curbuf;
}
-
- /* reset notion of buffer */
- aucmd_restbuf(&aco);
}
}
@@ -15393,12 +15449,11 @@ static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
- char_u *group = get_dict_string(d, "group", false);
+ char_u *group = get_dict_string(d, "group", true);
int priority = get_dict_number(d, "priority");
int id = get_dict_number(d, "id");
char_u *conceal = dict_find(d, (char_u *)"conceal", -1) != NULL
- ? get_dict_string(d, "conceal",
- false)
+ ? get_dict_string(d, "conceal", true)
: NULL;
if (i == 0) {
match_add(curwin, group,
@@ -15409,6 +15464,8 @@ static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)
list_unref(s);
s = NULL;
}
+ xfree(group);
+ xfree(conceal);
li = li->li_next;
}
rettv->vval.v_number = 0;
@@ -15516,7 +15573,10 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
if (argvars[1].v_type == VAR_LIST) {
- int len = argvars[1].vval.v_list->lv_len;
+ list_T *ll = argvars[1].vval.v_list;
+ // If the list is NULL handle like an empty list.
+ int len = ll == NULL ? 0 : ll->lv_len;
+
// First half: use for pointers to result lines; second half: use for
// pointers to allocated copies.
char_u **lstval = xmalloc(sizeof(char_u *) * ((len + 1) * 2));
@@ -15525,7 +15585,7 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr)
char_u **curallocval = allocval;
char_u buf[NUMBUFLEN];
- for (listitem_T *li = argvars[1].vval.v_list->lv_first;
+ for (listitem_T *li = ll == NULL ? NULL : ll->lv_first;
li != NULL;
li = li->li_next) {
char_u *strval = get_tv_string_buf_chk(&li->li_tv, buf);
@@ -16939,8 +16999,12 @@ static void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv,
}
// get shell command to execute
- char **argv = tv_to_argv(&argvars[0], NULL, NULL);
+ bool executable = true;
+ char **argv = tv_to_argv(&argvars[0], NULL, &executable);
if (!argv) {
+ if (!executable) {
+ set_vim_var_nr(VV_SHELL_ERROR, (long)-1);
+ }
xfree(input);
return; // Already did emsg.
}
@@ -17249,14 +17313,6 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return;
}
-/*
- * "test(list)" function: Just checking the walls...
- */
-static void f_test(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- /* Used for unit testing. Change the code below to your liking. */
-}
-
static bool callback_from_typval(Callback *callback, typval_T *arg)
{
if (arg->v_type == VAR_PARTIAL && arg->vval.v_partial != NULL) {
@@ -19034,124 +19090,205 @@ void free_tv(typval_T *varp)
#define TYPVAL_ENCODE_ALLOW_SPECIALS false
-#define TYPVAL_ENCODE_CONV_NIL() \
+#define TYPVAL_ENCODE_CONV_NIL(tv) \
do { \
tv->vval.v_special = kSpecialVarFalse; \
tv->v_lock = VAR_UNLOCKED; \
} while (0)
-#define TYPVAL_ENCODE_CONV_BOOL(ignored) \
- TYPVAL_ENCODE_CONV_NIL()
+#define TYPVAL_ENCODE_CONV_BOOL(tv, num) \
+ TYPVAL_ENCODE_CONV_NIL(tv)
-#define TYPVAL_ENCODE_CONV_NUMBER(ignored) \
+#define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \
do { \
- (void)ignored; \
+ (void)num; \
tv->vval.v_number = 0; \
tv->v_lock = VAR_UNLOCKED; \
} while (0)
-#define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(ignored) \
- assert(false)
+#define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(tv, num)
-#define TYPVAL_ENCODE_CONV_FLOAT(ignored) \
+#define TYPVAL_ENCODE_CONV_FLOAT(tv, flt) \
do { \
tv->vval.v_float = 0; \
tv->v_lock = VAR_UNLOCKED; \
} while (0)
-#define TYPVAL_ENCODE_CONV_STRING(str, ignored) \
+#define TYPVAL_ENCODE_CONV_STRING(tv, buf, len) \
do { \
- xfree(str); \
+ xfree(buf); \
tv->vval.v_string = NULL; \
tv->v_lock = VAR_UNLOCKED; \
} while (0)
-#define TYPVAL_ENCODE_CONV_STR_STRING(ignored1, ignored2)
+#define TYPVAL_ENCODE_CONV_STR_STRING(tv, buf, len)
-#define TYPVAL_ENCODE_CONV_EXT_STRING(ignored1, ignored2, ignored3)
+#define TYPVAL_ENCODE_CONV_EXT_STRING(tv, buf, len, type)
-#define TYPVAL_ENCODE_CONV_FUNC(fun) \
+static inline int _nothing_conv_func_start(typval_T *const tv,
+ char_u *const fun)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ARG(1)
+{
+ tv->v_lock = VAR_UNLOCKED;
+ if (tv->v_type == VAR_PARTIAL) {
+ partial_T *const pt_ = tv->vval.v_partial;
+ if (pt_ != NULL && pt_->pt_refcount > 1) {
+ pt_->pt_refcount--;
+ tv->vval.v_partial = NULL;
+ return OK;
+ }
+ } else {
+ func_unref(fun);
+ if (fun != empty_string) {
+ xfree(fun);
+ }
+ tv->vval.v_string = NULL;
+ }
+ return NOTDONE;
+}
+#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \
do { \
- func_unref(fun); \
- if (fun != empty_string) { \
- xfree(fun); \
+ if (_nothing_conv_func_start(tv, fun) != NOTDONE) { \
+ return OK; \
} \
- tv->vval.v_string = NULL; \
- tv->v_lock = VAR_UNLOCKED; \
} while (0)
-#define TYPVAL_ENCODE_CONV_PARTIAL(pt) \
- do { \
- partial_unref(pt); \
- pt = NULL; \
- tv->v_lock = VAR_UNLOCKED; \
- } while (0)
+#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(tv, len)
+#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, len)
+
+static inline void _nothing_conv_func_end(typval_T *const tv, const int copyID)
+ FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL
+{
+ if (tv->v_type == VAR_PARTIAL) {
+ partial_T *const pt = tv->vval.v_partial;
+ if (pt == NULL) {
+ return;
+ }
+ // Dictionary should already be freed by the time.
+ // If it was not freed then it is a part of the reference cycle.
+ assert(pt->pt_dict == NULL || pt->pt_dict->dv_copyID == copyID);
+ pt->pt_dict = NULL;
+ // As well as all arguments.
+ pt->pt_argc = 0;
+ assert(pt->pt_refcount <= 1);
+ partial_unref(pt);
+ tv->vval.v_partial = NULL;
+ assert(tv->v_lock == VAR_UNLOCKED);
+ }
+}
+#define TYPVAL_ENCODE_CONV_FUNC_END(tv) _nothing_conv_func_end(tv, copyID)
-#define TYPVAL_ENCODE_CONV_EMPTY_LIST() \
+#define TYPVAL_ENCODE_CONV_EMPTY_LIST(tv) \
do { \
list_unref(tv->vval.v_list); \
tv->vval.v_list = NULL; \
tv->v_lock = VAR_UNLOCKED; \
} while (0)
-#define TYPVAL_ENCODE_CONV_EMPTY_DICT() \
- do { \
- dict_unref(tv->vval.v_dict); \
- tv->vval.v_dict = NULL; \
- tv->v_lock = VAR_UNLOCKED; \
- } while (0)
-
-#define TYPVAL_ENCODE_CONV_LIST_START(ignored) \
+#define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \
do { \
- if (tv->vval.v_list->lv_refcount > 1) { \
- tv->vval.v_list->lv_refcount--; \
- tv->vval.v_list = NULL; \
- tv->v_lock = VAR_UNLOCKED; \
- return OK; \
+ assert((void *)&dict != (void *)&TYPVAL_ENCODE_NODICT_VAR); \
+ dict_unref((dict_T *)dict); \
+ *((dict_T **)&dict) = NULL; \
+ if (tv != NULL) { \
+ ((typval_T *)tv)->v_lock = VAR_UNLOCKED; \
} \
} while (0)
-#define TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS()
-
-#define TYPVAL_ENCODE_CONV_LIST_END() \
- do { \
- typval_T *const cur_tv = cur_mpsv->tv; \
- assert(cur_tv->v_type == VAR_LIST); \
- list_unref(cur_tv->vval.v_list); \
- cur_tv->vval.v_list = NULL; \
- cur_tv->v_lock = VAR_UNLOCKED; \
- } while (0)
+static inline int _nothing_conv_real_list_after_start(
+ typval_T *const tv, MPConvStackVal *const mpsv)
+ FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ assert(tv != NULL);
+ tv->v_lock = VAR_UNLOCKED;
+ if (tv->vval.v_list->lv_refcount > 1) {
+ tv->vval.v_list->lv_refcount--;
+ tv->vval.v_list = NULL;
+ mpsv->data.l.li = NULL;
+ return OK;
+ }
+ return NOTDONE;
+}
+#define TYPVAL_ENCODE_CONV_LIST_START(tv, len)
-#define TYPVAL_ENCODE_CONV_DICT_START(ignored) \
+#define TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, mpsv) \
do { \
- if (tv->vval.v_dict->dv_refcount > 1) { \
- tv->vval.v_dict->dv_refcount--; \
- tv->vval.v_dict = NULL; \
- tv->v_lock = VAR_UNLOCKED; \
- return OK; \
+ if (_nothing_conv_real_list_after_start(tv, &mpsv) != NOTDONE) { \
+ goto typval_encode_stop_converting_one_item; \
} \
} while (0)
-#define TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK(ignored1, ignored2)
+#define TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(tv)
-#define TYPVAL_ENCODE_CONV_DICT_AFTER_KEY()
+static inline void _nothing_conv_list_end(typval_T *const tv)
+ FUNC_ATTR_ALWAYS_INLINE
+{
+ if (tv == NULL) {
+ return;
+ }
+ assert(tv->v_type == VAR_LIST);
+ list_T *const list = tv->vval.v_list;
+ list_unref(list);
+ tv->vval.v_list = NULL;
+}
+#define TYPVAL_ENCODE_CONV_LIST_END(tv) _nothing_conv_list_end(tv)
-#define TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS()
+static inline int _nothing_conv_real_dict_after_start(
+ typval_T *const tv, dict_T **const dictp, const void *const nodictvar,
+ MPConvStackVal *const mpsv)
+ FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (tv != NULL) {
+ tv->v_lock = VAR_UNLOCKED;
+ }
+ if ((const void *)dictp != nodictvar && (*dictp)->dv_refcount > 1) {
+ (*dictp)->dv_refcount--;
+ *dictp = NULL;
+ mpsv->data.d.todo = 0;
+ return OK;
+ }
+ return NOTDONE;
+}
+#define TYPVAL_ENCODE_CONV_DICT_START(tv, dict, len)
-#define TYPVAL_ENCODE_CONV_DICT_END() \
+#define TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(tv, dict, mpsv) \
do { \
- typval_T *const cur_tv = cur_mpsv->tv; \
- assert(cur_tv->v_type == VAR_DICT); \
- dict_unref(cur_tv->vval.v_dict); \
- cur_tv->vval.v_dict = NULL; \
- cur_tv->v_lock = VAR_UNLOCKED; \
+ if (_nothing_conv_real_dict_after_start( \
+ tv, (dict_T **)&dict, (void *)&TYPVAL_ENCODE_NODICT_VAR, \
+ &mpsv) != NOTDONE) { \
+ goto typval_encode_stop_converting_one_item; \
+ } \
} while (0)
-#define TYPVAL_ENCODE_CONV_RECURSE(ignored1, ignored2)
+#define TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK(tv, dict)
+#define TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(tv, dict)
+#define TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(tv, dict)
+
+static inline void _nothing_conv_dict_end(typval_T *const tv,
+ dict_T **const dictp,
+ const void *const nodictvar)
+ FUNC_ATTR_ALWAYS_INLINE
+{
+ if ((const void *)dictp != nodictvar) {
+ dict_unref(*dictp);
+ *dictp = NULL;
+ }
+}
+#define TYPVAL_ENCODE_CONV_DICT_END(tv, dict) \
+ _nothing_conv_dict_end(tv, (dict_T **)&dict, \
+ (void *)&TYPVAL_ENCODE_NODICT_VAR)
+
+#define TYPVAL_ENCODE_CONV_RECURSE(val, conv_type)
-// nothing_convert_one_value()
-// encode_vim_to_nothing()
-TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(static, nothing, void *, ignored)
+#define TYPVAL_ENCODE_SCOPE static
+#define TYPVAL_ENCODE_NAME nothing
+#define TYPVAL_ENCODE_FIRST_ARG_TYPE const void *const
+#define TYPVAL_ENCODE_FIRST_ARG_NAME ignored
+#include "nvim/eval/typval_encode.c.h"
+#undef TYPVAL_ENCODE_SCOPE
+#undef TYPVAL_ENCODE_NAME
+#undef TYPVAL_ENCODE_FIRST_ARG_TYPE
+#undef TYPVAL_ENCODE_FIRST_ARG_NAME
#undef TYPVAL_ENCODE_ALLOW_SPECIALS
#undef TYPVAL_ENCODE_CONV_NIL
@@ -19162,15 +19299,19 @@ TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(static, nothing, void *, ignored)
#undef TYPVAL_ENCODE_CONV_STRING
#undef TYPVAL_ENCODE_CONV_STR_STRING
#undef TYPVAL_ENCODE_CONV_EXT_STRING
-#undef TYPVAL_ENCODE_CONV_FUNC
-#undef TYPVAL_ENCODE_CONV_PARTIAL
+#undef TYPVAL_ENCODE_CONV_FUNC_START
+#undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS
+#undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF
+#undef TYPVAL_ENCODE_CONV_FUNC_END
#undef TYPVAL_ENCODE_CONV_EMPTY_LIST
#undef TYPVAL_ENCODE_CONV_EMPTY_DICT
#undef TYPVAL_ENCODE_CONV_LIST_START
+#undef TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START
#undef TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS
#undef TYPVAL_ENCODE_CONV_LIST_END
#undef TYPVAL_ENCODE_CONV_DICT_START
-#undef TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK
+#undef TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START
+#undef TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK
#undef TYPVAL_ENCODE_CONV_DICT_AFTER_KEY
#undef TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS
#undef TYPVAL_ENCODE_CONV_DICT_END
@@ -19182,7 +19323,9 @@ TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(static, nothing, void *, ignored)
void clear_tv(typval_T *varp)
{
if (varp != NULL && varp->v_type != VAR_UNKNOWN) {
- encode_vim_to_nothing(varp, varp, "clear_tv argument");
+ const int evn_ret = encode_vim_to_nothing(varp, varp, "clear_tv argument");
+ (void)evn_ret;
+ assert(evn_ret == OK);
}
}
@@ -21570,9 +21713,12 @@ void func_unref(char_u *name)
fp = find_func(name);
if (fp == NULL) {
#ifdef EXITFREE
- if (!entered_free_all_mem) // NOLINT(readability/braces)
-#endif
+ if (!entered_free_all_mem) {
EMSG2(_(e_intern2), "func_unref()");
+ }
+#else
+ EMSG2(_(e_intern2), "func_unref()");
+#endif
} else {
user_func_unref(fp);
}
@@ -23042,11 +23188,10 @@ static void on_job_output(Stream *stream, TerminalJobData *data, RBuffer *buf,
terminal_receive(data->term, ptr, count);
}
+ rbuffer_consumed(buf, count);
if (callback->type != kCallbackNone) {
process_job_event(data, callback, type, ptr, count, 0);
}
-
- rbuffer_consumed(buf, count);
}
static void eval_job_process_exit_cb(Process *proc, int status, void *d)
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index 980a8d2326..e0b72feb19 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -79,7 +79,7 @@ return {
eval={args=1},
eventhandler={},
executable={args=1},
- execute={args=1},
+ execute={args={1, 2}},
exepath={args=1},
exists={args=1},
exp={args=1, func="float_op_wrapper", data="&exp"},
@@ -302,7 +302,6 @@ return {
tanh={args=1, func="float_op_wrapper", data="&tanh"},
tempname={},
termopen={args={1, 2}},
- test={args=1},
timer_start={args={2,3}},
timer_stop={args=1},
tolower={args=1},
diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c
index 5af4893975..ee66b7cf09 100644
--- a/src/nvim/eval/encode.c
+++ b/src/nvim/eval/encode.c
@@ -108,9 +108,12 @@ static int conv_error(const char *const msg, const MPConvStack *const mpstack,
{
garray_T msg_ga;
ga_init(&msg_ga, (int)sizeof(char), 80);
- char *const key_msg = _("key %s");
- char *const key_pair_msg = _("key %s at index %i from special map");
- char *const idx_msg = _("index %i");
+ const char *const key_msg = _("key %s");
+ const char *const key_pair_msg = _("key %s at index %i from special map");
+ const char *const idx_msg = _("index %i");
+ const char *const partial_arg_msg = _("partial");
+ const char *const partial_arg_i_msg = _("argument %i");
+ const char *const partial_self_msg = _("partial self dictionary");
for (size_t i = 0; i < kv_size(*mpstack); i++) {
if (i != 0) {
ga_concat(&msg_ga, ", ");
@@ -154,6 +157,29 @@ static int conv_error(const char *const msg, const MPConvStack *const mpstack,
}
break;
}
+ case kMPConvPartial: {
+ switch (v.data.p.stage) {
+ case kMPConvPartialArgs: {
+ assert(false);
+ break;
+ }
+ case kMPConvPartialSelf: {
+ ga_concat(&msg_ga, partial_arg_msg);
+ break;
+ }
+ case kMPConvPartialEnd: {
+ ga_concat(&msg_ga, partial_self_msg);
+ break;
+ }
+ }
+ break;
+ }
+ case kMPConvPartialList: {
+ const int idx = (int)(v.data.a.arg - v.data.a.argv) - 1;
+ vim_snprintf((char *)IObuff, IOSIZE, partial_arg_i_msg, idx);
+ ga_concat(&msg_ga, IObuff);
+ break;
+ }
}
}
EMSG3(msg, objname, (kv_size(*mpstack) == 0
@@ -254,7 +280,7 @@ int encode_read_from_list(ListReaderState *const state, char *const buf,
: OK);
}
-#define TYPVAL_ENCODE_CONV_STRING(buf, len) \
+#define TYPVAL_ENCODE_CONV_STRING(tv, buf, len) \
do { \
const char *const buf_ = (const char *) buf; \
if (buf == NULL) { \
@@ -273,19 +299,19 @@ int encode_read_from_list(ListReaderState *const state, char *const buf,
} \
} while (0)
-#define TYPVAL_ENCODE_CONV_STR_STRING(buf, len) \
- TYPVAL_ENCODE_CONV_STRING(buf, len)
+#define TYPVAL_ENCODE_CONV_STR_STRING(tv, buf, len) \
+ TYPVAL_ENCODE_CONV_STRING(tv, buf, len)
-#define TYPVAL_ENCODE_CONV_EXT_STRING(buf, len, type)
+#define TYPVAL_ENCODE_CONV_EXT_STRING(tv, buf, len, type)
-#define TYPVAL_ENCODE_CONV_NUMBER(num) \
+#define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \
do { \
char numbuf[NUMBUFLEN]; \
vim_snprintf(numbuf, ARRAY_SIZE(numbuf), "%" PRId64, (int64_t) (num)); \
ga_concat(gap, numbuf); \
} while (0)
-#define TYPVAL_ENCODE_CONV_FLOAT(flt) \
+#define TYPVAL_ENCODE_CONV_FLOAT(tv, flt) \
do { \
const float_T flt_ = (flt); \
switch (fpclassify(flt_)) { \
@@ -308,103 +334,75 @@ int encode_read_from_list(ListReaderState *const state, char *const buf,
} \
} while (0)
-#define TYPVAL_ENCODE_CONV_FUNC(fun) \
+#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \
do { \
- ga_concat(gap, "function("); \
- TYPVAL_ENCODE_CONV_STRING(fun, STRLEN(fun)); \
- ga_append(gap, ')'); \
+ const char *const fun_ = (const char *)(fun); \
+ if (fun_ == NULL) { \
+ EMSG2(_(e_intern2), "string(): NULL function name"); \
+ ga_concat(gap, "function(NULL"); \
+ } else { \
+ ga_concat(gap, "function("); \
+ TYPVAL_ENCODE_CONV_STRING(tv, fun_, strlen(fun_)); \
+ }\
} while (0)
-#define TYPVAL_ENCODE_CONV_PARTIAL(pt) \
+#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(tv, len) \
do { \
- int i; \
- ga_concat(gap, "function("); \
- if (pt->pt_name != NULL) { \
- size_t len; \
- char_u *p; \
- len = 3; \
- len += STRLEN(pt->pt_name); \
- for (p = pt->pt_name; *p != NUL; mb_ptr_adv(p)) { \
- if (*p == '\'') { \
- len++; \
- } \
- } \
- char_u *r, *s; \
- s = r = xmalloc(len); \
- if (r != NULL) { \
- *r++ = '\''; \
- for (p = pt->pt_name; *p != NUL; ) { \
- if (*p == '\'') { \
- *r++ = '\''; \
- } \
- MB_COPY_CHAR(p, r); \
- } \
- *r++ = '\''; \
- *r++ = NUL; \
- } \
- ga_concat(gap, s); \
- xfree(s); \
- } \
- if (pt->pt_argc > 0) { \
- ga_concat(gap, ", ["); \
- for (i = 0; i < pt->pt_argc; i++) { \
- if (i > 0) { \
- ga_concat(gap, ", "); \
- } \
- char *tofree = encode_tv2string(&pt->pt_argv[i], NULL); \
- ga_concat(gap, tofree); \
- xfree(tofree); \
- } \
- ga_append(gap, ']'); \
- } \
- if (pt->pt_dict != NULL) { \
- typval_T dtv; \
- ga_concat(gap, ", "); \
- dtv.v_type = VAR_DICT; \
- dtv.vval.v_dict = pt->pt_dict; \
- char *tofree = encode_tv2string(&dtv, NULL); \
- ga_concat(gap, tofree); \
- xfree(tofree); \
- } \
- ga_append(gap, ')'); \
+ if (len != 0) { \
+ ga_concat(gap, ", "); \
+ } \
+ } while (0)
+
+#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, len) \
+ do { \
+ if ((ptrdiff_t)len != -1) { \
+ ga_concat(gap, ", "); \
+ } \
} while (0)
-#define TYPVAL_ENCODE_CONV_EMPTY_LIST() \
+#define TYPVAL_ENCODE_CONV_FUNC_END(tv) \
+ ga_append(gap, ')')
+
+#define TYPVAL_ENCODE_CONV_EMPTY_LIST(tv) \
ga_concat(gap, "[]")
-#define TYPVAL_ENCODE_CONV_LIST_START(len) \
+#define TYPVAL_ENCODE_CONV_LIST_START(tv, len) \
ga_append(gap, '[')
-#define TYPVAL_ENCODE_CONV_EMPTY_DICT() \
+#define TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, mpsv)
+
+#define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \
ga_concat(gap, "{}")
-#define TYPVAL_ENCODE_CONV_NIL() \
+#define TYPVAL_ENCODE_CONV_NIL(tv) \
ga_concat(gap, "v:null")
-#define TYPVAL_ENCODE_CONV_BOOL(num) \
+#define TYPVAL_ENCODE_CONV_BOOL(tv, num) \
ga_concat(gap, ((num)? "v:true": "v:false"))
-#define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(num)
+#define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(tv, num)
-#define TYPVAL_ENCODE_CONV_DICT_START(len) \
+#define TYPVAL_ENCODE_CONV_DICT_START(tv, dict, len) \
ga_append(gap, '{')
-#define TYPVAL_ENCODE_CONV_DICT_END() \
+#define TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(tv, dict, mpsv)
+
+#define TYPVAL_ENCODE_CONV_DICT_END(tv, dict) \
ga_append(gap, '}')
-#define TYPVAL_ENCODE_CONV_DICT_AFTER_KEY() \
+#define TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(tv, dict) \
ga_concat(gap, ": ")
-#define TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS() \
+#define TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(tv, dict) \
ga_concat(gap, ", ")
-#define TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK(label, key)
+#define TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK(label, key)
-#define TYPVAL_ENCODE_CONV_LIST_END() \
+#define TYPVAL_ENCODE_CONV_LIST_END(tv) \
ga_append(gap, ']')
-#define TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS() \
- TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS()
+#define TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(tv) \
+ TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(tv, NULL)
#define TYPVAL_ENCODE_CONV_RECURSE(val, conv_type) \
do { \
@@ -437,9 +435,15 @@ int encode_read_from_list(ListReaderState *const state, char *const buf,
#define TYPVAL_ENCODE_ALLOW_SPECIALS false
-// string_convert_one_value()
-// encode_vim_to_string()
-TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(static, string, garray_T *const, gap)
+#define TYPVAL_ENCODE_SCOPE static
+#define TYPVAL_ENCODE_NAME string
+#define TYPVAL_ENCODE_FIRST_ARG_TYPE garray_T *const
+#define TYPVAL_ENCODE_FIRST_ARG_NAME gap
+#include "nvim/eval/typval_encode.c.h"
+#undef TYPVAL_ENCODE_SCOPE
+#undef TYPVAL_ENCODE_NAME
+#undef TYPVAL_ENCODE_FIRST_ARG_TYPE
+#undef TYPVAL_ENCODE_FIRST_ARG_NAME
#undef TYPVAL_ENCODE_CONV_RECURSE
#define TYPVAL_ENCODE_CONV_RECURSE(val, conv_type) \
@@ -469,9 +473,15 @@ TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(static, string, garray_T *const, gap)
return OK; \
} while (0)
-// echo_convert_one_value()
-// encode_vim_to_echo()
-TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(, echo, garray_T *const, gap)
+#define TYPVAL_ENCODE_SCOPE
+#define TYPVAL_ENCODE_NAME echo
+#define TYPVAL_ENCODE_FIRST_ARG_TYPE garray_T *const
+#define TYPVAL_ENCODE_FIRST_ARG_NAME gap
+#include "nvim/eval/typval_encode.c.h"
+#undef TYPVAL_ENCODE_SCOPE
+#undef TYPVAL_ENCODE_NAME
+#undef TYPVAL_ENCODE_FIRST_ARG_TYPE
+#undef TYPVAL_ENCODE_FIRST_ARG_NAME
#undef TYPVAL_ENCODE_CONV_RECURSE
#define TYPVAL_ENCODE_CONV_RECURSE(val, conv_type) \
@@ -489,15 +499,15 @@ TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(, echo, garray_T *const, gap)
#define TYPVAL_ENCODE_ALLOW_SPECIALS true
#undef TYPVAL_ENCODE_CONV_NIL
-#define TYPVAL_ENCODE_CONV_NIL() \
+#define TYPVAL_ENCODE_CONV_NIL(tv) \
ga_concat(gap, "null")
#undef TYPVAL_ENCODE_CONV_BOOL
-#define TYPVAL_ENCODE_CONV_BOOL(num) \
+#define TYPVAL_ENCODE_CONV_BOOL(tv, num) \
ga_concat(gap, ((num)? "true": "false"))
#undef TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER
-#define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(num) \
+#define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(tv, num) \
do { \
char numbuf[NUMBUFLEN]; \
vim_snprintf(numbuf, ARRAY_SIZE(numbuf), "%" PRIu64, (num)); \
@@ -505,7 +515,7 @@ TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(, echo, garray_T *const, gap)
} while (0)
#undef TYPVAL_ENCODE_CONV_FLOAT
-#define TYPVAL_ENCODE_CONV_FLOAT(flt) \
+#define TYPVAL_ENCODE_CONV_FLOAT(tv, flt) \
do { \
const float_T flt_ = (flt); \
switch (fpclassify(flt_)) { \
@@ -694,7 +704,7 @@ static inline int convert_to_json_string(garray_T *const gap,
}
#undef TYPVAL_ENCODE_CONV_STRING
-#define TYPVAL_ENCODE_CONV_STRING(buf, len) \
+#define TYPVAL_ENCODE_CONV_STRING(tv, buf, len) \
do { \
if (convert_to_json_string(gap, (const char *) (buf), (len)) != OK) { \
return FAIL; \
@@ -702,25 +712,19 @@ static inline int convert_to_json_string(garray_T *const gap,
} while (0)
#undef TYPVAL_ENCODE_CONV_EXT_STRING
-#define TYPVAL_ENCODE_CONV_EXT_STRING(buf, len, type) \
+#define TYPVAL_ENCODE_CONV_EXT_STRING(tv, buf, len, type) \
do { \
xfree(buf); \
EMSG(_("E474: Unable to convert EXT string to JSON")); \
return FAIL; \
} while (0)
-#undef TYPVAL_ENCODE_CONV_FUNC
-#define TYPVAL_ENCODE_CONV_FUNC(fun) \
+#undef TYPVAL_ENCODE_CONV_FUNC_START
+#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \
return conv_error(_("E474: Error while dumping %s, %s: " \
"attempt to dump function reference"), \
mpstack, objname)
-#undef TYPVAL_ENCODE_CONV_PARTIAL
-#define TYPVAL_ENCODE_CONV_PARTIAL(pt) \
- return conv_error(_("E474: Error while dumping %s, %s: " \
- "attempt to dump partial"), \
- mpstack, objname)
-
/// Check whether given key can be used in json_encode()
///
/// @param[in] tv Key to check.
@@ -759,8 +763,8 @@ bool encode_check_json_key(const typval_T *const tv)
return true;
}
-#undef TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK
-#define TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK(label, key) \
+#undef TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK
+#define TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK(label, key) \
do { \
if (!encode_check_json_key(&key)) { \
EMSG(_("E474: Invalid key in special dictionary")); \
@@ -768,28 +772,38 @@ bool encode_check_json_key(const typval_T *const tv)
} \
} while (0)
-// json_convert_one_value()
-// encode_vim_to_json()
-TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(static, json, garray_T *const, gap)
+#define TYPVAL_ENCODE_SCOPE static
+#define TYPVAL_ENCODE_NAME json
+#define TYPVAL_ENCODE_FIRST_ARG_TYPE garray_T *const
+#define TYPVAL_ENCODE_FIRST_ARG_NAME gap
+#include "nvim/eval/typval_encode.c.h"
+#undef TYPVAL_ENCODE_SCOPE
+#undef TYPVAL_ENCODE_NAME
+#undef TYPVAL_ENCODE_FIRST_ARG_TYPE
+#undef TYPVAL_ENCODE_FIRST_ARG_NAME
#undef TYPVAL_ENCODE_CONV_STRING
#undef TYPVAL_ENCODE_CONV_STR_STRING
#undef TYPVAL_ENCODE_CONV_EXT_STRING
#undef TYPVAL_ENCODE_CONV_NUMBER
#undef TYPVAL_ENCODE_CONV_FLOAT
-#undef TYPVAL_ENCODE_CONV_FUNC
-#undef TYPVAL_ENCODE_CONV_PARTIAL
+#undef TYPVAL_ENCODE_CONV_FUNC_START
+#undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS
+#undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF
+#undef TYPVAL_ENCODE_CONV_FUNC_END
#undef TYPVAL_ENCODE_CONV_EMPTY_LIST
#undef TYPVAL_ENCODE_CONV_LIST_START
+#undef TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START
#undef TYPVAL_ENCODE_CONV_EMPTY_DICT
#undef TYPVAL_ENCODE_CONV_NIL
#undef TYPVAL_ENCODE_CONV_BOOL
#undef TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER
#undef TYPVAL_ENCODE_CONV_DICT_START
+#undef TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START
#undef TYPVAL_ENCODE_CONV_DICT_END
#undef TYPVAL_ENCODE_CONV_DICT_AFTER_KEY
#undef TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS
-#undef TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK
+#undef TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK
#undef TYPVAL_ENCODE_CONV_LIST_END
#undef TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS
#undef TYPVAL_ENCODE_CONV_RECURSE
@@ -807,7 +821,10 @@ char *encode_tv2string(typval_T *tv, size_t *len)
{
garray_T ga;
ga_init(&ga, (int)sizeof(char), 80);
- encode_vim_to_string(&ga, tv, "encode_tv2string() argument");
+ const int evs_ret = encode_vim_to_string(&ga, tv,
+ "encode_tv2string() argument");
+ (void)evs_ret;
+ assert(evs_ret == OK);
did_echo_string_emsg = false;
if (len != NULL) {
*len = (size_t) ga.ga_len;
@@ -833,7 +850,9 @@ char *encode_tv2echo(typval_T *tv, size_t *len)
ga_concat(&ga, tv->vval.v_string);
}
} else {
- encode_vim_to_echo(&ga, tv, ":echo argument");
+ const int eve_ret = encode_vim_to_echo(&ga, tv, ":echo argument");
+ (void)eve_ret;
+ assert(eve_ret == OK);
}
if (len != NULL) {
*len = (size_t) ga.ga_len;
@@ -854,16 +873,19 @@ char *encode_tv2json(typval_T *tv, size_t *len)
{
garray_T ga;
ga_init(&ga, (int)sizeof(char), 80);
- encode_vim_to_json(&ga, tv, "encode_tv2json() argument");
+ const int evj_ret = encode_vim_to_json(&ga, tv, "encode_tv2json() argument");
+ if (!evj_ret) {
+ ga_clear(&ga);
+ }
did_echo_string_emsg = false;
if (len != NULL) {
- *len = (size_t) ga.ga_len;
+ *len = (size_t)ga.ga_len;
}
ga_append(&ga, '\0');
- return (char *) ga.ga_data;
+ return (char *)ga.ga_data;
}
-#define TYPVAL_ENCODE_CONV_STRING(buf, len) \
+#define TYPVAL_ENCODE_CONV_STRING(tv, buf, len) \
do { \
if (buf == NULL) { \
msgpack_pack_bin(packer, 0); \
@@ -874,7 +896,7 @@ char *encode_tv2json(typval_T *tv, size_t *len)
} \
} while (0)
-#define TYPVAL_ENCODE_CONV_STR_STRING(buf, len) \
+#define TYPVAL_ENCODE_CONV_STR_STRING(tv, buf, len) \
do { \
if (buf == NULL) { \
msgpack_pack_str(packer, 0); \
@@ -885,7 +907,7 @@ char *encode_tv2json(typval_T *tv, size_t *len)
} \
} while (0)
-#define TYPVAL_ENCODE_CONV_EXT_STRING(buf, len, type) \
+#define TYPVAL_ENCODE_CONV_EXT_STRING(tv, buf, len, type) \
do { \
if (buf == NULL) { \
msgpack_pack_ext(packer, 0, (int8_t) type); \
@@ -896,35 +918,36 @@ char *encode_tv2json(typval_T *tv, size_t *len)
} \
} while (0)
-#define TYPVAL_ENCODE_CONV_NUMBER(num) \
- msgpack_pack_int64(packer, (int64_t) (num))
+#define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \
+ msgpack_pack_int64(packer, (int64_t)(num))
-#define TYPVAL_ENCODE_CONV_FLOAT(flt) \
- msgpack_pack_double(packer, (double) (flt))
+#define TYPVAL_ENCODE_CONV_FLOAT(tv, flt) \
+ msgpack_pack_double(packer, (double)(flt))
-#define TYPVAL_ENCODE_CONV_FUNC(fun) \
+#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \
return conv_error(_("E5004: Error while dumping %s, %s: " \
"attempt to dump function reference"), \
mpstack, objname)
-#define TYPVAL_ENCODE_CONV_PARTIAL(partial) \
- return conv_error(_("E5004: Error while dumping %s, %s: " \
- "attempt to dump partial"), \
- mpstack, objname)
+#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(tv, len)
+#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, len)
+#define TYPVAL_ENCODE_CONV_FUNC_END(tv)
-#define TYPVAL_ENCODE_CONV_EMPTY_LIST() \
+#define TYPVAL_ENCODE_CONV_EMPTY_LIST(tv) \
msgpack_pack_array(packer, 0)
-#define TYPVAL_ENCODE_CONV_LIST_START(len) \
- msgpack_pack_array(packer, (size_t) (len))
+#define TYPVAL_ENCODE_CONV_LIST_START(tv, len) \
+ msgpack_pack_array(packer, (size_t)(len))
-#define TYPVAL_ENCODE_CONV_EMPTY_DICT() \
+#define TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, mpsv)
+
+#define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \
msgpack_pack_map(packer, 0)
-#define TYPVAL_ENCODE_CONV_NIL() \
+#define TYPVAL_ENCODE_CONV_NIL(tv) \
msgpack_pack_nil(packer)
-#define TYPVAL_ENCODE_CONV_BOOL(num) \
+#define TYPVAL_ENCODE_CONV_BOOL(tv, num) \
do { \
if ((num)) { \
msgpack_pack_true(packer); \
@@ -933,23 +956,25 @@ char *encode_tv2json(typval_T *tv, size_t *len)
} \
} while (0)
-#define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(num) \
+#define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(tv, num) \
msgpack_pack_uint64(packer, (num))
-#define TYPVAL_ENCODE_CONV_DICT_START(len) \
- msgpack_pack_map(packer, (size_t) (len))
+#define TYPVAL_ENCODE_CONV_DICT_START(tv, dict, len) \
+ msgpack_pack_map(packer, (size_t)(len))
+
+#define TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(tv, dict, mpsv)
-#define TYPVAL_ENCODE_CONV_DICT_END()
+#define TYPVAL_ENCODE_CONV_DICT_END(tv, dict)
-#define TYPVAL_ENCODE_CONV_DICT_AFTER_KEY()
+#define TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(tv, dict)
-#define TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS()
+#define TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(tv, dict)
-#define TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK(label, key)
+#define TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK(label, key)
-#define TYPVAL_ENCODE_CONV_LIST_END()
+#define TYPVAL_ENCODE_CONV_LIST_END(tv)
-#define TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS()
+#define TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(tv)
#define TYPVAL_ENCODE_CONV_RECURSE(val, conv_type) \
return conv_error(_("E5005: Unable to dump %s: " \
@@ -958,28 +983,38 @@ char *encode_tv2json(typval_T *tv, size_t *len)
#define TYPVAL_ENCODE_ALLOW_SPECIALS true
-// msgpack_convert_one_value()
-// encode_vim_to_msgpack()
-TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(, msgpack, msgpack_packer *const, packer)
+#define TYPVAL_ENCODE_SCOPE
+#define TYPVAL_ENCODE_NAME msgpack
+#define TYPVAL_ENCODE_FIRST_ARG_TYPE msgpack_packer *const
+#define TYPVAL_ENCODE_FIRST_ARG_NAME packer
+#include "nvim/eval/typval_encode.c.h"
+#undef TYPVAL_ENCODE_SCOPE
+#undef TYPVAL_ENCODE_NAME
+#undef TYPVAL_ENCODE_FIRST_ARG_TYPE
+#undef TYPVAL_ENCODE_FIRST_ARG_NAME
#undef TYPVAL_ENCODE_CONV_STRING
#undef TYPVAL_ENCODE_CONV_STR_STRING
#undef TYPVAL_ENCODE_CONV_EXT_STRING
#undef TYPVAL_ENCODE_CONV_NUMBER
#undef TYPVAL_ENCODE_CONV_FLOAT
-#undef TYPVAL_ENCODE_CONV_FUNC
-#undef TYPVAL_ENCODE_CONV_PARTIAL
+#undef TYPVAL_ENCODE_CONV_FUNC_START
+#undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS
+#undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF
+#undef TYPVAL_ENCODE_CONV_FUNC_END
#undef TYPVAL_ENCODE_CONV_EMPTY_LIST
#undef TYPVAL_ENCODE_CONV_LIST_START
+#undef TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START
#undef TYPVAL_ENCODE_CONV_EMPTY_DICT
#undef TYPVAL_ENCODE_CONV_NIL
#undef TYPVAL_ENCODE_CONV_BOOL
#undef TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER
#undef TYPVAL_ENCODE_CONV_DICT_START
+#undef TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START
#undef TYPVAL_ENCODE_CONV_DICT_END
#undef TYPVAL_ENCODE_CONV_DICT_AFTER_KEY
#undef TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS
-#undef TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK
+#undef TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK
#undef TYPVAL_ENCODE_CONV_LIST_END
#undef TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS
#undef TYPVAL_ENCODE_CONV_RECURSE
diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h
new file mode 100644
index 0000000000..365eb2dd77
--- /dev/null
+++ b/src/nvim/eval/typval_encode.c.h
@@ -0,0 +1,802 @@
+/// @file eval/typval_encode.c.h
+///
+/// Contains set of macros used to convert (possibly recursive) typval_T into
+/// something else. For these macros to work the following macros must be
+/// defined:
+
+/// @def TYPVAL_ENCODE_CONV_NIL
+/// @brief Macros used to convert NIL value
+///
+/// Is called both for special dictionary (unless #TYPVAL_ENCODE_ALLOW_SPECIALS
+/// is false) and `v:null`.
+///
+/// @param tv Pointer to typval where value is stored. May not be NULL. May
+/// point to special dictionary.
+
+/// @def TYPVAL_ENCODE_CONV_BOOL
+/// @brief Macros used to convert boolean value
+///
+/// Is called both for special dictionary (unless #TYPVAL_ENCODE_ALLOW_SPECIALS
+/// is false) and `v:true`/`v:false`.
+///
+/// @param tv Pointer to typval where value is stored. May not be NULL. May
+/// point to a special dictionary.
+/// @param num Boolean value to convert. Value is an expression which
+/// evaluates to some integer.
+
+/// @def TYPVAL_ENCODE_CONV_NUMBER
+/// @brief Macros used to convert integer
+///
+/// @param tv Pointer to typval where value is stored. May not be NULL. May
+/// point to a special dictionary.
+/// @param num Integer to convert, must accept both varnumber_T and int64_t.
+
+/// @def TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER
+/// @brief Macros used to convert unsigned integer
+///
+/// Not used if #TYPVAL_ENCODE_ALLOW_SPECIALS is false, but still must be
+/// defined.
+///
+/// @param tv Pointer to typval where value is stored. May not be NULL. Points
+/// to a special dictionary.
+/// @param num Integer to convert, must accept uint64_t.
+
+/// @def TYPVAL_ENCODE_CONV_FLOAT
+/// @brief Macros used to convert floating-point number
+///
+/// @param tv Pointer to typval where value is stored. May not be NULL. May
+/// point to a special dictionary.
+/// @param flt Number to convert, must accept float_T.
+
+/// @def TYPVAL_ENCODE_CONV_STRING
+/// @brief Macros used to convert plain string
+///
+/// Is used to convert VAR_STRING objects as well as BIN strings represented as
+/// special dictionary.
+///
+/// @param tv Pointer to typval where value is stored. May not be NULL. May
+/// point to a special dictionary.
+/// @param buf String to convert. Is a char[] buffer, not NUL-terminated.
+/// @param len String length.
+
+/// @def TYPVAL_ENCODE_CONV_STR_STRING
+/// @brief Like #TYPVAL_ENCODE_CONV_STRING, but for STR strings
+///
+/// Is used to convert dictionary keys and STR strings represented as special
+/// dictionaries.
+///
+/// @param tv Pointer to typval where value is stored. May be NULL. May
+/// point to a special dictionary.
+/// @param buf String to convert. Is a char[] buffer, not NUL-terminated.
+/// @param len String length.
+
+/// @def TYPVAL_ENCODE_CONV_EXT_STRING
+/// @brief Macros used to convert EXT string
+///
+/// Is used to convert EXT strings represented as special dictionaries. Never
+/// actually used if #TYPVAL_ENCODE_ALLOW_SPECIALS is false, but still must be
+/// defined.
+///
+/// @param tv Pointer to typval where value is stored. May not be NULL. Points
+/// to a special dictionary.
+/// @param buf String to convert. Is a char[] buffer, not NUL-terminated.
+/// @param len String length.
+/// @param type EXT type.
+
+/// @def TYPVAL_ENCODE_CONV_FUNC_START
+/// @brief Macros used when starting to convert a funcref or a partial
+///
+/// @param tv Pointer to typval where value is stored. May not be NULL.
+/// @param fun Function name. May be NULL.
+
+/// @def TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS
+/// @brief Macros used before starting to convert partial arguments
+///
+/// @param tv Pointer to typval where value is stored. May not be NULL.
+/// @param len Number of arguments. Zero for absent arguments or when
+/// converting a funcref.
+
+/// @def TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF
+/// @brief Macros used before starting to convert self dictionary
+///
+/// @param tv Pointer to typval where value is stored. May not be NULL.
+/// @param len Number of arguments. May be zero for empty dictionary or -1 for
+/// missing self dictionary, also when converting function
+/// reference.
+
+/// @def TYPVAL_ENCODE_CONV_FUNC_END
+/// @brief Macros used after converting a funcref or a partial
+///
+/// @param tv Pointer to typval where value is stored. May not be NULL.
+
+/// @def TYPVAL_ENCODE_CONV_EMPTY_LIST
+/// @brief Macros used to convert an empty list
+///
+/// @param tv Pointer to typval where value is stored. May not be NULL.
+
+/// @def TYPVAL_ENCODE_CONV_EMPTY_DICT
+/// @brief Macros used to convert an empty dictionary
+///
+/// @param tv Pointer to typval where value is stored. May be NULL. May
+/// point to a special dictionary.
+/// @param dict Converted dictionary, lvalue or #TYPVAL_ENCODE_NODICT_VAR
+/// (for dictionaries represented as special lists).
+
+/// @def TYPVAL_ENCODE_CONV_LIST_START
+/// @brief Macros used before starting to convert non-empty list
+///
+/// @param tv Pointer to typval where value is stored. May be NULL. May
+/// point to a special dictionary.
+/// @param len List length. Is an expression which evaluates to an integer.
+
+/// @def TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START
+/// @brief Macros used after pushing list onto the stack
+///
+/// Only used for real list_T* lists, not for special dictionaries or partial
+/// arguments.
+///
+/// @param tv Pointer to typval where value is stored. May be NULL. May
+/// point to a special dictionary.
+/// @param mpsv Pushed MPConvStackVal value.
+
+/// @def TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS
+/// @brief Macros used after finishing converting non-last list item
+///
+/// @param tv Pointer to typval where list is stored. May be NULL.
+
+/// @def TYPVAL_ENCODE_CONV_LIST_END
+/// @brief Macros used after converting non-empty list
+///
+/// @param tv Pointer to typval where list is stored. May be NULL.
+
+/// @def TYPVAL_ENCODE_CONV_DICT_START
+/// @brief Macros used before starting to convert non-empty dictionary
+///
+/// Only used for real dict_T* dictionaries, not for special dictionaries. Also
+/// used for partial self dictionary.
+///
+/// @param tv Pointer to typval where dictionary is stored. May be NULL. May
+/// point to a special dictionary.
+/// @param dict Converted dictionary, lvalue or #TYPVAL_ENCODE_NODICT_VAR
+/// (for dictionaries represented as special lists).
+/// @param len Dictionary length. Is an expression which evaluates to an
+/// integer.
+
+/// @def TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START
+/// @brief Macros used after pushing dictionary onto the stack
+///
+/// @param tv Pointer to typval where dictionary is stored. May be NULL.
+/// May not point to a special dictionary.
+/// @param dict Converted dictionary, lvalue.
+/// @param mpsv Pushed MPConvStackVal value.
+
+/// @def TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK
+/// @brief Macros used to check special dictionary key
+///
+/// @param label Label for goto in case check was not successfull.
+/// @param key typval_T key to check.
+
+/// @def TYPVAL_ENCODE_CONV_DICT_AFTER_KEY
+/// @brief Macros used after finishing converting dictionary key
+///
+/// @param tv Pointer to typval where dictionary is stored. May be NULL. May
+/// point to a special dictionary.
+/// @param dict Converted dictionary, lvalue or #TYPVAL_ENCODE_NODICT_VAR
+/// (for dictionaries represented as special lists).
+
+/// @def TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS
+/// @brief Macros used after finishing converting non-last dictionary value
+///
+/// @param tv Pointer to typval where dictionary is stored. May be NULL. May
+/// point to a special dictionary.
+/// @param dict Converted dictionary, lvalue or #TYPVAL_ENCODE_NODICT_VAR
+/// (for dictionaries represented as special lists).
+
+/// @def TYPVAL_ENCODE_CONV_DICT_END
+/// @brief Macros used after converting non-empty dictionary
+///
+/// @param tv Pointer to typval where dictionary is stored. May be NULL. May
+/// point to a special dictionary.
+/// @param dict Converted dictionary, lvalue or #TYPVAL_ENCODE_NODICT_VAR
+/// (for dictionaries represented as special lists).
+
+/// @def TYPVAL_ENCODE_CONV_RECURSE
+/// @brief Macros used when self-containing container is detected
+///
+/// @param val Container for which this situation was detected.
+/// @param conv_type Type of the stack entry, @see MPConvStackValType.
+
+/// @def TYPVAL_ENCODE_ALLOW_SPECIALS
+/// @brief Macros that specifies whether special dictionaries are special
+///
+/// Must be something that evaluates to boolean, most likely `true` or `false`.
+/// If it is false then special dictionaries are not treated specially.
+
+/// @def TYPVAL_ENCODE_SCOPE
+/// @brief Scope of the main function: either nothing or `static`
+
+/// @def TYPVAL_ENCODE_NAME
+/// @brief Name of the target converter
+///
+/// After including this file it will define function
+/// `encode_vim_to_{TYPVAL_ENCODE_NAME}` with scope #TYPVAL_ENCODE_SCOPE and
+/// static functions `_typval_encode_{TYPVAL_ENCODE_NAME}_convert_one_value` and
+/// `_typval_encode_{TYPVAL_ENCODE_NAME}_check_self_reference`.
+
+/// @def TYPVAL_ENCODE_FIRST_ARG_TYPE
+/// @brief Type of the first argument, which will be used to return the results
+///
+/// Is expected to be a pointer type.
+
+/// @def TYPVAL_ENCODE_FIRST_ARG_NAME
+/// @brief Name of the first argument
+///
+/// This name will only be used by one of the above macros which are defined by
+/// the caller. Functions defined here do not use first argument directly.
+#ifndef NVIM_EVAL_TYPVAL_ENCODE_C_H
+#define NVIM_EVAL_TYPVAL_ENCODE_C_H
+#undef NVIM_EVAL_TYPVAL_ENCODE_C_H
+
+#include <stddef.h>
+#include <inttypes.h>
+#include <assert.h>
+
+#include "nvim/lib/kvec.h"
+#include "nvim/eval_defs.h"
+#include "nvim/eval/encode.h"
+#include "nvim/func_attr.h"
+#include "nvim/eval/typval_encode.h"
+
+/// Dummy variable used because some macros need lvalue
+///
+/// Must not be written to, if needed one must check that address of the
+/// macros argument is (not) equal to `&TYPVAL_ENCODE_NODICT_VAR`.
+const dict_T *const TYPVAL_ENCODE_NODICT_VAR = NULL;
+
+static inline int _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(
+ TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME,
+ void *const val, int *const val_copyID,
+ const MPConvStack *const mpstack, const int copyID,
+ const MPConvStackValType conv_type,
+ const char *const objname)
+ REAL_FATTR_NONNULL_ARG(2, 3, 4, 7) REAL_FATTR_WARN_UNUSED_RESULT
+ REAL_FATTR_ALWAYS_INLINE;
+
+/// Function for checking whether container references itself
+///
+/// @param TYPVAL_ENCODE_FIRST_ARG_NAME First argument.
+/// @param[in,out] val Container to check.
+/// @param val_copyID Pointer to the container attribute that holds copyID.
+/// After checking whether value of this attribute is
+/// copyID (variable) it is set to copyID.
+/// @param[in] mpstack Stack with values to convert. Read-only, used for error
+/// reporting.
+/// @param[in] copyID CopyID used by the caller.
+/// @param[in] conv_type Type of the conversion, @see MPConvStackValType.
+/// @param[in] objname Object name, used for error reporting.
+///
+/// @return NOTDONE in case of success, what to return in case of failure.
+static inline int _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(
+ TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME,
+ void *const val, int *const val_copyID,
+ const MPConvStack *const mpstack, const int copyID,
+ const MPConvStackValType conv_type,
+ const char *const objname)
+{
+ if (*val_copyID == copyID) {
+ TYPVAL_ENCODE_CONV_RECURSE(val, conv_type);
+ return OK;
+ }
+ *val_copyID = copyID;
+ return NOTDONE;
+}
+
+static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE(
+ TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME,
+ MPConvStack *const mpstack, MPConvStackVal *const cur_mpsv,
+ typval_T *const tv, const int copyID,
+ const char *const objname)
+ REAL_FATTR_NONNULL_ARG(2, 4, 6) REAL_FATTR_WARN_UNUSED_RESULT;
+
+/// Convert single value
+///
+/// Only scalar values are converted immediately, everything else is pushed onto
+/// the stack.
+///
+/// @param TYPVAL_ENCODE_FIRST_ARG_NAME First argument, defined by the
+/// includer. Only meaningful to macros
+/// defined by the includer.
+/// @param mpstack Stack with values to convert. Values which are not
+/// converted completely by this function (i.e.
+/// non-scalars) are pushed here.
+/// @param cur_mpsv Currently converted value from stack.
+/// @param tv Converted value.
+/// @param[in] copyID CopyID.
+/// @param[in] objname Object name, used for error reporting.
+///
+/// @return OK in case of success, FAIL in case of failure.
+static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE(
+ TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME,
+ MPConvStack *const mpstack, MPConvStackVal *const cur_mpsv,
+ typval_T *const tv, const int copyID,
+ const char *const objname)
+{
+ switch (tv->v_type) {
+ case VAR_STRING: {
+ TYPVAL_ENCODE_CONV_STRING(tv, tv->vval.v_string, tv_strlen(tv));
+ break;
+ }
+ case VAR_NUMBER: {
+ TYPVAL_ENCODE_CONV_NUMBER(tv, tv->vval.v_number);
+ break;
+ }
+ case VAR_FLOAT: {
+ TYPVAL_ENCODE_CONV_FLOAT(tv, tv->vval.v_float);
+ break;
+ }
+ case VAR_FUNC: {
+ TYPVAL_ENCODE_CONV_FUNC_START(tv, tv->vval.v_string);
+ TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(tv, 0);
+ TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, -1);
+ TYPVAL_ENCODE_CONV_FUNC_END(tv);
+ break;
+ }
+ case VAR_PARTIAL: {
+ partial_T *const pt = tv->vval.v_partial;
+ (void)pt;
+ TYPVAL_ENCODE_CONV_FUNC_START(tv, (pt == NULL ? NULL : pt->pt_name));
+ _mp_push(*mpstack, ((MPConvStackVal) {
+ .type = kMPConvPartial,
+ .tv = tv,
+ .data = {
+ .p = {
+ .stage = kMPConvPartialArgs,
+ .pt = tv->vval.v_partial,
+ },
+ },
+ }));
+ break;
+ }
+ case VAR_LIST: {
+ if (tv->vval.v_list == NULL || tv->vval.v_list->lv_len == 0) {
+ TYPVAL_ENCODE_CONV_EMPTY_LIST(tv);
+ break;
+ }
+ _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(tv->vval.v_list, lv_copyID, copyID,
+ kMPConvList);
+ TYPVAL_ENCODE_CONV_LIST_START(tv, tv->vval.v_list->lv_len);
+ _mp_push(*mpstack, ((MPConvStackVal) {
+ .type = kMPConvList,
+ .tv = tv,
+ .data = {
+ .l = {
+ .list = tv->vval.v_list,
+ .li = tv->vval.v_list->lv_first,
+ },
+ },
+ }));
+ TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, _mp_last(*mpstack));
+ break;
+ }
+ case VAR_SPECIAL: {
+ switch (tv->vval.v_special) {
+ case kSpecialVarNull: {
+ TYPVAL_ENCODE_CONV_NIL(tv);
+ break;
+ }
+ case kSpecialVarTrue:
+ case kSpecialVarFalse: {
+ TYPVAL_ENCODE_CONV_BOOL(tv, tv->vval.v_special == kSpecialVarTrue);
+ break;
+ }
+ }
+ break;
+ }
+ case VAR_DICT: {
+ if (tv->vval.v_dict == NULL
+ || tv->vval.v_dict->dv_hashtab.ht_used == 0) {
+ TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, tv->vval.v_dict);
+ break;
+ }
+ const dictitem_T *type_di;
+ const dictitem_T *val_di;
+ if (TYPVAL_ENCODE_ALLOW_SPECIALS
+ && tv->vval.v_dict->dv_hashtab.ht_used == 2
+ && (type_di = dict_find((dict_T *)tv->vval.v_dict,
+ (char_u *)"_TYPE", -1)) != NULL
+ && type_di->di_tv.v_type == VAR_LIST
+ && (val_di = dict_find((dict_T *)tv->vval.v_dict,
+ (char_u *)"_VAL", -1)) != NULL) {
+ size_t i;
+ for (i = 0; i < ARRAY_SIZE(eval_msgpack_type_lists); i++) {
+ if (type_di->di_tv.vval.v_list == eval_msgpack_type_lists[i]) {
+ break;
+ }
+ }
+ if (i == ARRAY_SIZE(eval_msgpack_type_lists)) {
+ goto _convert_one_value_regular_dict;
+ }
+ switch ((MessagePackType)i) {
+ case kMPNil: {
+ TYPVAL_ENCODE_CONV_NIL(tv);
+ break;
+ }
+ case kMPBoolean: {
+ if (val_di->di_tv.v_type != VAR_NUMBER) {
+ goto _convert_one_value_regular_dict;
+ }
+ TYPVAL_ENCODE_CONV_BOOL(tv, val_di->di_tv.vval.v_number);
+ break;
+ }
+ case kMPInteger: {
+ const list_T *val_list;
+ varnumber_T sign;
+ varnumber_T highest_bits;
+ varnumber_T high_bits;
+ varnumber_T low_bits;
+ // List of 4 integers; first is signed (should be 1 or -1, but
+ // this is not checked), second is unsigned and have at most
+ // one (sign is -1) or two (sign is 1) non-zero bits (number of
+ // bits is not checked), other unsigned and have at most 31
+ // non-zero bits (number of bits is not checked).
+ if (val_di->di_tv.v_type != VAR_LIST
+ || (val_list = val_di->di_tv.vval.v_list) == NULL
+ || val_list->lv_len != 4
+ || val_list->lv_first->li_tv.v_type != VAR_NUMBER
+ || (sign = val_list->lv_first->li_tv.vval.v_number) == 0
+ || val_list->lv_first->li_next->li_tv.v_type != VAR_NUMBER
+ || (highest_bits =
+ val_list->lv_first->li_next->li_tv.vval.v_number) < 0
+ || val_list->lv_last->li_prev->li_tv.v_type != VAR_NUMBER
+ || (high_bits =
+ val_list->lv_last->li_prev->li_tv.vval.v_number) < 0
+ || val_list->lv_last->li_tv.v_type != VAR_NUMBER
+ || (low_bits = val_list->lv_last->li_tv.vval.v_number) < 0) {
+ goto _convert_one_value_regular_dict;
+ }
+ uint64_t number = ((uint64_t)(((uint64_t)highest_bits) << 62)
+ | (uint64_t)(((uint64_t)high_bits) << 31)
+ | (uint64_t)low_bits);
+ if (sign > 0) {
+ TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(tv, number);
+ } else {
+ TYPVAL_ENCODE_CONV_NUMBER(tv, -number);
+ }
+ break;
+ }
+ case kMPFloat: {
+ if (val_di->di_tv.v_type != VAR_FLOAT) {
+ goto _convert_one_value_regular_dict;
+ }
+ TYPVAL_ENCODE_CONV_FLOAT(tv, val_di->di_tv.vval.v_float);
+ break;
+ }
+ case kMPString:
+ case kMPBinary: {
+ const bool is_string = ((MessagePackType)i == kMPString);
+ if (val_di->di_tv.v_type != VAR_LIST) {
+ goto _convert_one_value_regular_dict;
+ }
+ size_t len;
+ char *buf;
+ if (!encode_vim_list_to_buf(val_di->di_tv.vval.v_list, &len,
+ &buf)) {
+ goto _convert_one_value_regular_dict;
+ }
+ if (is_string) {
+ TYPVAL_ENCODE_CONV_STR_STRING(tv, buf, len);
+ } else {
+ TYPVAL_ENCODE_CONV_STRING(tv, buf, len);
+ }
+ xfree(buf);
+ break;
+ }
+ case kMPArray: {
+ if (val_di->di_tv.v_type != VAR_LIST) {
+ goto _convert_one_value_regular_dict;
+ }
+ _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val_di->di_tv.vval.v_list,
+ lv_copyID, copyID,
+ kMPConvList);
+ TYPVAL_ENCODE_CONV_LIST_START(tv,
+ val_di->di_tv.vval.v_list->lv_len);
+ _mp_push(*mpstack, ((MPConvStackVal) {
+ .tv = tv,
+ .type = kMPConvList,
+ .data = {
+ .l = {
+ .list = val_di->di_tv.vval.v_list,
+ .li = val_di->di_tv.vval.v_list->lv_first,
+ },
+ },
+ }));
+ break;
+ }
+ case kMPMap: {
+ if (val_di->di_tv.v_type != VAR_LIST) {
+ goto _convert_one_value_regular_dict;
+ }
+ list_T *const val_list = val_di->di_tv.vval.v_list;
+ if (val_list == NULL || val_list->lv_len == 0) {
+ TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, TYPVAL_ENCODE_NODICT_VAR);
+ break;
+ }
+ for (const listitem_T *li = val_list->lv_first; li != NULL;
+ li = li->li_next) {
+ if (li->li_tv.v_type != VAR_LIST
+ || li->li_tv.vval.v_list->lv_len != 2) {
+ goto _convert_one_value_regular_dict;
+ }
+ }
+ _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val_list, lv_copyID, copyID,
+ kMPConvPairs);
+ TYPVAL_ENCODE_CONV_DICT_START(tv, TYPVAL_ENCODE_NODICT_VAR,
+ val_list->lv_len);
+ _mp_push(*mpstack, ((MPConvStackVal) {
+ .tv = tv,
+ .type = kMPConvPairs,
+ .data = {
+ .l = {
+ .list = val_list,
+ .li = val_list->lv_first,
+ },
+ },
+ }));
+ break;
+ }
+ case kMPExt: {
+ const list_T *val_list;
+ varnumber_T type;
+ if (val_di->di_tv.v_type != VAR_LIST
+ || (val_list = val_di->di_tv.vval.v_list) == NULL
+ || val_list->lv_len != 2
+ || (val_list->lv_first->li_tv.v_type != VAR_NUMBER)
+ || (type = val_list->lv_first->li_tv.vval.v_number) > INT8_MAX
+ || type < INT8_MIN
+ || (val_list->lv_last->li_tv.v_type != VAR_LIST)) {
+ goto _convert_one_value_regular_dict;
+ }
+ size_t len;
+ char *buf;
+ if (!encode_vim_list_to_buf(val_list->lv_last->li_tv.vval.v_list,
+ &len, &buf)) {
+ goto _convert_one_value_regular_dict;
+ }
+ TYPVAL_ENCODE_CONV_EXT_STRING(tv, buf, len, type);
+ xfree(buf);
+ break;
+ }
+ }
+ break;
+ }
+_convert_one_value_regular_dict:
+ _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(tv->vval.v_dict, dv_copyID, copyID,
+ kMPConvDict);
+ TYPVAL_ENCODE_CONV_DICT_START(tv, tv->vval.v_dict,
+ tv->vval.v_dict->dv_hashtab.ht_used);
+ _mp_push(*mpstack, ((MPConvStackVal) {
+ .tv = tv,
+ .type = kMPConvDict,
+ .data = {
+ .d = {
+ .dict = tv->vval.v_dict,
+ .dictp = &tv->vval.v_dict,
+ .hi = tv->vval.v_dict->dv_hashtab.ht_array,
+ .todo = tv->vval.v_dict->dv_hashtab.ht_used,
+ },
+ },
+ }));
+ TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(tv, tv->vval.v_dict,
+ _mp_last(*mpstack));
+ break;
+ }
+ case VAR_UNKNOWN: {
+ EMSG2(_(e_intern2), STR(_TYPVAL_ENCODE_CONVERT_ONE_VALUE) "()");
+ return FAIL;
+ }
+ }
+typval_encode_stop_converting_one_item:
+ return OK;
+ // Prevent “unused label” warnings.
+ goto typval_encode_stop_converting_one_item;
+}
+
+TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE(
+ TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME,
+ typval_T *const tv, const char *const objname)
+ REAL_FATTR_NONNULL_ARG(2, 3) REAL_FATTR_WARN_UNUSED_RESULT;
+
+/// Convert the whole typval
+///
+/// @param TYPVAL_ENCODE_FIRST_ARG_NAME First argument, defined by the
+/// includer. Only meaningful to macros
+/// defined by the includer.
+/// @param top_tv Converted value.
+/// @param[in] objname Object name, used for error reporting.
+///
+/// @return OK in case of success, FAIL in case of failure.
+TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE(
+ TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME,
+ typval_T *const top_tv, const char *const objname)
+{
+ const int copyID = get_copyID();
+ MPConvStack mpstack;
+ _mp_init(mpstack);
+ if (_TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack,
+ NULL,
+ top_tv, copyID, objname)
+ == FAIL) {
+ goto encode_vim_to__error_ret;
+ }
+/// Label common for this and convert_one_value functions, used for escaping
+/// from macros like TYPVAL_ENCODE_CONV_DICT_START.
+typval_encode_stop_converting_one_item:
+ while (_mp_size(mpstack)) {
+ MPConvStackVal *cur_mpsv = &_mp_last(mpstack);
+ typval_T *tv = NULL;
+ switch (cur_mpsv->type) {
+ case kMPConvDict: {
+ if (!cur_mpsv->data.d.todo) {
+ (void)_mp_pop(mpstack);
+ cur_mpsv->data.d.dict->dv_copyID = copyID - 1;
+ TYPVAL_ENCODE_CONV_DICT_END(cur_mpsv->tv, *cur_mpsv->data.d.dictp);
+ continue;
+ } else if (cur_mpsv->data.d.todo
+ != cur_mpsv->data.d.dict->dv_hashtab.ht_used) {
+ TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(cur_mpsv->tv,
+ *cur_mpsv->data.d.dictp);
+ }
+ while (HASHITEM_EMPTY(cur_mpsv->data.d.hi)) {
+ cur_mpsv->data.d.hi++;
+ }
+ dictitem_T *const di = HI2DI(cur_mpsv->data.d.hi);
+ cur_mpsv->data.d.todo--;
+ cur_mpsv->data.d.hi++;
+ TYPVAL_ENCODE_CONV_STR_STRING(NULL, &di->di_key[0],
+ strlen((char *)&di->di_key[0]));
+ TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(cur_mpsv->tv,
+ *cur_mpsv->data.d.dictp);
+ tv = &di->di_tv;
+ break;
+ }
+ case kMPConvList: {
+ if (cur_mpsv->data.l.li == NULL) {
+ (void)_mp_pop(mpstack);
+ cur_mpsv->data.l.list->lv_copyID = copyID - 1;
+ TYPVAL_ENCODE_CONV_LIST_END(cur_mpsv->tv);
+ continue;
+ } else if (cur_mpsv->data.l.li != cur_mpsv->data.l.list->lv_first) {
+ TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(cur_mpsv->tv);
+ }
+ tv = &cur_mpsv->data.l.li->li_tv;
+ cur_mpsv->data.l.li = cur_mpsv->data.l.li->li_next;
+ break;
+ }
+ case kMPConvPairs: {
+ if (cur_mpsv->data.l.li == NULL) {
+ (void)_mp_pop(mpstack);
+ cur_mpsv->data.l.list->lv_copyID = copyID - 1;
+ TYPVAL_ENCODE_CONV_DICT_END(cur_mpsv->tv, TYPVAL_ENCODE_NODICT_VAR);
+ continue;
+ } else if (cur_mpsv->data.l.li != cur_mpsv->data.l.list->lv_first) {
+ TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(
+ cur_mpsv->tv, TYPVAL_ENCODE_NODICT_VAR);
+ }
+ const list_T *const kv_pair = cur_mpsv->data.l.li->li_tv.vval.v_list;
+ TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK(
+ encode_vim_to__error_ret, kv_pair->lv_first->li_tv);
+ if (_TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME,
+ &mpstack, cur_mpsv,
+ &kv_pair->lv_first->li_tv,
+ copyID,
+ objname) == FAIL) {
+ goto encode_vim_to__error_ret;
+ }
+ TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(cur_mpsv->tv,
+ TYPVAL_ENCODE_NODICT_VAR);
+ tv = &kv_pair->lv_last->li_tv;
+ cur_mpsv->data.l.li = cur_mpsv->data.l.li->li_next;
+ break;
+ }
+ case kMPConvPartial: {
+ partial_T *const pt = cur_mpsv->data.p.pt;
+ tv = cur_mpsv->tv;
+ switch (cur_mpsv->data.p.stage) {
+ case kMPConvPartialArgs: {
+ TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(tv,
+ pt == NULL ? 0 : pt->pt_argc);
+ cur_mpsv->data.p.stage = kMPConvPartialSelf;
+ if (pt != NULL && pt->pt_argc > 0) {
+ TYPVAL_ENCODE_CONV_LIST_START(NULL, pt->pt_argc);
+ _mp_push(mpstack, ((MPConvStackVal) {
+ .type = kMPConvPartialList,
+ .tv = NULL,
+ .data = {
+ .a = {
+ .arg = pt->pt_argv,
+ .argv = pt->pt_argv,
+ .todo = (size_t)pt->pt_argc,
+ },
+ },
+ }));
+ }
+ break;
+ }
+ case kMPConvPartialSelf: {
+ cur_mpsv->data.p.stage = kMPConvPartialEnd;
+ dict_T *const dict = pt == NULL ? NULL : pt->pt_dict;
+ if (dict != NULL) {
+ TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, dict->dv_hashtab.ht_used);
+ if (dict->dv_hashtab.ht_used == 0) {
+ TYPVAL_ENCODE_CONV_EMPTY_DICT(NULL, pt->pt_dict);
+ continue;
+ }
+ const int te_csr_ret = _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(
+ TYPVAL_ENCODE_FIRST_ARG_NAME,
+ dict, &dict->dv_copyID, &mpstack, copyID, kMPConvDict,
+ objname);
+ if (te_csr_ret != NOTDONE) {
+ if (te_csr_ret == FAIL) {
+ goto encode_vim_to__error_ret;
+ } else {
+ continue;
+ }
+ }
+ TYPVAL_ENCODE_CONV_DICT_START(NULL, pt->pt_dict,
+ dict->dv_hashtab.ht_used);
+ _mp_push(mpstack, ((MPConvStackVal) {
+ .type = kMPConvDict,
+ .tv = NULL,
+ .data = {
+ .d = {
+ .dict = dict,
+ .dictp = &pt->pt_dict,
+ .hi = dict->dv_hashtab.ht_array,
+ .todo = dict->dv_hashtab.ht_used,
+ },
+ },
+ }));
+ TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(NULL, pt->pt_dict,
+ _mp_last(mpstack));
+ } else {
+ TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, -1);
+ }
+ break;
+ }
+ case kMPConvPartialEnd: {
+ TYPVAL_ENCODE_CONV_FUNC_END(tv);
+ (void)_mp_pop(mpstack);
+ break;
+ }
+ }
+ continue;
+ }
+ case kMPConvPartialList: {
+ if (!cur_mpsv->data.a.todo) {
+ (void)_mp_pop(mpstack);
+ TYPVAL_ENCODE_CONV_LIST_END(NULL);
+ continue;
+ } else if (cur_mpsv->data.a.argv != cur_mpsv->data.a.arg) {
+ TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(NULL);
+ }
+ tv = cur_mpsv->data.a.arg++;
+ cur_mpsv->data.a.todo--;
+ break;
+ }
+ }
+ assert(tv != NULL);
+ if (_TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack,
+ cur_mpsv, tv, copyID, objname)
+ == FAIL) {
+ goto encode_vim_to__error_ret;
+ }
+ }
+ _mp_destroy(mpstack);
+ return OK;
+encode_vim_to__error_ret:
+ _mp_destroy(mpstack);
+ return FAIL;
+ // Prevent “unused label” warnings.
+ goto typval_encode_stop_converting_one_item;
+}
+#endif // NVIM_EVAL_TYPVAL_ENCODE_C_H
diff --git a/src/nvim/eval/typval_encode.h b/src/nvim/eval/typval_encode.h
index b79158b30c..ba325b8f55 100644
--- a/src/nvim/eval/typval_encode.h
+++ b/src/nvim/eval/typval_encode.h
@@ -1,161 +1,35 @@
-/// @file eval/typval_convert.h
+/// @file eval/typval_encode.h
///
-/// Contains set of macros used to convert (possibly recursive) typval_T into
-/// something else. For these macros to work the following macros must be
-/// defined:
-
-/// @def TYPVAL_ENCODE_CONV_NIL
-/// @brief Macros used to convert NIL value
-///
-/// Is called both for special dictionary (unless #TYPVAL_ENCODE_ALLOW_SPECIALS
-/// is false) and `v:null`. Accepts no arguments, but still must be
-/// a function-like macros.
-
-/// @def TYPVAL_ENCODE_CONV_BOOL
-/// @brief Macros used to convert boolean value
-///
-/// Is called both for special dictionary (unless #TYPVAL_ENCODE_ALLOW_SPECIALS
-/// is false) and `v:true`/`v:false`.
-///
-/// @param num Boolean value to convert. Value is an expression which
-/// evaluates to some integer.
-
-/// @def TYPVAL_ENCODE_CONV_NUMBER
-/// @brief Macros used to convert integer
-///
-/// @param num Integer to convert, must accept both varnumber_T and int64_t.
-
-/// @def TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER
-/// @brief Macros used to convert unsigned integer
-///
-/// Not used if #TYPVAL_ENCODE_ALLOW_SPECIALS is false, but still must be
-/// defined.
-///
-/// @param num Integer to convert, must accept uint64_t.
-
-/// @def TYPVAL_ENCODE_CONV_FLOAT
-/// @brief Macros used to convert floating-point number
-///
-/// @param flt Number to convert, must accept float_T.
-
-/// @def TYPVAL_ENCODE_CONV_STRING
-/// @brief Macros used to convert plain string
-///
-/// Is used to convert VAR_STRING objects as well as BIN strings represented as
-/// special dictionary.
-///
-/// @param buf String to convert. Is a char[] buffer, not NUL-terminated.
-/// @param len String length.
-
-/// @def TYPVAL_ENCODE_CONV_STR_STRING
-/// @brief Like #TYPVAL_ENCODE_CONV_STRING, but for STR strings
-///
-/// Is used to convert dictionary keys and STR strings represented as special
-/// dictionaries.
-
-/// @def TYPVAL_ENCODE_CONV_EXT_STRING
-/// @brief Macros used to convert EXT string
-///
-/// Is used to convert EXT strings represented as special dictionaries. Never
-/// actually used if #TYPVAL_ENCODE_ALLOW_SPECIALS is false, but still must be
-/// defined.
-///
-/// @param buf String to convert. Is a char[] buffer, not NUL-terminated.
-/// @param len String length.
-/// @param type EXT type.
-
-/// @def TYPVAL_ENCODE_CONV_FUNC
-/// @brief Macros used to convert a function reference
-///
-/// @param fun Function name.
-
-/// @def TYPVAL_ENCODE_CONV_PARTIAL
-/// @brief Macros used to convert a partial
-///
-/// @param pt Partial name.
-
-/// @def TYPVAL_ENCODE_CONV_EMPTY_LIST
-/// @brief Macros used to convert an empty list
-///
-/// Accepts no arguments, but still must be a function-like macros.
-
-/// @def TYPVAL_ENCODE_CONV_EMPTY_DICT
-/// @brief Macros used to convert an empty dictionary
-///
-/// Accepts no arguments, but still must be a function-like macros.
-
-/// @def TYPVAL_ENCODE_CONV_LIST_START
-/// @brief Macros used before starting to convert non-empty list
-///
-/// @param len List length. Is an expression which evaluates to an integer.
-
-/// @def TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS
-/// @brief Macros used after finishing converting non-last list item
-///
-/// Accepts no arguments, but still must be a function-like macros.
-
-/// @def TYPVAL_ENCODE_CONV_LIST_END
-/// @brief Macros used after converting non-empty list
-///
-/// Accepts no arguments, but still must be a function-like macros.
-
-/// @def TYPVAL_ENCODE_CONV_DICT_START
-/// @brief Macros used before starting to convert non-empty dictionary
-///
-/// @param len Dictionary length. Is an expression which evaluates to an
-/// integer.
-
-/// @def TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK
-/// @brief Macros used to check special dictionary key
-///
-/// @param label Label for goto in case check was not successfull.
-/// @param key typval_T key to check.
-
-/// @def TYPVAL_ENCODE_CONV_DICT_AFTER_KEY
-/// @brief Macros used after finishing converting dictionary key
-///
-/// Accepts no arguments, but still must be a function-like macros.
-
-/// @def TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS
-/// @brief Macros used after finishing converting non-last dictionary value
-///
-/// Accepts no arguments, but still must be a function-like macros.
-
-/// @def TYPVAL_ENCODE_CONV_DICT_END
-/// @brief Macros used after converting non-empty dictionary
-///
-/// Accepts no arguments, but still must be a function-like macros.
-
-/// @def TYPVAL_ENCODE_CONV_RECURSE
-/// @brief Macros used when self-containing container is detected
-///
-/// @param val Container for which this situation was detected.
-/// @param conv_type Type of the stack entry, @see MPConvStackValType.
-
-/// @def TYPVAL_ENCODE_ALLOW_SPECIALS
-/// @brief Macros that specifies whether special dictionaries are special
-///
-/// Must be something that evaluates to boolean, most likely `true` or `false`.
-/// If it is false then special dictionaries are not treated specially.
+/// Contains common definitions for eval/typval_encode.c.h. Most of time should
+/// not be included directly.
#ifndef NVIM_EVAL_TYPVAL_ENCODE_H
#define NVIM_EVAL_TYPVAL_ENCODE_H
#include <stddef.h>
#include <inttypes.h>
+#include <string.h>
#include <assert.h>
#include "nvim/lib/kvec.h"
#include "nvim/eval_defs.h"
-#include "nvim/eval/encode.h"
#include "nvim/func_attr.h"
/// Type of the stack entry
typedef enum {
- kMPConvDict, ///< Convert dict_T *dictionary.
- kMPConvList, ///< Convert list_T *list.
+ kMPConvDict, ///< Convert dict_T *dictionary.
+ kMPConvList, ///< Convert list_T *list.
kMPConvPairs, ///< Convert mapping represented as a list_T* of pairs.
+ kMPConvPartial, ///< Convert partial_T* partial.
+ kMPConvPartialList, ///< Convert argc/argv pair coming from a partial.
} MPConvStackValType;
+/// Stage at which partial is being converted
+typedef enum {
+ kMPConvPartialArgs, ///< About to convert arguments.
+ kMPConvPartialSelf, ///< About to convert self dictionary.
+ kMPConvPartialEnd, ///< Already converted everything.
+} MPConvPartialStage;
+
/// Structure representing current VimL to messagepack conversion state
typedef struct {
MPConvStackValType type; ///< Type of the stack entry.
@@ -163,6 +37,9 @@ typedef struct {
union {
struct {
dict_T *dict; ///< Currently converted dictionary.
+ dict_T **dictp; ///< Location where that dictionary is stored.
+ ///< Normally it is &.tv->vval.v_dict, but not when
+ ///< converting partials.
hashitem_T *hi; ///< Currently converted dictionary item.
size_t todo; ///< Amount of items left to process.
} d; ///< State of dictionary conversion.
@@ -170,6 +47,15 @@ typedef struct {
list_T *list; ///< Currently converted list.
listitem_T *li; ///< Currently converted list item.
} l; ///< State of list or generic mapping conversion.
+ struct {
+ MPConvPartialStage stage; ///< Stage at which partial is being converted.
+ partial_T *pt; ///< Currently converted partial.
+ } p; ///< State of partial conversion.
+ struct {
+ typval_T *arg; ///< Currently converted argument.
+ typval_T *argv; ///< Start of the argument list.
+ size_t todo; ///< Number of items left to process.
+ } a; ///< State of list or generic mapping conversion.
} data; ///< Data to convert.
} MPConvStackVal;
@@ -184,21 +70,9 @@ typedef kvec_withinit_t(MPConvStackVal, 8) MPConvStack;
#define _mp_pop kv_pop
#define _mp_last kv_last
-/// Code for checking whether container references itself
-///
-/// @param[in,out] val Container to check.
-/// @param copyID_attr Name of the container attribute that holds copyID.
-/// After checking whether value of this attribute is
-/// copyID (variable) it is set to copyID.
-/// @param conv_type Type of the conversion, @see MPConvStackValType.
-#define _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(val, copyID_attr, conv_type) \
- do { \
- if ((val)->copyID_attr == copyID) { \
- TYPVAL_ENCODE_CONV_RECURSE((val), conv_type); \
- return OK; \
- } \
- (val)->copyID_attr = copyID; \
- } while (0)
+static inline size_t tv_strlen(const typval_T *const tv)
+ REAL_FATTR_ALWAYS_INLINE REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT
+ REAL_FATTR_NONNULL_ALL;
/// Length of the string stored in typval_T
///
@@ -208,8 +82,6 @@ typedef kvec_withinit_t(MPConvStackVal, 8) MPConvStack;
/// @return Length of the string stored in typval_T, including 0 for NULL
/// string.
static inline size_t tv_strlen(const typval_T *const tv)
- FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
- FUNC_ATTR_NONNULL_ALL
{
assert(tv->v_type == VAR_STRING);
return (tv->vval.v_string == NULL
@@ -217,363 +89,57 @@ static inline size_t tv_strlen(const typval_T *const tv)
: strlen((char *) tv->vval.v_string));
}
-/// Define functions to convert a VimL value:
-/// `{name}_convert_one_value(...)`
-/// `encode_vim_to_{name}(...)`
+/// Code for checking whether container references itself
///
-/// @param scope Scope of the main function: either nothing or `static`.
-/// @param name Name of the target converter.
-/// @param firstargtype Type of the first argument. It will be used to return
-/// the results.
-/// @param firstargname Name of the first argument.
-#define TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(scope, name, firstargtype, \
- firstargname) \
-/* Returns OK or FAIL */ \
-static int name##_convert_one_value(firstargtype firstargname, \
- MPConvStack *const mpstack, \
- typval_T *const tv, \
- const int copyID, \
- const char *const objname) \
- FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT \
-{ \
- switch (tv->v_type) { \
- case VAR_STRING: { \
- TYPVAL_ENCODE_CONV_STRING(tv->vval.v_string, tv_strlen(tv)); \
- break; \
- } \
- case VAR_NUMBER: { \
- TYPVAL_ENCODE_CONV_NUMBER(tv->vval.v_number); \
- break; \
- } \
- case VAR_FLOAT: { \
- TYPVAL_ENCODE_CONV_FLOAT(tv->vval.v_float); \
- break; \
- } \
- case VAR_FUNC: { \
- TYPVAL_ENCODE_CONV_FUNC(tv->vval.v_string); \
- break; \
- } \
- case VAR_PARTIAL: { \
- TYPVAL_ENCODE_CONV_PARTIAL(tv->vval.v_partial); \
- break; \
- } \
- case VAR_LIST: { \
- if (tv->vval.v_list == NULL || tv->vval.v_list->lv_len == 0) { \
- TYPVAL_ENCODE_CONV_EMPTY_LIST(); \
- break; \
- } \
- _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(tv->vval.v_list, lv_copyID, \
- kMPConvList); \
- TYPVAL_ENCODE_CONV_LIST_START(tv->vval.v_list->lv_len); \
- _mp_push(*mpstack, ((MPConvStackVal) { \
- .type = kMPConvList, \
- .tv = tv, \
- .data = { \
- .l = { \
- .list = tv->vval.v_list, \
- .li = tv->vval.v_list->lv_first, \
- }, \
- }, \
- })); \
- break; \
- } \
- case VAR_SPECIAL: { \
- switch (tv->vval.v_special) { \
- case kSpecialVarNull: { \
- TYPVAL_ENCODE_CONV_NIL(); \
- break; \
- } \
- case kSpecialVarTrue: \
- case kSpecialVarFalse: { \
- TYPVAL_ENCODE_CONV_BOOL(tv->vval.v_special == kSpecialVarTrue); \
- break; \
- } \
- } \
- break; \
- } \
- case VAR_DICT: { \
- if (tv->vval.v_dict == NULL \
- || tv->vval.v_dict->dv_hashtab.ht_used == 0) { \
- TYPVAL_ENCODE_CONV_EMPTY_DICT(); \
- break; \
- } \
- const dictitem_T *type_di; \
- const dictitem_T *val_di; \
- if (TYPVAL_ENCODE_ALLOW_SPECIALS \
- && tv->vval.v_dict->dv_hashtab.ht_used == 2 \
- && (type_di = dict_find((dict_T *) tv->vval.v_dict, \
- (char_u *) "_TYPE", -1)) != NULL \
- && type_di->di_tv.v_type == VAR_LIST \
- && (val_di = dict_find((dict_T *) tv->vval.v_dict, \
- (char_u *) "_VAL", -1)) != NULL) { \
- size_t i; \
- for (i = 0; i < ARRAY_SIZE(eval_msgpack_type_lists); i++) { \
- if (type_di->di_tv.vval.v_list == eval_msgpack_type_lists[i]) { \
- break; \
- } \
- } \
- if (i == ARRAY_SIZE(eval_msgpack_type_lists)) { \
- goto name##_convert_one_value_regular_dict; \
- } \
- switch ((MessagePackType) i) { \
- case kMPNil: { \
- TYPVAL_ENCODE_CONV_NIL(); \
- break; \
- } \
- case kMPBoolean: { \
- if (val_di->di_tv.v_type != VAR_NUMBER) { \
- goto name##_convert_one_value_regular_dict; \
- } \
- TYPVAL_ENCODE_CONV_BOOL(val_di->di_tv.vval.v_number); \
- break; \
- } \
- case kMPInteger: { \
- const list_T *val_list; \
- varnumber_T sign; \
- varnumber_T highest_bits; \
- varnumber_T high_bits; \
- varnumber_T low_bits; \
- /* List of 4 integers; first is signed (should be 1 or -1, but */ \
- /* this is not checked), second is unsigned and have at most */ \
- /* one (sign is -1) or two (sign is 1) non-zero bits (number of */ \
- /* bits is not checked), other unsigned and have at most 31 */ \
- /* non-zero bits (number of bits is not checked).*/ \
- if (val_di->di_tv.v_type != VAR_LIST \
- || (val_list = val_di->di_tv.vval.v_list) == NULL \
- || val_list->lv_len != 4 \
- || val_list->lv_first->li_tv.v_type != VAR_NUMBER \
- || (sign = val_list->lv_first->li_tv.vval.v_number) == 0 \
- || val_list->lv_first->li_next->li_tv.v_type != VAR_NUMBER \
- || (highest_bits = \
- val_list->lv_first->li_next->li_tv.vval.v_number) < 0 \
- || val_list->lv_last->li_prev->li_tv.v_type != VAR_NUMBER \
- || (high_bits = \
- val_list->lv_last->li_prev->li_tv.vval.v_number) < 0 \
- || val_list->lv_last->li_tv.v_type != VAR_NUMBER \
- || (low_bits = val_list->lv_last->li_tv.vval.v_number) < 0) { \
- goto name##_convert_one_value_regular_dict; \
- } \
- uint64_t number = ((uint64_t) (((uint64_t) highest_bits) << 62) \
- | (uint64_t) (((uint64_t) high_bits) << 31) \
- | (uint64_t) low_bits); \
- if (sign > 0) { \
- TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(number); \
- } else { \
- TYPVAL_ENCODE_CONV_NUMBER(-number); \
- } \
- break; \
- } \
- case kMPFloat: { \
- if (val_di->di_tv.v_type != VAR_FLOAT) { \
- goto name##_convert_one_value_regular_dict; \
- } \
- TYPVAL_ENCODE_CONV_FLOAT(val_di->di_tv.vval.v_float); \
- break; \
- } \
- case kMPString: \
- case kMPBinary: { \
- const bool is_string = ((MessagePackType) i == kMPString); \
- if (val_di->di_tv.v_type != VAR_LIST) { \
- goto name##_convert_one_value_regular_dict; \
- } \
- size_t len; \
- char *buf; \
- if (!encode_vim_list_to_buf(val_di->di_tv.vval.v_list, &len, \
- &buf)) { \
- goto name##_convert_one_value_regular_dict; \
- } \
- if (is_string) { \
- TYPVAL_ENCODE_CONV_STR_STRING(buf, len); \
- } else { \
- TYPVAL_ENCODE_CONV_STRING(buf, len); \
- } \
- xfree(buf); \
- break; \
- } \
- case kMPArray: { \
- if (val_di->di_tv.v_type != VAR_LIST) { \
- goto name##_convert_one_value_regular_dict; \
- } \
- _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(val_di->di_tv.vval.v_list, \
- lv_copyID, kMPConvList); \
- TYPVAL_ENCODE_CONV_LIST_START(val_di->di_tv.vval.v_list->lv_len); \
- _mp_push(*mpstack, ((MPConvStackVal) { \
- .tv = tv, \
- .type = kMPConvList, \
- .data = { \
- .l = { \
- .list = val_di->di_tv.vval.v_list, \
- .li = val_di->di_tv.vval.v_list->lv_first, \
- }, \
- }, \
- })); \
- break; \
- } \
- case kMPMap: { \
- if (val_di->di_tv.v_type != VAR_LIST) { \
- goto name##_convert_one_value_regular_dict; \
- } \
- list_T *const val_list = val_di->di_tv.vval.v_list; \
- if (val_list == NULL || val_list->lv_len == 0) { \
- TYPVAL_ENCODE_CONV_EMPTY_DICT(); \
- break; \
- } \
- for (const listitem_T *li = val_list->lv_first; li != NULL; \
- li = li->li_next) { \
- if (li->li_tv.v_type != VAR_LIST \
- || li->li_tv.vval.v_list->lv_len != 2) { \
- goto name##_convert_one_value_regular_dict; \
- } \
- } \
- _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(val_list, lv_copyID, \
- kMPConvPairs); \
- TYPVAL_ENCODE_CONV_DICT_START(val_list->lv_len); \
- _mp_push(*mpstack, ((MPConvStackVal) { \
- .tv = tv, \
- .type = kMPConvPairs, \
- .data = { \
- .l = { \
- .list = val_list, \
- .li = val_list->lv_first, \
- }, \
- }, \
- })); \
- break; \
- } \
- case kMPExt: { \
- const list_T *val_list; \
- varnumber_T type; \
- if (val_di->di_tv.v_type != VAR_LIST \
- || (val_list = val_di->di_tv.vval.v_list) == NULL \
- || val_list->lv_len != 2 \
- || (val_list->lv_first->li_tv.v_type != VAR_NUMBER) \
- || (type = val_list->lv_first->li_tv.vval.v_number) > INT8_MAX \
- || type < INT8_MIN \
- || (val_list->lv_last->li_tv.v_type != VAR_LIST)) { \
- goto name##_convert_one_value_regular_dict; \
- } \
- size_t len; \
- char *buf; \
- if (!encode_vim_list_to_buf(val_list->lv_last->li_tv.vval.v_list, \
- &len, &buf)) { \
- goto name##_convert_one_value_regular_dict; \
- } \
- TYPVAL_ENCODE_CONV_EXT_STRING(buf, len, type); \
- xfree(buf); \
- break; \
- } \
- } \
- break; \
- } \
-name##_convert_one_value_regular_dict: \
- _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(tv->vval.v_dict, dv_copyID, \
- kMPConvDict); \
- TYPVAL_ENCODE_CONV_DICT_START(tv->vval.v_dict->dv_hashtab.ht_used); \
- _mp_push(*mpstack, ((MPConvStackVal) { \
- .tv = tv, \
- .type = kMPConvDict, \
- .data = { \
- .d = { \
- .dict = tv->vval.v_dict, \
- .hi = tv->vval.v_dict->dv_hashtab.ht_array, \
- .todo = tv->vval.v_dict->dv_hashtab.ht_used, \
- }, \
- }, \
- })); \
- break; \
- } \
- case VAR_UNKNOWN: { \
- EMSG2(_(e_intern2), #name "_convert_one_value()"); \
- return FAIL; \
- } \
- } \
- return OK; \
-} \
-\
-scope int encode_vim_to_##name(firstargtype firstargname, typval_T *const tv, \
- const char *const objname) \
- FUNC_ATTR_WARN_UNUSED_RESULT \
-{ \
- const int copyID = get_copyID(); \
- MPConvStack mpstack; \
- _mp_init(mpstack); \
- if (name##_convert_one_value(firstargname, &mpstack, tv, copyID, objname) \
- == FAIL) { \
- goto encode_vim_to_##name##_error_ret; \
- } \
- while (_mp_size(mpstack)) { \
- MPConvStackVal *cur_mpsv = &_mp_last(mpstack); \
- typval_T *cur_tv = NULL; \
- switch (cur_mpsv->type) { \
- case kMPConvDict: { \
- if (!cur_mpsv->data.d.todo) { \
- (void) _mp_pop(mpstack); \
- cur_mpsv->data.d.dict->dv_copyID = copyID - 1; \
- TYPVAL_ENCODE_CONV_DICT_END(); \
- continue; \
- } else if (cur_mpsv->data.d.todo \
- != cur_mpsv->data.d.dict->dv_hashtab.ht_used) { \
- TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(); \
- } \
- while (HASHITEM_EMPTY(cur_mpsv->data.d.hi)) { \
- cur_mpsv->data.d.hi++; \
- } \
- dictitem_T *const di = HI2DI(cur_mpsv->data.d.hi); \
- cur_mpsv->data.d.todo--; \
- cur_mpsv->data.d.hi++; \
- TYPVAL_ENCODE_CONV_STR_STRING(&di->di_key[0], \
- strlen((char *) &di->di_key[0])); \
- TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(); \
- cur_tv = &di->di_tv; \
- break; \
- } \
- case kMPConvList: { \
- if (cur_mpsv->data.l.li == NULL) { \
- (void) _mp_pop(mpstack); \
- cur_mpsv->data.l.list->lv_copyID = copyID - 1; \
- TYPVAL_ENCODE_CONV_LIST_END(); \
- continue; \
- } else if (cur_mpsv->data.l.li != cur_mpsv->data.l.list->lv_first) { \
- TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(); \
- } \
- cur_tv = &cur_mpsv->data.l.li->li_tv; \
- cur_mpsv->data.l.li = cur_mpsv->data.l.li->li_next; \
- break; \
- } \
- case kMPConvPairs: { \
- if (cur_mpsv->data.l.li == NULL) { \
- (void) _mp_pop(mpstack); \
- cur_mpsv->data.l.list->lv_copyID = copyID - 1; \
- TYPVAL_ENCODE_CONV_DICT_END(); \
- continue; \
- } else if (cur_mpsv->data.l.li != cur_mpsv->data.l.list->lv_first) { \
- TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(); \
- } \
- const list_T *const kv_pair = cur_mpsv->data.l.li->li_tv.vval.v_list; \
- TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK( \
- encode_vim_to_##name##_error_ret, kv_pair->lv_first->li_tv); \
- if (name##_convert_one_value(firstargname, &mpstack, \
- &kv_pair->lv_first->li_tv, copyID, \
- objname) == FAIL) { \
- goto encode_vim_to_##name##_error_ret; \
- } \
- TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(); \
- cur_tv = &kv_pair->lv_last->li_tv; \
- cur_mpsv->data.l.li = cur_mpsv->data.l.li->li_next; \
- break; \
+/// @param[in,out] val Container to check.
+/// @param copyID_attr Name of the container attribute that holds copyID.
+/// After checking whether value of this attribute is
+/// copyID (variable) it is set to copyID.
+/// @param[in] copyID CopyID used by the caller.
+/// @param conv_type Type of the conversion, @see MPConvStackValType.
+#define _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val, copyID_attr, copyID, \
+ conv_type) \
+ do { \
+ const int te_csr_ret = _TYPVAL_ENCODE_CHECK_SELF_REFERENCE( \
+ TYPVAL_ENCODE_FIRST_ARG_NAME, \
+ (val), &(val)->copyID_attr, mpstack, copyID, conv_type, objname); \
+ if (te_csr_ret != NOTDONE) { \
+ return te_csr_ret; \
} \
- } \
- assert(cur_tv != NULL); \
- if (name##_convert_one_value(firstargname, &mpstack, cur_tv, copyID, \
- objname) == FAIL) { \
- goto encode_vim_to_##name##_error_ret; \
- } \
- } \
- _mp_destroy(mpstack); \
- return OK; \
-encode_vim_to_##name##_error_ret: \
- _mp_destroy(mpstack); \
- return FAIL; \
-}
+ } while (0)
+
+#define _TYPVAL_ENCODE_FUNC_NAME_INNER_2(pref, name, suf) \
+ pref##name##suf
+#define _TYPVAL_ENCODE_FUNC_NAME_INNER(pref, name, suf) \
+ _TYPVAL_ENCODE_FUNC_NAME_INNER_2(pref, name, suf)
+
+/// Construct function name, possibly using macros
+///
+/// Is used to expand macros that may appear in arguments.
+///
+/// @note Expands all arguments, even if only one is needed.
+///
+/// @param[in] pref Prefix.
+/// @param[in] suf Suffix.
+///
+/// @return Concat: pref + #TYPVAL_ENCODE_NAME + suf.
+#define _TYPVAL_ENCODE_FUNC_NAME(pref, suf) \
+ _TYPVAL_ENCODE_FUNC_NAME_INNER(pref, TYPVAL_ENCODE_NAME, suf)
+
+/// Self reference checker function name
+#define _TYPVAL_ENCODE_CHECK_SELF_REFERENCE \
+ _TYPVAL_ENCODE_FUNC_NAME(_typval_encode_, _check_self_reference)
+
+/// Entry point function name
+#define _TYPVAL_ENCODE_ENCODE \
+ _TYPVAL_ENCODE_FUNC_NAME(encode_vim_to_, )
+
+/// Name of the …convert_one_value function
+#define _TYPVAL_ENCODE_CONVERT_ONE_VALUE \
+ _TYPVAL_ENCODE_FUNC_NAME(_typval_encode_, _convert_one_value)
+
+/// Name of the dummy const dict_T *const variable
+#define TYPVAL_ENCODE_NODICT_VAR \
+ _TYPVAL_ENCODE_FUNC_NAME(_typval_encode_, _nodict_var)
#endif // NVIM_EVAL_TYPVAL_ENCODE_H
diff --git a/src/nvim/event/libuv_process.c b/src/nvim/event/libuv_process.c
index a68badcc8f..907187aa17 100644
--- a/src/nvim/event/libuv_process.c
+++ b/src/nvim/event/libuv_process.c
@@ -19,8 +19,7 @@ bool libuv_process_spawn(LibuvProcess *uvproc)
Process *proc = (Process *)uvproc;
uvproc->uvopts.file = proc->argv[0];
uvproc->uvopts.args = proc->argv;
- uvproc->uvopts.flags = UV_PROCESS_WINDOWS_HIDE
- | UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS;
+ uvproc->uvopts.flags = UV_PROCESS_WINDOWS_HIDE;
if (proc->detach) {
uvproc->uvopts.flags |= UV_PROCESS_DETACHED;
}
diff --git a/src/nvim/event/process.c b/src/nvim/event/process.c
index 39dd5fd55a..dc7886469b 100644
--- a/src/nvim/event/process.c
+++ b/src/nvim/event/process.c
@@ -170,8 +170,9 @@ int process_wait(Process *proc, int ms, MultiQueue *events)
int status = -1;
bool interrupted = false;
if (!proc->refcount) {
+ status = proc->status;
LOOP_PROCESS_EVENTS(proc->loop, proc->events, 0);
- return proc->status;
+ return status;
}
if (!events) {
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index fcaf17a0fc..69eed33736 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -1193,8 +1193,8 @@ static void do_filter(
if (do_out) {
if (otmp != NULL) {
- if (readfile(otmp, NULL, line2, (linenr_T)0, (linenr_T)MAXLNUM,
- eap, READ_FILTER) == FAIL) {
+ if (readfile(otmp, NULL, line2, (linenr_T)0, (linenr_T)MAXLNUM, eap,
+ READ_FILTER) != OK) {
if (!aborting()) {
msg_putchar('\n');
EMSG2(_(e_notread), otmp);
@@ -1611,6 +1611,7 @@ int do_write(exarg_T *eap)
int retval = FAIL;
char_u *free_fname = NULL;
buf_T *alt_buf = NULL;
+ int name_was_missing;
if (not_writing()) /* check 'write' option */
return FAIL;
@@ -1734,6 +1735,7 @@ int do_write(exarg_T *eap)
fname = curbuf->b_sfname;
}
+ name_was_missing = curbuf->b_ffname == NULL;
retval = buf_write(curbuf, ffname, fname, eap->line1, eap->line2,
eap, eap->append, eap->forceit, TRUE, FALSE);
@@ -1743,7 +1745,11 @@ int do_write(exarg_T *eap)
curbuf->b_p_ro = FALSE;
redraw_tabline = TRUE;
}
- /* Change directories when the 'acd' option is set. */
+ }
+
+ // Change directories when the 'acd' option is set and the file name
+ // got changed or set.
+ if (eap->cmdidx == CMD_saveas || name_was_missing) {
do_autochdir();
}
}
@@ -6087,7 +6093,6 @@ void ex_substitute(exarg_T *eap)
int save_w_p_cul = curwin->w_p_cul;
int save_w_p_cuc = curwin->w_p_cuc;
- emsg_off++; // No error messages during command preview.
curbuf->b_p_ul = LONG_MAX; // make sure we can undo all changes
curwin->w_p_cul = false; // Disable 'cursorline'
curwin->w_p_cuc = false; // Disable 'cursorcolumn'
@@ -6114,6 +6119,5 @@ void ex_substitute(exarg_T *eap)
restore_search_patterns();
win_size_restore(&save_view);
ga_clear(&save_view);
- emsg_off--;
unblock_autocmds();
}
diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua
index b998b81284..88095602ba 100644
--- a/src/nvim/ex_cmds.lua
+++ b/src/nvim/ex_cmds.lua
@@ -480,6 +480,12 @@ return {
func='ex_close',
},
{
+ command='clearjumps',
+ flags=bit.bor(TRLBAR, CMDWIN),
+ addr_type=ADDR_LINES,
+ func='ex_clearjumps',
+ },
+ {
command='cmap',
flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, USECTRLV, CMDWIN),
addr_type=ADDR_LINES,
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 79a0b5849f..e205901635 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -1374,7 +1374,7 @@ static char_u * do_one_cmd(char_u **cmdlinep,
}
continue;
}
- if (!checkforcmd(&ea.cmd, "noswapfile", 6)) {
+ if (!checkforcmd(&ea.cmd, "noswapfile", 3)) {
break;
}
cmdmod.noswapfile = true;
@@ -1711,7 +1711,7 @@ static char_u * do_one_cmd(char_u **cmdlinep,
xfree(p);
// If the autocommands did something and didn't cause an error, try
// finding the command again.
- p = (ret && !aborting()) ? find_command(&ea, NULL) : NULL;
+ p = (ret && !aborting()) ? find_command(&ea, NULL) : ea.cmd;
}
if (p == NULL) {
@@ -6910,9 +6910,10 @@ static void ex_read(exarg_T *eap)
eap->line2, (linenr_T)0, (linenr_T)MAXLNUM, eap, 0);
}
- if (i == FAIL) {
- if (!aborting())
+ if (i != OK) {
+ if (!aborting()) {
EMSG2(_(e_notopen), eap->arg);
+ }
} else {
if (empty && exmode_active) {
/* Delete the empty line that remains. Historically ex does
@@ -6997,8 +6998,6 @@ void post_chdir(CdScope scope)
shorten_fnames(TRUE);
}
-
-
/// `:cd`, `:tcd`, `:lcd`, `:chdir`, `:tchdir` and `:lchdir`.
void ex_cd(exarg_T *eap)
{
@@ -7040,30 +7039,31 @@ void ex_cd(exarg_T *eap)
new_dir = NameBuff;
}
#endif
- if (vim_chdir(new_dir)) {
- EMSG(_(e_failed));
- } else {
- CdScope scope = kCdScopeGlobal; // Depends on command invoked
+ CdScope scope = kCdScopeGlobal; // Depends on command invoked
- switch (eap->cmdidx) {
- case CMD_tcd:
- case CMD_tchdir:
- scope = kCdScopeTab;
- break;
- case CMD_lcd:
- case CMD_lchdir:
- scope = kCdScopeWindow;
- break;
- default:
- break;
- }
+ switch (eap->cmdidx) {
+ case CMD_tcd:
+ case CMD_tchdir:
+ scope = kCdScopeTab;
+ break;
+ case CMD_lcd:
+ case CMD_lchdir:
+ scope = kCdScopeWindow;
+ break;
+ default:
+ break;
+ }
+ if (vim_chdir(new_dir, scope)) {
+ EMSG(_(e_failed));
+ } else {
post_chdir(scope);
-
- /* Echo the new current directory if the command was typed. */
- if (KeyTyped || p_verbose >= 5)
+ // Echo the new current directory if the command was typed.
+ if (KeyTyped || p_verbose >= 5) {
ex_pwd(eap);
+ }
}
+
xfree(tofree);
}
}
@@ -9674,9 +9674,20 @@ bool cmd_can_preview(char_u *cmd)
if (*ea.cmd == '*') {
ea.cmd = skipwhite(ea.cmd + 1);
}
- find_command(&ea, NULL);
+ char_u *end = find_command(&ea, NULL);
+
+ switch (ea.cmdidx) {
+ case CMD_substitute:
+ case CMD_smagic:
+ case CMD_snomagic:
+ // Only preview once the pattern delimiter has been typed
+ if (*end && !ASCII_ISALNUM(*end)) {
+ return true;
+ }
+ break;
+ default:
+ break;
+ }
- return ea.cmdidx == CMD_substitute
- || ea.cmdidx == CMD_smagic
- || ea.cmdidx == CMD_snomagic;
+ return false;
}
diff --git a/src/nvim/ex_docmd.h b/src/nvim/ex_docmd.h
index fb6aac223f..4def4cbbae 100644
--- a/src/nvim/ex_docmd.h
+++ b/src/nvim/ex_docmd.h
@@ -19,21 +19,6 @@
#define EXMODE_NORMAL 1
#define EXMODE_VIM 2
-/// The scope of a working-directory command like `:cd`.
-///
-/// Scopes are enumerated from lowest to highest. When adding a scope make sure
-/// to update all functions using scopes as well, such as the implementation of
-/// `getcwd()`. When using scopes as limits (e.g. in loops) don't use the scopes
-/// directly, use `MIN_CD_SCOPE` and `MAX_CD_SCOPE` instead.
-typedef enum {
- kCdScopeInvalid = -1,
- kCdScopeWindow, ///< Affects one window.
- kCdScopeTab, ///< Affects one tab page.
- kCdScopeGlobal, ///< Affects the entire instance of Neovim.
-} CdScope;
-#define MIN_CD_SCOPE kCdScopeWindow
-#define MAX_CD_SCOPE kCdScopeGlobal
-
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_docmd.h.generated.h"
#endif
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 7c725179dd..dba7a73814 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -1607,7 +1607,9 @@ static int command_line_changed(CommandLineState *s)
// - Update the screen while the effects are in place.
// - Immediately undo the effects.
State |= CMDPREVIEW;
+ emsg_silent++; // Block error reporting as the command may be incomplete
do_cmdline(ccline.cmdbuff, NULL, NULL, DOCMD_KEEPLINE|DOCMD_NOWAIT);
+ emsg_silent--; // Unblock error reporting
// Restore the window "view".
curwin->w_cursor = s->old_cursor;
diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c
index 2ac8e27047..03cb504f17 100644
--- a/src/nvim/file_search.c
+++ b/src/nvim/file_search.c
@@ -48,6 +48,7 @@
#include <limits.h>
#include "nvim/vim.h"
+#include "nvim/eval.h"
#include "nvim/ascii.h"
#include "nvim/file_search.h"
#include "nvim/charset.h"
@@ -1522,6 +1523,47 @@ theend:
return file_name;
}
+static void do_autocmd_dirchanged(char_u *new_dir, CdScope scope)
+{
+ static bool recursive = false;
+
+ if (recursive || !has_event(EVENT_DIRCHANGED)) {
+ // No autocommand was defined or we changed
+ // the directory from this autocommand.
+ return;
+ }
+
+ recursive = true;
+
+ dict_T *dict = get_vim_var_dict(VV_EVENT);
+ char buf[8];
+
+ switch (scope) {
+ case kCdScopeGlobal:
+ snprintf(buf, sizeof(buf), "global");
+ break;
+ case kCdScopeTab:
+ snprintf(buf, sizeof(buf), "tab");
+ break;
+ case kCdScopeWindow:
+ snprintf(buf, sizeof(buf), "window");
+ break;
+ case kCdScopeInvalid:
+ // Should never happen.
+ assert(false);
+ }
+
+ dict_add_nr_str(dict, "scope", 0L, (char_u *)buf);
+ dict_add_nr_str(dict, "cwd", 0L, new_dir);
+ dict_set_keys_readonly(dict);
+
+ apply_autocmds(EVENT_DIRCHANGED, (char_u *)buf, new_dir, false, NULL);
+
+ dict_clear(dict);
+
+ recursive = false;
+}
+
/// Change to a file's directory.
/// Caller must call shorten_fnames()!
/// @return OK or FAIL
@@ -1531,18 +1573,28 @@ int vim_chdirfile(char_u *fname)
STRLCPY(dir, fname, MAXPATHL);
*path_tail_with_sep(dir) = NUL;
- return os_chdir((char *)dir) == 0 ? OK : FAIL;
+ if (os_chdir((char *)dir) != 0) {
+ return FAIL;
+ }
+ do_autocmd_dirchanged(dir, kCdScopeWindow);
+
+ return OK;
}
/// Change directory to "new_dir". Search 'cdpath' for relative directory names.
-int vim_chdir(char_u *new_dir)
+int vim_chdir(char_u *new_dir, CdScope scope)
{
char_u *dir_name = find_directory_in_path(new_dir, STRLEN(new_dir),
FNAME_MESS, curbuf->b_ffname);
if (dir_name == NULL) {
return -1;
}
+
int r = os_chdir((char *)dir_name);
+ if (r == 0) {
+ do_autocmd_dirchanged(dir_name, scope);
+ }
+
xfree(dir_name);
return r;
}
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index 529b48adbd..13329d771b 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -51,6 +51,7 @@
#include "nvim/window.h"
#include "nvim/shada.h"
#include "nvim/os/os.h"
+#include "nvim/os/os_defs.h"
#include "nvim/os/time.h"
#include "nvim/os/input.h"
@@ -248,7 +249,7 @@ void filemess(buf_T *buf, char_u *name, char_u *s, int attr)
* READ_DUMMY read into a dummy buffer (to check if file contents changed)
* READ_KEEP_UNDO don't clear undo info or read it from a file
*
- * return FAIL for failure, OK otherwise
+ * return FAIL for failure, NOTDONE for directory (failure), or OK
*/
int
readfile (
@@ -257,7 +258,7 @@ readfile (
linenr_T from,
linenr_T lines_to_skip,
linenr_T lines_to_read,
- exarg_T *eap, /* can be NULL! */
+ exarg_T *eap, // can be NULL!
int flags
)
{
@@ -280,7 +281,7 @@ readfile (
int wasempty; /* buffer was empty before reading */
colnr_T len;
long size = 0;
- char_u *p;
+ char_u *p = NULL;
off_t filesize = 0;
int skip_read = FALSE;
context_sha256_T sha_ctx;
@@ -443,13 +444,14 @@ readfile (
// ... or a character special file named /dev/fd/<n>
# endif
) {
- if (S_ISDIR(perm))
+ if (S_ISDIR(perm)) {
filemess(curbuf, fname, (char_u *)_("is a directory"), 0);
- else
+ } else {
filemess(curbuf, fname, (char_u *)_("is not a file"), 0);
+ }
msg_end();
msg_scroll = msg_save;
- return FAIL;
+ return S_ISDIR(perm) ? NOTDONE : FAIL;
}
#endif
}
@@ -1881,16 +1883,20 @@ failed:
xfree(keep_msg);
keep_msg = NULL;
msg_scrolled_ign = TRUE;
- p = msg_trunc_attr(IObuff, FALSE, 0);
+
+ if (!read_stdin && !read_buffer) {
+ p = msg_trunc_attr(IObuff, FALSE, 0);
+ }
+
if (read_stdin || read_buffer || restart_edit != 0
- || (msg_scrolled != 0 && !need_wait_return))
- /* Need to repeat the message after redrawing when:
- * - When reading from stdin (the screen will be cleared next).
- * - When restart_edit is set (otherwise there will be a delay
- * before redrawing).
- * - When the screen was scrolled but there is no wait-return
- * prompt. */
+ || (msg_scrolled != 0 && !need_wait_return)) {
+ // Need to repeat the message after redrawing when:
+ // - When reading from stdin (the screen will be cleared next).
+ // - When restart_edit is set (otherwise there will be a delay before
+ // redrawing).
+ // - When the screen was scrolled but there is no wait-return prompt.
set_keep_msg(p, 0);
+ }
msg_scrolled_ign = FALSE;
}
@@ -5103,13 +5109,13 @@ void buf_reload(buf_T *buf, int orig_mode)
}
if (saved == OK) {
- curbuf->b_flags |= BF_CHECK_RO; /* check for RO again */
- keep_filetype = TRUE; /* don't detect 'filetype' */
- if (readfile(buf->b_ffname, buf->b_fname, (linenr_T)0,
- (linenr_T)0,
- (linenr_T)MAXLNUM, &ea, flags) == FAIL) {
- if (!aborting())
+ curbuf->b_flags |= BF_CHECK_RO; // check for RO again
+ keep_filetype = true; // don't detect 'filetype'
+ if (readfile(buf->b_ffname, buf->b_fname, (linenr_T)0, (linenr_T)0,
+ (linenr_T)MAXLNUM, &ea, flags) != OK) {
+ if (!aborting()) {
EMSG2(_("E321: Could not reload \"%s\""), buf->b_fname);
+ }
if (savebuf != NULL && buf_valid(savebuf) && buf == curbuf) {
/* Put the text back from the save buffer. First
* delete any lines that readfile() added. */
@@ -5224,6 +5230,10 @@ static void vim_maketempdir(void)
// Try the entries in `TEMP_DIR_NAMES` to create the temp directory.
char_u template[TEMP_FILE_PATH_MAXLEN];
char_u path[TEMP_FILE_PATH_MAXLEN];
+
+ // Make sure the umask doesn't remove the executable bit.
+ // "repl" has been reported to use "0177".
+ mode_t umask_save = umask(0077);
for (size_t i = 0; i < ARRAY_SIZE(temp_dirs); i++) {
// Expand environment variables, leave room for "/nvimXXXXXX/999999999"
expand_env((char_u *)temp_dirs[i], template, TEMP_FILE_PATH_MAXLEN - 22);
@@ -5247,6 +5257,7 @@ static void vim_maketempdir(void)
os_rmdir((char *)path);
}
}
+ (void)umask(umask_save);
}
/// Delete "name" and everything in it, recursively.
@@ -5366,6 +5377,8 @@ static AutoPatCmd *active_apc_list = NULL; /* stack of active autocommands */
*/
static garray_T augroups = {0, 0, sizeof(char_u *), 10, NULL};
#define AUGROUP_NAME(i) (((char_u **)augroups.ga_data)[i])
+// use get_deleted_augroup() to get this
+static char_u *deleted_augroup = NULL;
/*
* The ID of the current group. Group 0 is the default one.
@@ -5380,6 +5393,14 @@ static event_T last_event;
static int last_group;
static int autocmd_blocked = 0; /* block all autocmds */
+static char_u *get_deleted_augroup(void)
+{
+ if (deleted_augroup == NULL) {
+ deleted_augroup = (char_u *)_("--Deleted--");
+ }
+ return deleted_augroup;
+}
+
/*
* Show the autocommands for one AutoPat.
*/
@@ -5399,10 +5420,11 @@ static void show_autocmd(AutoPat *ap, event_T event)
return;
if (event != last_event || ap->group != last_group) {
if (ap->group != AUGROUP_DEFAULT) {
- if (AUGROUP_NAME(ap->group) == NULL)
- msg_puts_attr((char_u *)_("--Deleted--"), hl_attr(HLF_E));
- else
+ 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((char_u *)" ");
}
msg_puts_attr(event_nr2name(event), hl_attr(HLF_T));
@@ -5568,11 +5590,33 @@ static void au_del_group(char_u *name)
int i;
i = au_find_group(name);
- if (i == AUGROUP_ERROR) /* the group doesn't exist */
+ if (i == AUGROUP_ERROR) { // the group doesn't exist
EMSG2(_("E367: No such group: \"%s\""), name);
- else {
+ } 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 < (int)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;
+ }
+ }
+ }
xfree(AUGROUP_NAME(i));
- AUGROUP_NAME(i) = NULL;
+ if (in_use) {
+ AUGROUP_NAME(i) = get_deleted_augroup();
+ } else {
+ AUGROUP_NAME(i) = NULL;
+ }
}
}
@@ -5584,8 +5628,9 @@ static void au_del_group(char_u *name)
static int au_find_group(const char_u *name)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
- for (int i = 0; i < augroups.ga_len; ++i) {
- if (AUGROUP_NAME(i) != NULL && STRCMP(AUGROUP_NAME(i), name) == 0) {
+ 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;
}
}
@@ -5633,10 +5678,21 @@ void do_augroup(char_u *arg, int del_group)
#if defined(EXITFREE)
void free_all_autocmds(void)
{
+ int i;
+ char_u *s;
+
for (current_augroup = -1; current_augroup < augroups.ga_len;
- ++current_augroup)
- do_autocmd((char_u *)"", TRUE);
- ga_clear_strings(&augroups);
+ current_augroup++) {
+ do_autocmd((char_u *)"", true);
+ }
+
+ for (i = 0; i < augroups.ga_len; i++) {
+ s = ((char_u **)(augroups.ga_data))[i];
+ if (s != get_deleted_augroup()) {
+ xfree(s);
+ }
+ }
+ ga_clear(&augroups);
}
#endif
@@ -5652,13 +5708,14 @@ static event_T event_name2nr(char_u *start, char_u **end)
int i;
int len;
- /* the event name ends with end of line, a blank or a comma */
- for (p = start; *p && !ascii_iswhite(*p) && *p != ','; ++p)
- ;
- for (i = 0; event_names[i].name != NULL; ++i) {
- len = (int) event_names[i].len;
- if (len == p - start && STRNICMP(event_names[i].name, start, len) == 0)
+ // the event name ends with end of line, '|', a blank or a comma */
+ for (p = start; *p && !ascii_iswhite(*p) && *p != ',' && *p != '|'; p++) {
+ }
+ for (i = 0; event_names[i].name != NULL; i++) {
+ len = (int)event_names[i].len;
+ if (len == p - start && STRNICMP(event_names[i].name, start, len) == 0) {
break;
+ }
}
if (*p == ',')
++p;
@@ -5700,7 +5757,7 @@ find_end_event (
}
pat = arg + 1;
} else {
- for (pat = arg; *pat && !ascii_iswhite(*pat); pat = p) {
+ for (pat = arg; *pat && *pat != '|' && !ascii_iswhite(*pat); pat = p) {
if ((int)event_name2nr(pat, &p) >= (int)NUM_EVENTS) {
if (have_group)
EMSG2(_("E216: No such event: %s"), pat);
@@ -5815,8 +5872,9 @@ void au_event_restore(char_u *old_ei)
*
* Mostly a {group} argument can optionally appear before <event>.
*/
-void do_autocmd(char_u *arg, int forceit)
+void do_autocmd(char_u *arg_in, int forceit)
{
+ char_u *arg = arg_in;
char_u *pat;
char_u *envpat = NULL;
char_u *cmd;
@@ -5825,10 +5883,13 @@ void do_autocmd(char_u *arg, int forceit)
int nested = FALSE;
int group;
- /*
- * Check for a legal group name. If not, use AUGROUP_ALL.
- */
- group = au_get_grouparg(&arg);
+ if (*arg == '|') {
+ arg = (char_u *)"";
+ 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);
+ }
/*
* Scan over the events.
@@ -5838,50 +5899,54 @@ void do_autocmd(char_u *arg, int forceit)
if (pat == NULL)
return;
- /*
- * Scan over the pattern. Put a NUL at the end.
- */
pat = skipwhite(pat);
- cmd = pat;
- while (*cmd && (!ascii_iswhite(*cmd) || cmd[-1] == '\\'))
- cmd++;
- if (*cmd)
- *cmd++ = NUL;
-
- /* Expand environment variables in the pattern. Set 'shellslash', we want
- * forward slashes here. */
- if (vim_strchr(pat, '$') != NULL || vim_strchr(pat, '~') != NULL) {
+ if (*pat == '|') {
+ pat = (char_u *)"";
+ cmd = (char_u *)"";
+ } else {
+ // Scan over the pattern. Put a NUL at the end.
+ cmd = pat;
+ while (*cmd && (!ascii_iswhite(*cmd) || cmd[-1] == '\\')) {
+ cmd++;
+ }
+ if (*cmd) {
+ *cmd++ = NUL;
+ }
+
+ // Expand environment variables in the pattern. Set 'shellslash', we want
+ // forward slashes here.
+ if (vim_strchr(pat, '$') != NULL || vim_strchr(pat, '~') != NULL) {
#ifdef BACKSLASH_IN_FILENAME
- int p_ssl_save = p_ssl;
+ int p_ssl_save = p_ssl;
- p_ssl = TRUE;
+ p_ssl = true;
#endif
- envpat = expand_env_save(pat);
+ envpat = expand_env_save(pat);
#ifdef BACKSLASH_IN_FILENAME
- p_ssl = p_ssl_save;
+ p_ssl = p_ssl_save;
#endif
- if (envpat != NULL)
- pat = envpat;
- }
+ if (envpat != NULL) {
+ pat = envpat;
+ }
+ }
- /*
- * Check for "nested" flag.
- */
- cmd = skipwhite(cmd);
- if (*cmd != NUL && STRNCMP(cmd, "nested", 6) == 0 && ascii_iswhite(cmd[6])) {
- nested = TRUE;
- cmd = skipwhite(cmd + 6);
- }
+ // Check for "nested" flag.
+ cmd = skipwhite(cmd);
+ if (*cmd != NUL && STRNCMP(cmd, "nested", 6) == 0
+ && ascii_iswhite(cmd[6])) {
+ nested = true;
+ cmd = skipwhite(cmd + 6);
+ }
- /*
- * Find the start of the commands.
- * Expand <sfile> in it.
- */
- if (*cmd != NUL) {
- cmd = expand_sfile(cmd);
- if (cmd == NULL) /* some error */
- return;
- need_free = TRUE;
+ // Find the start of the commands.
+ // Expand <sfile> in it.
+ if (*cmd != NUL) {
+ cmd = expand_sfile(cmd);
+ if (cmd == NULL) { // some error
+ return;
+ }
+ need_free = true;
+ }
}
/*
@@ -5895,16 +5960,17 @@ void do_autocmd(char_u *arg, int forceit)
/*
* 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) {
+ last_event = (event_T)-1; // for listing the event name
+ last_group = AUGROUP_ERROR; // for listing the group name
+ if (*arg == '*' || *arg == NUL || *arg == '|') {
for (event = (event_T)0; (int)event < (int)NUM_EVENTS;
- event = (event_T)((int)event + 1))
- if (do_autocmd_event(event, pat,
- nested, cmd, forceit, group) == FAIL)
+ event = (event_T)((int)event + 1)) {
+ if (do_autocmd_event(event, pat, nested, cmd, forceit, group) == FAIL) {
break;
+ }
+ }
} else {
- while (*arg && !ascii_iswhite(*arg)) {
+ while (*arg && *arg != '|' && !ascii_iswhite(*arg)) {
event_T event = event_name2nr(arg, &arg);
assert(event < NUM_EVENTS);
if (do_autocmd_event(event, pat, nested, cmd, forceit, group) == FAIL) {
@@ -5931,7 +5997,8 @@ static int au_get_grouparg(char_u **argp)
char_u *arg = *argp;
int group = AUGROUP_ALL;
- p = skiptowhite(arg);
+ for (p = arg; *p && !ascii_iswhite(*p) && *p != '|'; p++) {
+ }
if (p > arg) {
group_name = vim_strnsave(arg, (int)(p - arg));
group = au_find_group(group_name);
@@ -6698,8 +6765,9 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io,
fname = vim_strsave(fname); /* make a copy, so we can change it */
} else {
sfname = vim_strsave(fname);
- // don't try expanding the following events
+ // Don't try expanding the following events.
if (event == EVENT_COLORSCHEME
+ || event == EVENT_DIRCHANGED
|| event == EVENT_FILETYPE
|| event == EVENT_FUNCUNDEFINED
|| event == EVENT_OPTIONSET
@@ -6708,10 +6776,11 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io,
|| event == EVENT_REMOTEREPLY
|| event == EVENT_SPELLFILEMISSING
|| event == EVENT_SYNTAX
- || event == EVENT_TABCLOSED)
+ || event == EVENT_TABCLOSED) {
fname = vim_strsave(fname);
- else
- fname = (char_u *)FullName_save((char *)fname, FALSE);
+ } else {
+ fname = (char_u *)FullName_save((char *)fname, false);
+ }
}
if (fname == NULL) { /* out of memory */
xfree(sfname);
@@ -7087,9 +7156,11 @@ char_u *get_augroup_name(expand_T *xp, int idx)
return (char_u *)"END";
if (idx >= augroups.ga_len) /* end of list */
return NULL;
- if (AUGROUP_NAME(idx) == NULL) /* skip deleted entries */
+ if (AUGROUP_NAME(idx) == NULL || AUGROUP_NAME(idx) == get_deleted_augroup()) {
+ // skip deleted entries
return (char_u *)"";
- return AUGROUP_NAME(idx); /* return a name */
+ }
+ return AUGROUP_NAME(idx); // return a name
}
static int include_groups = FALSE;
@@ -7146,10 +7217,12 @@ set_context_in_autocmd (
*/
char_u *get_event_name(expand_T *xp, int idx)
{
- if (idx < augroups.ga_len) { /* First list group names, if wanted */
- if (!include_groups || AUGROUP_NAME(idx) == NULL)
- return (char_u *)""; /* skip deleted entries */
- return AUGROUP_NAME(idx); /* return a name */
+ if (idx < augroups.ga_len) { // First list group names, if wanted
+ if (!include_groups || AUGROUP_NAME(idx) == NULL
+ || AUGROUP_NAME(idx) == get_deleted_augroup()) {
+ return (char_u *)""; // skip deleted entries
+ }
+ return AUGROUP_NAME(idx); // return a name
}
return (char_u *)event_names[idx - augroups.ga_len].name;
}
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index 872ff8d5b8..e3c84cb852 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -409,10 +409,6 @@ EXTERN struct caller_scope {
} provider_caller_scope;
EXTERN int provider_call_nesting INIT(= 0);
-/* Magic number used for hashitem "hi_key" value indicating a deleted item.
- * Only the address is used. */
-EXTERN char_u hash_removed;
-
EXTERN int t_colors INIT(= 256); // int value of T_CCO
@@ -636,10 +632,6 @@ EXTERN int exiting INIT(= FALSE);
/* TRUE when planning to exit Vim. Might
* still keep on running if there is a changed
* buffer. */
-#if defined(EXITFREE)
-// true when in or after free_all_mem()
-EXTERN bool entered_free_all_mem INIT(= false);
-#endif
// volatile because it is used in signal handler deathtrap().
EXTERN volatile int full_screen INIT(= false);
// TRUE when doing full-screen output
@@ -877,9 +869,10 @@ EXTERN int mapped_ctrl_c INIT(= 0); // Modes where CTRL-C is mapped.
EXTERN cmdmod_T cmdmod; /* Ex command modifiers */
-EXTERN int msg_silent INIT(= 0); /* don't print messages */
-EXTERN int emsg_silent INIT(= 0); /* don't print error messages */
-EXTERN int cmd_silent INIT(= FALSE); /* don't echo the command line */
+EXTERN int msg_silent INIT(= 0); // don't print messages
+EXTERN int emsg_silent INIT(= 0); // don't print error messages
+EXTERN bool emsg_noredir INIT(= false); // don't redirect error messages
+EXTERN int cmd_silent INIT(= false); // don't echo the command line
/* Values for swap_exists_action: what to do when swap file already exists */
#define SEA_NONE 0 /* don't use dialog */
@@ -1256,4 +1249,20 @@ typedef enum {
kBroken
} WorkingStatus;
+/// The scope of a working-directory command like `:cd`.
+///
+/// Scopes are enumerated from lowest to highest. When adding a scope make sure
+/// to update all functions using scopes as well, such as the implementation of
+/// `getcwd()`. When using scopes as limits (e.g. in loops) don't use the scopes
+/// directly, use `MIN_CD_SCOPE` and `MAX_CD_SCOPE` instead.
+typedef enum {
+ kCdScopeInvalid = -1,
+ kCdScopeWindow, ///< Affects one window.
+ kCdScopeTab, ///< Affects one tab page.
+ kCdScopeGlobal, ///< Affects the entire instance of Neovim.
+} CdScope;
+
+#define MIN_CD_SCOPE kCdScopeWindow
+#define MAX_CD_SCOPE kCdScopeGlobal
+
#endif /* NVIM_GLOBALS_H */
diff --git a/src/nvim/hashtab.c b/src/nvim/hashtab.c
index fa4077f22f..376f33e23e 100644
--- a/src/nvim/hashtab.c
+++ b/src/nvim/hashtab.c
@@ -36,6 +36,8 @@
# include "hashtab.c.generated.h"
#endif
+char hash_removed;
+
/// Initialize an empty hash table.
void hash_init(hashtab_T *ht)
{
@@ -380,3 +382,13 @@ hash_T hash_hash(char_u *key)
return hash;
}
+
+/// Function to get HI_KEY_REMOVED value
+///
+/// Used for testing because luajit ffi does not allow getting addresses of
+/// globals.
+const char_u *_hash_key_removed(void)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ return HI_KEY_REMOVED;
+}
diff --git a/src/nvim/hashtab.h b/src/nvim/hashtab.h
index 7233d8c47c..0da2b13f2e 100644
--- a/src/nvim/hashtab.h
+++ b/src/nvim/hashtab.h
@@ -5,14 +5,19 @@
#include "nvim/types.h"
+/// Magic number used for hashitem "hi_key" value indicating a deleted item
+///
+/// Only the address is used.
+extern char hash_removed;
+
/// Type for hash number (hash calculation result).
typedef size_t hash_T;
/// The address of "hash_removed" is used as a magic number
/// for hi_key to indicate a removed item.
-#define HI_KEY_REMOVED &hash_removed
+#define HI_KEY_REMOVED ((char_u *)&hash_removed)
#define HASHITEM_EMPTY(hi) ((hi)->hi_key == NULL \
- || (hi)->hi_key == &hash_removed)
+ || (hi)->hi_key == (char_u *)&hash_removed)
/// A hastable item.
///
diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c
index 99e94fc60f..94bbaf4239 100644
--- a/src/nvim/keymap.c
+++ b/src/nvim/keymap.c
@@ -573,8 +573,10 @@ int find_special_key(const char_u **srcp, const size_t src_len, int *const modp,
} else {
l = 1;
}
- if (end - bp > l && bp[l + 1] == '>') {
- bp += l; // anything accepted, like <C-?>
+ if (end - bp > l && bp[l] != '"' && bp[l + 1] == '>') {
+ // Anything accepted, like <C-?>, except <C-">, because the "
+ // ends the string.
+ bp += l;
}
}
}
diff --git a/src/nvim/mark.c b/src/nvim/mark.c
index 6453c41415..4e05845eb5 100644
--- a/src/nvim/mark.c
+++ b/src/nvim/mark.c
@@ -130,17 +130,23 @@ int setmark_pos(int c, pos_T *pos, int fnum)
return OK;
}
- if (c > 'z') /* some islower() and isupper() cannot handle
- characters above 127 */
+ buf_T *buf = buflist_findnr(fnum);
+ // Can't set a mark in a non-existant buffer.
+ if (buf == NULL) {
return FAIL;
- if (islower(c)) {
+ }
+
+ if (ASCII_ISLOWER(c)) {
i = c - 'a';
- RESET_FMARK(curbuf->b_namedm + i, *pos, curbuf->b_fnum);
+ RESET_FMARK(buf->b_namedm + i, *pos, fnum);
return OK;
}
- if (isupper(c)) {
- assert(c >= 'A' && c <= 'Z');
- i = c - 'A';
+ if (ASCII_ISUPPER(c) || ascii_isdigit(c)) {
+ if (ascii_isdigit(c)) {
+ i = c - '0' + NMARKS;
+ } else {
+ i = c - 'A';
+ }
RESET_XFMARK(namedfm + i, *pos, fnum, NULL);
return OK;
}
@@ -798,6 +804,13 @@ void ex_jumps(exarg_T *eap)
MSG_PUTS("\n>");
}
+void ex_clearjumps(exarg_T *eap)
+{
+ free_jumplist(curwin);
+ curwin->w_jumplistlen = 0;
+ curwin->w_jumplistidx = 0;
+}
+
/*
* print the changelist
*/
diff --git a/src/nvim/memline.c b/src/nvim/memline.c
index a9a53ebca7..b8891f6560 100644
--- a/src/nvim/memline.c
+++ b/src/nvim/memline.c
@@ -1063,11 +1063,12 @@ void ml_recover(void)
if (!cannot_open) {
line_count = pp->pb_pointer[idx].pe_line_count;
if (readfile(curbuf->b_ffname, NULL, lnum,
- pp->pb_pointer[idx].pe_old_lnum - 1,
- line_count, NULL, 0) == FAIL)
- cannot_open = TRUE;
- else
+ pp->pb_pointer[idx].pe_old_lnum - 1, line_count,
+ NULL, 0) != OK) {
+ cannot_open = true;
+ } else {
lnum += line_count;
+ }
}
if (cannot_open) {
++error;
diff --git a/src/nvim/memory.c b/src/nvim/memory.c
index 1884d55999..92ead873ae 100644
--- a/src/nvim/memory.c
+++ b/src/nvim/memory.c
@@ -17,16 +17,41 @@
// Force je_ prefix on jemalloc functions.
# define JEMALLOC_NO_DEMANGLE
# include <jemalloc/jemalloc.h>
-# define malloc(size) je_malloc(size)
-# define calloc(count, size) je_calloc(count, size)
-# define realloc(ptr, size) je_realloc(ptr, size)
-# define free(ptr) je_free(ptr)
+#endif
+
+#ifdef UNIT_TESTING
+# define malloc(size) mem_malloc(size)
+# define calloc(count, size) mem_calloc(count, size)
+# define realloc(ptr, size) mem_realloc(ptr, size)
+# define free(ptr) mem_free(ptr)
+# ifdef HAVE_JEMALLOC
+MemMalloc mem_malloc = &je_malloc;
+MemFree mem_free = &je_free;
+MemCalloc mem_calloc = &je_calloc;
+MemRealloc mem_realloc = &je_realloc;
+# else
+MemMalloc mem_malloc = &malloc;
+MemFree mem_free = &free;
+MemCalloc mem_calloc = &calloc;
+MemRealloc mem_realloc = &realloc;
+# endif
+#else
+# ifdef HAVE_JEMALLOC
+# define malloc(size) je_malloc(size)
+# define calloc(count, size) je_calloc(count, size)
+# define realloc(ptr, size) je_realloc(ptr, size)
+# define free(ptr) je_free(ptr)
+# endif
#endif
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "memory.c.generated.h"
#endif
+#ifdef EXITFREE
+bool entered_free_all_mem = false;
+#endif
+
/// Try to free memory. Used when trying to recover from out of memory errors.
/// @see {xmalloc}
void try_to_free_memory(void)
@@ -353,15 +378,15 @@ char *xstpncpy(char *restrict dst, const char *restrict src, size_t maxlen)
size_t xstrlcpy(char *restrict dst, const char *restrict src, size_t size)
FUNC_ATTR_NONNULL_ALL
{
- size_t ret = strlen(src);
+ size_t ret = strlen(src);
- if (size) {
- size_t len = (ret >= size) ? size - 1 : ret;
- memcpy(dst, src, len);
- dst[len] = '\0';
- }
+ if (size) {
+ size_t len = (ret >= size) ? size - 1 : ret;
+ memcpy(dst, src, len);
+ dst[len] = '\0';
+ }
- return ret;
+ return ret;
}
/// strdup() wrapper
@@ -371,6 +396,7 @@ size_t xstrlcpy(char *restrict dst, const char *restrict src, size_t size)
/// @return pointer to a copy of the string
char *xstrdup(const char *str)
FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET
+ FUNC_ATTR_NONNULL_ALL
{
return xmemdupz(str, strlen(str));
}
@@ -401,6 +427,7 @@ void *xmemrchr(const void *src, uint8_t c, size_t len)
/// @return pointer to a copy of the string
char *xstrndup(const char *str, size_t len)
FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET
+ FUNC_ATTR_NONNULL_ALL
{
char *p = memchr(str, '\0', len);
return xmemdupz(str, p ? (size_t)(p - str) : len);
diff --git a/src/nvim/memory.h b/src/nvim/memory.h
index 62cc78360c..250ac3e08f 100644
--- a/src/nvim/memory.h
+++ b/src/nvim/memory.h
@@ -1,9 +1,41 @@
#ifndef NVIM_MEMORY_H
#define NVIM_MEMORY_H
+#include <stdbool.h> // for bool
#include <stdint.h> // for uint8_t
#include <stddef.h> // for size_t
-#include <time.h> // for time_t
+#include <time.h> // for time_t
+
+/// `malloc()` function signature
+typedef void *(*MemMalloc)(size_t);
+
+/// `free()` function signature
+typedef void (*MemFree)(void *);
+
+/// `calloc()` function signature
+typedef void *(*MemCalloc)(size_t, size_t);
+
+/// `realloc()` function signature
+typedef void *(*MemRealloc)(void *, size_t);
+
+#ifdef UNIT_TESTING
+/// When unit testing: pointer to the `malloc()` function, may be altered
+extern MemMalloc mem_malloc;
+
+/// When unit testing: pointer to the `free()` function, may be altered
+extern MemFree mem_free;
+
+/// When unit testing: pointer to the `calloc()` function, may be altered
+extern MemCalloc mem_calloc;
+
+/// When unit testing: pointer to the `realloc()` function, may be altered
+extern MemRealloc mem_realloc;
+#endif
+
+#ifdef EXITFREE
+/// Indicates that free_all_mem function was or is running
+extern bool entered_free_all_mem;
+#endif
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "memory.h.generated.h"
diff --git a/src/nvim/message.c b/src/nvim/message.c
index 637b89ccbe..2f8feda6ec 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -508,20 +508,22 @@ int emsg(char_u *s)
* But do write it to the redirection file.
*/
if (emsg_silent != 0) {
- msg_start();
- p = get_emsg_source();
- if (p != NULL) {
- STRCAT(p, "\n");
- redir_write(p, STRLEN(p));
- xfree(p);
- }
- p = get_emsg_lnum();
- if (p != NULL) {
- STRCAT(p, "\n");
- redir_write(p, STRLEN(p));
- xfree(p);
+ if (!emsg_noredir) {
+ msg_start();
+ p = get_emsg_source();
+ if (p != NULL) {
+ STRCAT(p, "\n");
+ redir_write(p, STRLEN(p));
+ xfree(p);
+ }
+ p = get_emsg_lnum();
+ if (p != NULL) {
+ STRCAT(p, "\n");
+ redir_write(p, STRLEN(p));
+ xfree(p);
+ }
+ redir_write(s, STRLEN(s));
}
- redir_write(s, STRLEN(s));
return true;
}
@@ -2508,8 +2510,7 @@ static void redir_write(char_u *str, int maxlen)
int redirecting(void)
{
return redir_fd != NULL || *p_vfile != NUL
- || redir_reg || redir_vname
- ;
+ || redir_reg || redir_vname || capture_ga != NULL;
}
/*
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index 907353c271..10d6be85f8 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -1404,7 +1404,7 @@ int op_delete(oparg_T *oap)
if (oap->regname == 0) {
set_clipboard(0, reg);
- yank_do_autocmd(oap, reg);
+ do_autocmd_textyankpost(oap, reg);
}
}
@@ -2315,7 +2315,7 @@ bool op_yank(oparg_T *oap, bool message)
yankreg_T *reg = get_yank_register(oap->regname, YREG_YANK);
op_yank_reg(oap, message, reg, is_append_register(oap->regname));
set_clipboard(oap->regname, reg);
- yank_do_autocmd(oap, reg);
+ do_autocmd_textyankpost(oap, reg);
return true;
}
@@ -2538,7 +2538,7 @@ static void yank_copy_line(yankreg_T *reg, struct block_def *bd, size_t y_idx)
///
/// @param oap Operator arguments.
/// @param reg The yank register used.
-static void yank_do_autocmd(oparg_T *oap, yankreg_T *reg)
+static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg)
FUNC_ATTR_NONNULL_ALL
{
static bool recursive = false;
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 52a8b19ca4..a4e7da770e 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -213,12 +213,12 @@ typedef struct vimoption {
#define P_VI_DEF 0x400U /* Use Vi default for Vim */
#define P_VIM 0x800U /* Vim option */
-/* when option changed, what to display: */
-#define P_RSTAT 0x1000U /* redraw status lines */
-#define P_RWIN 0x2000U /* redraw current window */
-#define P_RBUF 0x4000U /* redraw current buffer */
-#define P_RALL 0x6000U /* redraw all windows */
-#define P_RCLR 0x7000U /* clear and redraw all */
+// when option changed, what to display:
+#define P_RSTAT 0x1000U ///< redraw status lines
+#define P_RWIN 0x2000U ///< redraw current window and recompute text
+#define P_RBUF 0x4000U ///< redraw current buffer and recompute text
+#define P_RALL 0x6000U ///< redraw all windows
+#define P_RCLR 0x7000U ///< clear and redraw all
#define P_COMMA 0x8000U ///< comma separated list
#define P_ONECOMMA 0x18000U ///< P_COMMA and cannot have two consecutive
@@ -238,6 +238,8 @@ typedef struct vimoption {
///< when there is a redraw flag
#define P_NO_DEF_EXP 0x8000000U ///< Do not expand default value.
+#define P_RWINONLY 0x10000000U ///< only redraw current window
+
#define HIGHLIGHT_INIT \
"8:SpecialKey,~:EndOfBuffer,z:TermCursor,Z:TermCursorNC,@:NonText," \
"d:Directory,e:ErrorMsg,i:IncSearch,l:Search,m:MoreMsg,M:ModeMsg,n:LineNr," \
@@ -4357,17 +4359,24 @@ static void check_redraw(uint32_t flags)
bool doclear = (flags & P_RCLR) == P_RCLR;
bool all = ((flags & P_RALL) == P_RALL || doclear);
- if ((flags & P_RSTAT) || all) /* mark all status lines dirty */
+ if ((flags & P_RSTAT) || all) { // mark all status lines dirty
status_redraw_all();
+ }
- if ((flags & P_RBUF) || (flags & P_RWIN) || all)
+ if ((flags & P_RBUF) || (flags & P_RWIN) || all) {
changed_window_setting();
- if (flags & P_RBUF)
+ }
+ if (flags & P_RBUF) {
redraw_curbuf_later(NOT_VALID);
- if (doclear)
+ }
+ if (flags & P_RWINONLY) {
+ redraw_later(NOT_VALID);
+ }
+ if (doclear) {
redraw_all_later(CLEAR);
- else if (all)
+ } else if (all) {
redraw_all_later(NOT_VALID);
+ }
}
/// Find index for named option
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index 70d1f73cf4..859658e40d 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -17,8 +17,8 @@
-- types: bool, number, string
-- lists: (nil), comma, onecomma, flags, flagscomma
-- scopes: global, buffer, window
--- redraw options: statuslines, current_window, current_buffer, all_windows,
--- everything, curswant
+-- redraw options: statuslines, current_window, curent_window_only,
+-- current_buffer, all_windows, everything, curswant
-- default: {vi=…[, vim=…]}
-- defaults: {condition=#if condition, if_true=default, if_false=default}
-- #if condition:
@@ -539,7 +539,7 @@ return {
full_name='cursorline', abbreviation='cul',
type='bool', scope={'window'},
vi_def=true,
- redraw={'current_window'},
+ redraw={'current_window_only'},
defaults={if_true={vi=false}}
},
{
diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c
index 83a0262545..47d541c6a7 100644
--- a/src/nvim/os/env.c
+++ b/src/nvim/os/env.c
@@ -560,8 +560,8 @@ const void *vim_colon_env_iter_rev(const char *const val,
/// Vim's version of getenv().
/// Special handling of $HOME, $VIM and $VIMRUNTIME, allowing the user to
/// override the vim runtime directory at runtime. Also does ACP to 'enc'
-/// conversion for Win32. Results must be freed by the calling function.
-/// @param name Name of environment variable to expand
+/// conversion for Win32. Result must be freed by the caller.
+/// @param name Environment variable to expand
char *vim_getenv(const char *name)
{
const char *kos_env_path = os_getenv(name);
@@ -598,6 +598,27 @@ char *vim_getenv(const char *name)
if (p_hf != NULL && vim_strchr(p_hf, '$') == NULL) {
vim_path = (char *)p_hf;
}
+
+#ifdef WIN32
+ // Find runtime path relative to the nvim binary i.e. ../share/runtime
+ if (vim_path == NULL) {
+ char exe_name[MAXPATHL];
+ size_t exe_name_len = MAXPATHL;
+ if (os_exepath(exe_name, &exe_name_len) == 0) {
+ char *path_end = (char *)path_tail_with_sep((char_u *)exe_name);
+ *path_end = '\0'; // remove the trailing "nvim.exe"
+ path_end = (char *)path_tail((char_u *)exe_name);
+ *path_end = '\0'; // remove the trailing "bin/"
+ if (append_path(
+ exe_name,
+ "share" _PATHSEPSTR "nvim" _PATHSEPSTR "runtime" _PATHSEPSTR,
+ MAXPATHL) == OK) {
+ vim_path = exe_name;
+ }
+ }
+ }
+#endif
+
if (vim_path != NULL) {
// remove the file name
char *vim_path_end = (char *)path_tail((char_u *)vim_path);
diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c
index 3c821936e9..4aa727733e 100644
--- a/src/nvim/os/fs.c
+++ b/src/nvim/os/fs.c
@@ -192,6 +192,18 @@ int os_nodetype(const char *name)
return nodetype;
}
+/// Gets the absolute path of the currently running executable.
+///
+/// @param[out] buffer Returns the path string.
+/// @param[in] size Size of `buffer`.
+///
+/// @return `0` on success, or libuv error code on failure.
+int os_exepath(char *buffer, size_t *size)
+ FUNC_ATTR_NONNULL_ALL
+{
+ return uv_exepath(buffer, size);
+}
+
/// Checks if the given path represents an executable file.
///
/// @param[in] name Name of the executable.
diff --git a/src/nvim/os/stdpaths.c b/src/nvim/os/stdpaths.c
index 10b3f4c091..afb9bdec31 100644
--- a/src/nvim/os/stdpaths.c
+++ b/src/nvim/os/stdpaths.c
@@ -16,20 +16,29 @@ static const char *xdg_env_vars[] = {
[kXDGDataDirs] = "XDG_DATA_DIRS",
};
+#ifdef WIN32
+static const char *const xdg_defaults_env_vars[] = {
+ [kXDGConfigHome] = "LOCALAPPDATA",
+ [kXDGDataHome] = "LOCALAPPDATA",
+ [kXDGCacheHome] = "TEMP",
+ [kXDGRuntimeDir] = NULL,
+ [kXDGConfigDirs] = NULL,
+ [kXDGDataDirs] = NULL,
+};
+#endif
+
/// Defaults for XDGVarType values
///
/// Used in case environment variables contain nothing. Need to be expanded.
static const char *const xdg_defaults[] = {
#ifdef WIN32
- // Windows
- [kXDGConfigHome] = "$LOCALAPPDATA",
- [kXDGDataHome] = "$LOCALAPPDATA",
- [kXDGCacheHome] = "$TEMP",
+ [kXDGConfigHome] = "~\\AppData\\Local",
+ [kXDGDataHome] = "~\\AppData\\Local",
+ [kXDGCacheHome] = "~\\AppData\\Local\\Temp",
[kXDGRuntimeDir] = NULL,
[kXDGConfigDirs] = NULL,
[kXDGDataDirs] = NULL,
#else
- // Linux, BSD, CYGWIN, Apple
[kXDGConfigHome] = "~/.config",
[kXDGDataHome] = "~/.local/share",
[kXDGCacheHome] = "~/.cache",
@@ -50,7 +59,14 @@ char *stdpaths_get_xdg_var(const XDGVarType idx)
const char *const env = xdg_env_vars[idx];
const char *const fallback = xdg_defaults[idx];
- const char *const env_val = os_getenv(env);
+ const char *env_val = os_getenv(env);
+
+#ifdef WIN32
+ if (env_val == NULL) {
+ env_val = os_getenv(xdg_defaults_env_vars[idx]);
+ }
+#endif
+
char *ret = NULL;
if (env_val != NULL) {
ret = xstrdup(env_val);
diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c
index 2205ad0958..8ce2ecf4f4 100644
--- a/src/nvim/os/time.c
+++ b/src/nvim/os/time.c
@@ -7,6 +7,7 @@
#include <uv.h>
#include "nvim/os/time.h"
+#include "nvim/os/input.h"
#include "nvim/event/loop.h"
#include "nvim/vim.h"
#include "nvim/main.h"
@@ -34,10 +35,10 @@ uint64_t os_hrtime(void)
return uv_hrtime();
}
-/// Sleeps for a certain amount of milliseconds
+/// Sleeps for a certain amount of milliseconds.
///
/// @param milliseconds Number of milliseconds to sleep
-/// @param ignoreinput If true, allow a SIGINT to interrupt us
+/// @param ignoreinput If true, only SIGINT (CTRL-C) can interrupt.
void os_delay(uint64_t milliseconds, bool ignoreinput)
{
if (ignoreinput) {
@@ -46,26 +47,42 @@ void os_delay(uint64_t milliseconds, bool ignoreinput)
}
LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, (int)milliseconds, got_int);
} else {
- os_microdelay(milliseconds * 1000);
+ os_microdelay(milliseconds * 1000u, ignoreinput);
}
}
-/// Sleeps for a certain amount of microseconds
+/// Sleeps for a certain amount of microseconds.
///
-/// @param microseconds Number of microseconds to sleep
-void os_microdelay(uint64_t microseconds)
+/// @param ms Number of microseconds to sleep.
+/// @param ignoreinput If true, ignore all input (including SIGINT/CTRL-C).
+/// If false, waiting is aborted on any input.
+void os_microdelay(uint64_t ms, bool ignoreinput)
{
- uint64_t elapsed = 0;
- uint64_t ns = microseconds * 1000; // convert to nanoseconds
+ uint64_t elapsed = 0u;
uint64_t base = uv_hrtime();
+ // Convert microseconds to nanoseconds, or UINT64_MAX on overflow.
+ const uint64_t ns = (ms < UINT64_MAX / 1000u) ? ms * 1000u : UINT64_MAX;
uv_mutex_lock(&delay_mutex);
while (elapsed < ns) {
- if (uv_cond_timedwait(&delay_cond, &delay_mutex, ns - elapsed)
- == UV_ETIMEDOUT)
+ // If ignoring input, we simply wait the full delay.
+ // Else we check for input in ~100ms intervals.
+ const uint64_t ns_delta = ignoreinput
+ ? ns - elapsed
+ : MIN(ns - elapsed, 100000000u); // 100ms
+
+ const int rv = uv_cond_timedwait(&delay_cond, &delay_mutex, ns_delta);
+ if (0 != rv && UV_ETIMEDOUT != rv) {
+ assert(false);
+ break;
+ } // Else: Timeout proceeded normally.
+
+ if (!ignoreinput && os_char_avail()) {
break;
- uint64_t now = uv_hrtime();
+ }
+
+ const uint64_t now = uv_hrtime();
elapsed += now - base;
base = now;
}
diff --git a/src/nvim/path.c b/src/nvim/path.c
index 6149e1ab99..3d1def8dd4 100644
--- a/src/nvim/path.c
+++ b/src/nvim/path.c
@@ -417,15 +417,11 @@ char *FullName_save(char *fname, bool force)
}
char *buf = xmalloc(MAXPATHL);
- char *new_fname = NULL;
- if (vim_FullName(fname, buf, MAXPATHL, force) != FAIL) {
- new_fname = xstrdup(buf);
- } else {
- new_fname = xstrdup(fname);
+ if (vim_FullName(fname, buf, MAXPATHL, force) == FAIL) {
+ xfree(buf);
+ return xstrdup(fname);
}
- xfree(buf);
-
- return new_fname;
+ return buf;
}
/// Saves the absolute path.
@@ -1649,30 +1645,37 @@ bool vim_isAbsName(char_u *name)
/// Save absolute file name to "buf[len]".
///
-/// @param fname is the filename to evaluate
-/// @param[out] buf is the buffer for returning the absolute path for `fname`
-/// @param len is the length of `buf`
-/// @param force is a flag to force expanding even if the path is absolute
+/// @param fname filename to evaluate
+/// @param[out] buf contains `fname` absolute path, or:
+/// - truncated `fname` if longer than `len`
+/// - unmodified `fname` if absolute path fails or is a URL
+/// @param len length of `buf`
+/// @param force flag to force expanding even if the path is absolute
///
/// @return FAIL for failure, OK otherwise
int vim_FullName(const char *fname, char *buf, size_t len, bool force)
FUNC_ATTR_NONNULL_ARG(2)
{
- int retval = OK;
- int url;
-
*buf = NUL;
- if (fname == NULL)
+ if (fname == NULL) {
return FAIL;
+ }
- url = path_with_url(fname);
- if (!url)
- retval = path_get_absolute_path((char_u *)fname, (char_u *)buf, len, force);
- if (url || retval == FAIL) {
- /* something failed; use the file name (truncate when too long) */
+ if (strlen(fname) > (len - 1)) {
+ xstrlcpy(buf, fname, len); // truncate
+ return FAIL;
+ }
+
+ if (path_with_url(fname)) {
xstrlcpy(buf, fname, len);
+ return OK;
}
- return retval;
+
+ int rv = path_get_absolute_path((char_u *)fname, (char_u *)buf, len, force);
+ if (rv == FAIL) {
+ xstrlcpy(buf, fname, len); // something failed; use the filename
+ }
+ return rv;
}
/// Get the full resolved path for `fname`
diff --git a/src/nvim/po/uk.po b/src/nvim/po/uk.po
index 3145931bfe..cff140508b 100644
--- a/src/nvim/po/uk.po
+++ b/src/nvim/po/uk.po
@@ -4424,8 +4424,8 @@ msgstr ""
"перед записом: %s"
#, c-format
-msgid "E138: All %s.tmp.X files exist, cannot write ShaDa file!"
-msgstr "E138: Усі файли %s.tmp.X зайнято, неможливо записати файл ShaDa!"
+msgid "E929: All %s.tmp.X files exist, cannot write ShaDa file!"
+msgstr "E929: Усі файли %s.tmp.X зайнято, неможливо записати файл ShaDa!"
#, c-format
msgid "System error while opening temporary ShaDa file %s for writing: %s"
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index 41acc48f97..c0db076eff 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -4912,7 +4912,7 @@ void win_redr_status(win_T *wp)
screen_fill(row, row + 1, len + wp->w_wincol,
this_ru_col + wp->w_wincol, fillchar, fillchar, attr);
- if (get_keymap_str(wp, NameBuff, MAXPATHL)
+ if (get_keymap_str(wp, (char_u *)"<%s>", NameBuff, MAXPATHL)
&& this_ru_col - len > (int)(STRLEN(NameBuff) + 1))
screen_puts(NameBuff, row, (int)(this_ru_col - STRLEN(NameBuff)
- 1 + wp->w_wincol), attr);
@@ -4993,8 +4993,9 @@ int stl_connected(win_T *wp)
int
get_keymap_str (
win_T *wp,
- char_u *buf, /* buffer for the result */
- int len /* length of buffer */
+ char_u *fmt, // format string containing one %s item
+ char_u *buf, // buffer for the result
+ int len // length of buffer
)
{
char_u *p;
@@ -5021,10 +5022,9 @@ get_keymap_str (
else
p = (char_u *)"lang";
}
- if ((int)(STRLEN(p) + 3) < len)
- sprintf((char *)buf, "<%s>", p);
- else
+ if (vim_snprintf((char *)buf, len, (char *)fmt, p) > len - 1) {
buf[0] = NUL;
+ }
xfree(s);
}
return buf[0] != NUL;
@@ -6752,10 +6752,12 @@ int showmode(void)
if (p_fkmap)
MSG_PUTS_ATTR(farsi_text_5, attr);
if (State & LANGMAP) {
- if (curwin->w_p_arab)
+ if (curwin->w_p_arab) {
MSG_PUTS_ATTR(_(" Arabic"), attr);
- else
- MSG_PUTS_ATTR(_(" (lang)"), attr);
+ } else if (get_keymap_str(curwin, (char_u *)" (%s)",
+ NameBuff, MAXPATHL)) {
+ MSG_PUTS_ATTR(NameBuff, attr);
+ }
}
if ((State & INSERT) && p_paste)
MSG_PUTS_ATTR(_(" (paste)"), attr);
diff --git a/src/nvim/search.c b/src/nvim/search.c
index 1029190db4..2a087276e7 100644
--- a/src/nvim/search.c
+++ b/src/nvim/search.c
@@ -3976,12 +3976,10 @@ current_search (
return OK;
}
-/*
- * Check if the pattern is one character or zero-width.
- * If move is true, check from the beginning of the buffer,
- * else from the current cursor position.
- * Returns TRUE, FALSE or -1 for failure.
- */
+/// Check if the pattern is one character long or zero-width.
+/// If move is true, check from the beginning of the buffer,
+/// else from the current cursor position.
+/// Returns TRUE, FALSE or -1 for failure.
static int is_one_char(char_u *pattern, bool move)
{
regmmatch_T regmatch;
@@ -3991,11 +3989,17 @@ static int is_one_char(char_u *pattern, bool move)
int save_called_emsg = called_emsg;
int flag = 0;
+ if (pattern == NULL) {
+ pattern = spats[last_idx].pat;
+ }
+
if (search_regcomp(pattern, RE_SEARCH, RE_SEARCH,
SEARCH_KEEP, &regmatch) == FAIL)
return -1;
- /* move to match */
+ // init startcol correctly
+ regmatch.startpos[0].col = -1;
+ // move to match
if (move) {
clearpos(&pos);
} else {
@@ -4003,21 +4007,29 @@ static int is_one_char(char_u *pattern, bool move)
/* accept a match at the cursor position */
flag = SEARCH_START;
}
- if (searchit(curwin, curbuf, &pos, FORWARD, spats[last_idx].pat, 1,
- SEARCH_KEEP + flag, RE_SEARCH, 0, NULL) != FAIL) {
- /* Zero-width pattern should match somewhere, then we can check if
- * start and end are in the same position. */
- called_emsg = FALSE;
- nmatched = vim_regexec_multi(&regmatch, curwin, curbuf,
- pos.lnum, (colnr_T)0, NULL);
-
- if (!called_emsg)
+ if (searchit(curwin, curbuf, &pos, FORWARD, pattern, 1,
+ SEARCH_KEEP + flag, RE_SEARCH, 0, NULL) != FAIL) {
+ // Zero-width pattern should match somewhere, then we can check if
+ // start and end are in the same position.
+ called_emsg = false;
+ do {
+ regmatch.startpos[0].col++;
+ nmatched = vim_regexec_multi(&regmatch, curwin, curbuf,
+ pos.lnum, regmatch.startpos[0].col, NULL);
+ if (!nmatched) {
+ break;
+ }
+ } while (regmatch.startpos[0].col < pos.col);
+
+ if (!called_emsg) {
result = (nmatched != 0
&& regmatch.startpos[0].lnum == regmatch.endpos[0].lnum
&& regmatch.startpos[0].col == regmatch.endpos[0].col);
-
- if (!result && inc(&pos) >= 0 && pos.col == regmatch.endpos[0].col)
- result = TRUE;
+ // one char width
+ if (!result && inc(&pos) >= 0 && pos.col == regmatch.endpos[0].col) {
+ result = true;
+ }
+ }
}
called_emsg |= save_called_emsg;
diff --git a/src/nvim/shada.c b/src/nvim/shada.c
index 867c697a9a..8cf5976e8b 100644
--- a/src/nvim/shada.c
+++ b/src/nvim/shada.c
@@ -118,9 +118,10 @@ KHASH_SET_INIT_STR(strset)
// E576: Missing '>'
// E577: Illegal register name
// E886: Can't rename viminfo file to %s!
+// E929: Too many viminfo temp files, like %s!
// Now only six of them are used:
// E137: ShaDa file is not writeable (for pre-open checks)
-// E138: All %s.tmp.X files exist, cannot write ShaDa file!
+// E929: All %s.tmp.X files exist, cannot write ShaDa file!
// RCERR (E576) for critical read errors.
// RNERR (E136) for various errors when renaming.
// RERR (E575) for various errors inside read ShaDa file.
@@ -148,6 +149,9 @@ KHASH_SET_INIT_STR(strset)
/// Common prefix for all ignorable “write” errors
#define WERR "E574: "
+/// Callback function for add_search_pattern
+typedef void (*SearchPatternGetter)(SearchPattern *);
+
/// Flags for shada_read_file and children
typedef enum {
kShaDaWantInfo = 1, ///< Load non-mark information
@@ -2323,8 +2327,9 @@ static inline ShaDaWriteResult shada_read_when_writing(
/// @param[in] removable_bufs Buffers which are ignored
///
/// @return ShadaEntry List of buffers to save, kSDItemBufferList entry.
-static ShadaEntry shada_get_buflist(khash_t(bufset) *const removable_bufs)
- FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+static inline ShadaEntry shada_get_buflist(
+ khash_t(bufset) *const removable_bufs)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE
{
int max_bufs = get_shada_parameter('%');
size_t buf_count = 0;
@@ -2368,6 +2373,62 @@ static ShadaEntry shada_get_buflist(khash_t(bufset) *const removable_bufs)
return buflist_entry;
}
+/// Save search pattern to PossiblyFreedShadaEntry
+///
+/// @param[out] ret_pse Location where result will be saved.
+/// @param[in] get_pattern Function used to get pattern.
+/// @param[in] is_substitute_pattern True if pattern in question is substitute
+/// pattern. Also controls whether some
+/// fields should be initialized to default
+/// or values from get_pattern.
+/// @param[in] search_last_used Result of search_was_last_used().
+/// @param[in] search_highlighted True if search pattern was highlighted by
+/// &hlsearch and this information should be
+/// saved.
+static inline void add_search_pattern(PossiblyFreedShadaEntry *const ret_pse,
+ const SearchPatternGetter get_pattern,
+ const bool is_substitute_pattern,
+ const bool search_last_used,
+ const bool search_highlighted)
+ FUNC_ATTR_ALWAYS_INLINE
+{
+ const ShadaEntry defaults = sd_default_values[kSDItemSearchPattern];
+ SearchPattern pat;
+ get_pattern(&pat);
+ if (pat.pat != NULL) {
+ *ret_pse = (PossiblyFreedShadaEntry) {
+ .can_free_entry = false,
+ .data = {
+ .type = kSDItemSearchPattern,
+ .timestamp = pat.timestamp,
+ .data = {
+ .search_pattern = {
+ .magic = pat.magic,
+ .smartcase = !pat.no_scs,
+ .has_line_offset = (is_substitute_pattern
+ ? defaults.data.search_pattern.has_line_offset
+ : pat.off.line),
+ .place_cursor_at_end = (
+ is_substitute_pattern
+ ? defaults.data.search_pattern.place_cursor_at_end
+ : pat.off.end),
+ .offset = (is_substitute_pattern
+ ? defaults.data.search_pattern.offset
+ : pat.off.off),
+ .is_last_used = (is_substitute_pattern ^ search_last_used),
+ .is_substitute_pattern = is_substitute_pattern,
+ .highlighted = ((is_substitute_pattern ^ search_last_used)
+ && search_highlighted),
+ .pat = (char *)pat.pat,
+ .additional_data = pat.additional_data,
+ .search_backward = (!is_substitute_pattern && pat.off.dir == '?'),
+ }
+ }
+ }
+ };
+ }
+}
+
/// Write ShaDa file
///
/// @param[in] sd_writer Structure containing file writer definition.
@@ -2529,45 +2590,14 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer,
const bool search_highlighted = !(no_hlsearch
|| find_shada_parameter('h') != NULL);
const bool search_last_used = search_was_last_used();
-#define ADD_SEARCH_PAT(func, wms_attr, hlo, pcae, o, is_sub) \
- do { \
- SearchPattern pat; \
- func(&pat); \
- if (pat.pat != NULL) { \
- wms->wms_attr = (PossiblyFreedShadaEntry) { \
- .can_free_entry = false, \
- .data = { \
- .type = kSDItemSearchPattern, \
- .timestamp = pat.timestamp, \
- .data = { \
- .search_pattern = { \
- .magic = pat.magic, \
- .smartcase = !pat.no_scs, \
- .has_line_offset = hlo, \
- .place_cursor_at_end = pcae, \
- .offset = o, \
- .is_last_used = (is_sub ^ search_last_used), \
- .is_substitute_pattern = is_sub, \
- .highlighted = ((is_sub ^ search_last_used) \
- && search_highlighted), \
- .pat = (char *) pat.pat, \
- .additional_data = pat.additional_data, \
- .search_backward = (!is_sub && pat.off.dir == '?'), \
- } \
- } \
- } \
- }; \
- } \
- } while (0)
// Initialize search pattern
- ADD_SEARCH_PAT(get_search_pattern, search_pattern, pat.off.line, \
- pat.off.end, pat.off.off, false);
+ add_search_pattern(&wms->search_pattern, &get_search_pattern, false,
+ search_last_used, search_highlighted);
// Initialize substitute search pattern
- ADD_SEARCH_PAT(get_substitute_pattern, sub_search_pattern, false, false, 0,
- true);
-#undef ADD_SEARCH_PAT
+ add_search_pattern(&wms->sub_search_pattern, &get_substitute_pattern, true,
+ search_last_used, search_highlighted);
// Initialize substitute replacement string
{
@@ -2590,10 +2620,12 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer,
// Initialize jump list
const void *jump_iter = NULL;
+ setpcmark();
+ cleanup_jumplist();
do {
xfmark_T fm;
- cleanup_jumplist();
jump_iter = mark_jumplist_iter(jump_iter, curwin, &fm);
+
const buf_T *const buf = (fm.fmark.fnum == 0
? NULL
: buflist_findnr(fm.fmark.fnum));
diff --git a/src/nvim/spell.c b/src/nvim/spell.c
index 7119ac6dc1..7e0bbd6ad1 100644
--- a/src/nvim/spell.c
+++ b/src/nvim/spell.c
@@ -292,6 +292,9 @@
#include <stdlib.h>
#include <wctype.h>
+/* for offsetof() */
+#include <stddef.h>
+
#include "nvim/vim.h"
#include "nvim/ascii.h"
#include "nvim/spell.h"
@@ -583,9 +586,8 @@ typedef struct wordcount_S {
char_u wc_word[1]; // word, actually longer
} wordcount_T;
-static wordcount_T dumwc;
-#define WC_KEY_OFF (unsigned)(dumwc.wc_word - (char_u *)&dumwc)
-#define HI2WC(hi) ((wordcount_T *)((hi)->hi_key - WC_KEY_OFF))
+#define WC_KEY_OFF offsetof(wordcount_T, wc_word)
+#define HI2WC(hi) ((wordcount_T *)((hi)->hi_key - WC_KEY_OFF))
#define MAXWORDCOUNT 0xffff
// Information used when looking for suggestions.
diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile
index 84a0c0b889..814c0bc3cb 100644
--- a/src/nvim/testdir/Makefile
+++ b/src/nvim/testdir/Makefile
@@ -34,6 +34,7 @@ NEW_TESTS = \
test_cmdline.res \
test_cscope.res \
test_diffmode.res \
+ test_gn.res \
test_hardcopy.res \
test_help_tagjump.res \
test_history.res \
@@ -43,8 +44,8 @@ NEW_TESTS = \
test_quickfix.res \
test_signs.res \
test_syntax.res \
- test_usercommands.res \
test_timers.res \
+ test_usercommands.res \
test_viml.res \
test_visual.res \
test_window_id.res \
diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim
index 20863bbaf3..eb5912086b 100644
--- a/src/nvim/testdir/runtest.vim
+++ b/src/nvim/testdir/runtest.vim
@@ -39,6 +39,9 @@ if &lines < 24 || &columns < 80
cquit
endif
+" Common with all tests on all systems.
+source setup.vim
+
" This also enables use of line continuation.
set viminfo+=nviminfo
@@ -59,9 +62,6 @@ lang mess C
" Always use forward slashes.
set shellslash
-" Make sure $HOME does not get read or written.
-let $HOME = '/does/not/exist'
-
" Align with vim defaults.
set directory^=.
set nohidden
diff --git a/src/nvim/testdir/setup.vim b/src/nvim/testdir/setup.vim
new file mode 100644
index 0000000000..52876d1e6c
--- /dev/null
+++ b/src/nvim/testdir/setup.vim
@@ -0,0 +1,7 @@
+" Common preparations for running tests.
+
+" Make sure 'runtimepath' does not include $HOME.
+set rtp=$VIM/vimfiles,$VIMRUNTIME,$VIM/vimfiles/after
+
+" Make sure $HOME does not get read or written.
+let $HOME = '/does/not/exist'
diff --git a/src/nvim/testdir/test17.in b/src/nvim/testdir/test17.in
index 83abe17770..1a4ac6b6d1 100644
--- a/src/nvim/testdir/test17.in
+++ b/src/nvim/testdir/test17.in
@@ -4,13 +4,7 @@ Tests for:
STARTTEST
:set isfname=@,48-57,/,.,-,_,+,,,$,:,~,{,}
-:function! DeleteDirectory(dir)
-: if has("win16") || has("win32") || has("win64") || has("dos16") || has("dos32")
-: exec "silent !rmdir /Q /S " . a:dir
-: else
-: exec "silent !rm -rf " . a:dir
-: endif
-:endfun
+:"
:if has("unix")
:let $CDIR = "."
/CDIR
@@ -36,7 +30,7 @@ STARTTEST
:" check for 'include' without \zs or \ze
:lang C
:call delete("./Xbase.a")
-:call DeleteDirectory("Xdir1")
+:call delete("Xdir1", "rf")
:!mkdir Xdir1
:!mkdir "Xdir1/dir2"
:e! Xdir1/dir2/foo.a
@@ -61,7 +55,7 @@ ENDTEST
STARTTEST
:" check for 'include' with \zs and \ze
:call delete("./Xbase.b")
-:call DeleteDirectory("Xdir1")
+:call delete("Xdir1", "rf")
:!mkdir Xdir1
:!mkdir "Xdir1/dir2"
:let &include='^\s*%inc\s*/\zs[^/]\+\ze'
@@ -91,7 +85,7 @@ ENDTEST
STARTTEST
:" check for 'include' with \zs and no \ze
:call delete("./Xbase.c")
-:call DeleteDirectory("Xdir1")
+:call delete("Xdir1", "rf")
:!mkdir Xdir1
:!mkdir "Xdir1/dir2"
:let &include='^\s*%inc\s*\%([[:upper:]][^[:space:]]*\s\+\)\?\zs\S\+\ze'
diff --git a/src/nvim/testdir/test53.in b/src/nvim/testdir/test53.in
index f3778c5192..20b5d019af 100644
--- a/src/nvim/testdir/test53.in
+++ b/src/nvim/testdir/test53.in
@@ -4,8 +4,6 @@ Note that the end-of-line moves the cursor to the next test line.
Also test match() and matchstr()
-Also test the gn command and repeating it.
-
STARTTEST
/^start:/
da"
@@ -52,35 +50,6 @@ dit
:put =match('abc', '\zs', 2, 1) " 2
:put =match('abc', '\zs', 3, 1) " 3
:put =match('abc', '\zs', 4, 1) " -1
-/^foobar
-gncsearchmatch/one\_s*two\_s
-:1
-gnd
-/[a]bcdx
-:1
-2gnd/join
-/$
-0gnd
-/\>\zs
-0gnd/^
-gnd$h/\zs
-gnd/[u]niquepattern/s
-vlgnd
-/mother
-:set selection=exclusive
-$cgNmongoose/i
-cgnj
-:" Make sure there is no other match y uppercase.
-/x59
-gggnd
-:" test repeating dgn
-/^Johnny
-ggdgn.
-:" test repeating gUgn
-/^Depp
-gggUgn.
-gg/a:0\@!\zs\d\+
-nygnop
:/^start:/,/^end:/wq! test.out
ENDTEST
@@ -102,32 +71,4 @@ innertext object
</b>
</begin>
SEARCH:
-foobar
-one
-two
-abcdx | abcdx | abcdx
-join
-lines
-zero width pattern
-delete first and last chars
-uniquepattern uniquepattern
-my very excellent mother just served us nachos
-for (i=0; i<=10; i++)
-a:10
-
-a:1
-
-a:20
-Y
-text
-Y
---1
-Johnny
---2
-Johnny
---3
-Depp
---4
-Depp
---5
end:
diff --git a/src/nvim/testdir/test53.ok b/src/nvim/testdir/test53.ok
index 05206972a4..d57d86bbb0 100644
--- a/src/nvim/testdir/test53.ok
+++ b/src/nvim/testdir/test53.ok
@@ -42,30 +42,4 @@ a
3
-1
SEARCH:
-searchmatch
-abcdx | | abcdx
-join lines
-zerowidth pattern
-elete first and last char
- uniquepattern
-my very excellent mongoose just served us nachos
-for (j=0; i<=10; i++)
-a:10
-
-a:1
-1
-
-a:20
-
-text
-Y
---1
-
---2
-
---3
-DEPP
---4
-DEPP
---5
end:
diff --git a/src/nvim/testdir/test73.in b/src/nvim/testdir/test73.in
index 7d6c7287a5..9d50f7a789 100644
--- a/src/nvim/testdir/test73.in
+++ b/src/nvim/testdir/test73.in
@@ -8,16 +8,9 @@ STARTTEST
:" This will cause a few errors, do it silently.
:set visualbell
:"
-:function! DeleteDirectory(dir)
-: if has("win16") || has("win32") || has("win64") || has("dos16") || has("dos32")
-: exec "silent !rmdir /Q /S " . a:dir
-: else
-: exec "silent !rm -rf " . a:dir
-: endif
-:endfun
:" On windows a stale "Xfind" directory may exist, remove it so that
:" we start from a clean state.
-:call DeleteDirectory("Xfind")
+:call delete("Xfind", "rf")
:new
:let cwd=getcwd()
:let test_out = cwd . '/test.out'
@@ -169,7 +162,7 @@ SVoyager 2:w
:exec "w >>" . test_out
:q
:exec "cd " . cwd
-:call DeleteDirectory("Xfind")
+:call delete("Xfind", "rf")
:qa!
ENDTEST
diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim
index 818ff7cf54..60248bf430 100644
--- a/src/nvim/testdir/test_alot.vim
+++ b/src/nvim/testdir/test_alot.vim
@@ -4,6 +4,7 @@
source test_assign.vim
source test_autocmd.vim
source test_cursor_func.vim
+source test_execute_func.vim
source test_ex_undo.vim
source test_expr.vim
source test_expr_utf8.vim
diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim
index 1dceb70cd4..f05a55f1aa 100644
--- a/src/nvim/testdir/test_autocmd.vim
+++ b/src/nvim/testdir/test_autocmd.vim
@@ -19,6 +19,7 @@ if has('timers')
call timer_start(100, 'ExitInsertMode')
call feedkeys('a', 'x!')
call assert_equal(1, g:triggered)
+ au! CursorHoldI
endfunc
func Test_cursorhold_insert_ctrl_x()
@@ -29,6 +30,7 @@ if has('timers')
" CursorHoldI does not trigger after CTRL-X
call feedkeys("a\<C-X>", 'x!')
call assert_equal(0, g:triggered)
+ au! CursorHoldI
endfunc
endif
@@ -58,5 +60,139 @@ function Test_bufunload()
bwipeout
call assert_equal(["bufunload", "bufdelete", "bufwipeout"], s:li)
+ au! test_bufunload_group
augroup! test_bufunload_group
endfunc
+
+" SEGV occurs in older versions. (At least 7.4.2005 or older)
+function Test_autocmd_bufunload_with_tabnext()
+ tabedit
+ tabfirst
+
+ augroup test_autocmd_bufunload_with_tabnext_group
+ autocmd!
+ autocmd BufUnload <buffer> tabnext
+ augroup END
+
+ quit
+ call assert_equal(2, tabpagenr('$'))
+
+ augroup! test_autocmd_bufunload_with_tabnext_group
+ tablast
+ quit
+endfunc
+
+func Test_win_tab_autocmd()
+ let g:record = []
+
+ augroup testing
+ au WinNew * call add(g:record, 'WinNew')
+ au WinEnter * call add(g:record, 'WinEnter')
+ au WinLeave * call add(g:record, 'WinLeave')
+ au TabNew * call add(g:record, 'TabNew')
+ au TabClosed * call add(g:record, 'TabClosed')
+ au TabEnter * call add(g:record, 'TabEnter')
+ au TabLeave * call add(g:record, 'TabLeave')
+ augroup END
+
+ split
+ tabnew
+ close
+ close
+
+ call assert_equal([
+ \ 'WinLeave', 'WinNew', 'WinEnter',
+ \ 'WinLeave', 'TabLeave', 'WinNew', 'WinEnter', 'TabNew', 'TabEnter',
+ \ 'WinLeave', 'TabLeave', 'TabClosed', 'WinEnter', 'TabEnter',
+ \ 'WinLeave', 'WinEnter'
+ \ ], g:record)
+
+ let g:record = []
+ tabnew somefile
+ tabnext
+ bwipe somefile
+
+ call assert_equal([
+ \ 'WinLeave', 'TabLeave', 'WinNew', 'WinEnter', 'TabNew', 'TabEnter',
+ \ 'WinLeave', 'TabLeave', 'WinEnter', 'TabEnter',
+ \ 'TabClosed'
+ \ ], g:record)
+
+ augroup testing
+ au!
+ augroup END
+ unlet g:record
+endfunc
+
+func s:AddAnAutocmd()
+ augroup vimBarTest
+ au BufReadCmd * echo 'hello'
+ augroup END
+ call assert_equal(3, len(split(execute('au vimBarTest'), "\n")))
+endfunc
+
+func Test_early_bar()
+ " test that a bar is recognized before the {event}
+ call s:AddAnAutocmd()
+ augroup vimBarTest | au! | augroup END
+ call assert_equal(1, len(split(execute('au vimBarTest'), "\n")))
+
+ call s:AddAnAutocmd()
+ augroup vimBarTest| au!| augroup END
+ call assert_equal(1, len(split(execute('au vimBarTest'), "\n")))
+
+ " test that a bar is recognized after the {event}
+ call s:AddAnAutocmd()
+ augroup vimBarTest| au!BufReadCmd| augroup END
+ call assert_equal(1, len(split(execute('au vimBarTest'), "\n")))
+
+ " test that a bar is recognized after the {group}
+ call s:AddAnAutocmd()
+ au! vimBarTest|echo 'hello'
+ call assert_equal(1, len(split(execute('au vimBarTest'), "\n")))
+endfunc
+
+func RemoveGroup()
+ autocmd! StartOK
+ augroup! StartOK
+endfunc
+
+func Test_augroup_warning()
+ augroup TheWarning
+ au VimEnter * echo 'entering'
+ augroup END
+ call assert_true(match(execute('au VimEnter'), "TheWarning.*VimEnter") >= 0)
+ redir => res
+ augroup! TheWarning
+ redir END
+ call assert_true(match(res, "W19:") >= 0)
+ call assert_true(match(execute('au VimEnter'), "-Deleted-.*VimEnter") >= 0)
+
+ " check "Another" does not take the pace of the deleted entry
+ augroup Another
+ augroup END
+ call assert_true(match(execute('au VimEnter'), "-Deleted-.*VimEnter") >= 0)
+
+ " no warning for postpone aucmd delete
+ augroup StartOK
+ au VimEnter * call RemoveGroup()
+ augroup END
+ call assert_true(match(execute('au VimEnter'), "StartOK.*VimEnter") >= 0)
+ redir => res
+ doautocmd VimEnter
+ redir END
+ call assert_true(match(res, "W19:") < 0)
+ au! VimEnter
+endfunc
+
+func Test_augroup_deleted()
+ " This caused a crash before E936 was introduced
+ augroup x
+ call assert_fails('augroup! x', 'E936:')
+ au VimEnter * echo
+ augroup end
+ augroup! x
+ call assert_true(match(execute('au VimEnter'), "-Deleted-.*VimEnter") >= 0)
+ au! VimEnter
+endfunc
+
diff --git a/src/nvim/testdir/test_execute_func.vim b/src/nvim/testdir/test_execute_func.vim
new file mode 100644
index 0000000000..6f61bede93
--- /dev/null
+++ b/src/nvim/testdir/test_execute_func.vim
@@ -0,0 +1,55 @@
+" test execute()
+
+func NestedEval()
+ let nested = execute('echo "nested\nlines"')
+ echo 'got: "' . nested . '"'
+endfunc
+
+func NestedRedir()
+ redir => var
+ echo 'broken'
+ redir END
+endfunc
+
+func Test_execute_string()
+ call assert_equal("\nnocompatible", execute('set compatible?'))
+ call assert_equal("\nsomething\nnice", execute('echo "something\nnice"'))
+ call assert_equal("noendofline", execute('echon "noendofline"'))
+ call assert_equal("", execute(123))
+
+ call assert_equal("\ngot: \"\nnested\nlines\"", execute('call NestedEval()'))
+ redir => redired
+ echo 'this'
+ let evaled = execute('echo "that"')
+ echo 'theend'
+ redir END
+" Nvim supports execute('... :redir ...'), so this test is intentionally
+" disabled.
+" call assert_equal("\nthis\ntheend", redired)
+ call assert_equal("\nthat", evaled)
+
+ call assert_fails('call execute("doesnotexist")', 'E492:')
+ call assert_fails('call execute(3.4)', 'E806:')
+" Nvim supports execute('... :redir ...'), so this test is intentionally
+" disabled.
+" call assert_fails('call execute("call NestedRedir()")', 'E930:')
+
+ call assert_equal("\nsomething", execute('echo "something"', ''))
+ call assert_equal("\nsomething", execute('echo "something"', 'silent'))
+ call assert_equal("\nsomething", execute('echo "something"', 'silent!'))
+ call assert_equal("", execute('burp', 'silent!'))
+ call assert_fails('call execute("echo \"x\"", 3.4)', 'E806:')
+
+ call assert_equal("", execute(""))
+endfunc
+
+func Test_execute_list()
+ call assert_equal("\nsomething\nnice", execute(['echo "something"', 'echo "nice"']))
+ let l = ['for n in range(0, 3)',
+ \ 'echo n',
+ \ 'endfor']
+ call assert_equal("\n0\n1\n2\n3", execute(l))
+
+ call assert_equal("", execute([]))
+ call assert_equal("", execute(v:_null_list))
+endfunc
diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim
index 571a37c62c..39dcacb55f 100644
--- a/src/nvim/testdir/test_expr.vim
+++ b/src/nvim/testdir/test_expr.vim
@@ -1,20 +1,5 @@
" Tests for expressions.
-func Test_version()
- call assert_true(has('patch-7.4.001'))
- call assert_true(has('patch-7.4.01'))
- call assert_true(has('patch-7.4.1'))
- call assert_true(has('patch-6.9.999'))
- call assert_true(has('patch-7.1.999'))
- call assert_true(has('patch-7.4.123'))
-
- call assert_false(has('patch-7'))
- call assert_false(has('patch-7.4'))
- call assert_false(has('patch-7.4.'))
- call assert_false(has('patch-9.1.0'))
- call assert_false(has('patch-9.9.1'))
-endfunc
-
func Test_equal()
let base = {}
func base.method()
@@ -37,6 +22,35 @@ func Test_equal()
call assert_fails('echo base.method > instance.method')
endfunc
+func Test_version()
+ call assert_true(has('patch-7.4.001'))
+ call assert_true(has('patch-7.4.01'))
+ call assert_true(has('patch-7.4.1'))
+ call assert_true(has('patch-6.9.999'))
+ call assert_true(has('patch-7.1.999'))
+ call assert_true(has('patch-7.4.123'))
+
+ call assert_false(has('patch-7'))
+ call assert_false(has('patch-7.4'))
+ call assert_false(has('patch-7.4.'))
+ call assert_false(has('patch-9.1.0'))
+ call assert_false(has('patch-9.9.1'))
+endfunc
+
+func Test_dict()
+ let d = {'': 'empty', 'a': 'a', 0: 'zero'}
+ call assert_equal('empty', d[''])
+ call assert_equal('a', d['a'])
+ call assert_equal('zero', d[0])
+ call assert_true(has_key(d, ''))
+ call assert_true(has_key(d, 'a'))
+
+ let d[''] = 'none'
+ let d['a'] = 'aaa'
+ call assert_equal('none', d[''])
+ call assert_equal('aaa', d['a'])
+endfunc
+
func Test_strgetchar()
call assert_equal(char2nr('a'), strgetchar('axb', 0))
call assert_equal(char2nr('x'), strgetchar('axb', 1))
@@ -61,23 +75,34 @@ func Test_strcharpart()
call assert_equal('a', strcharpart('axb', -1, 2))
endfunc
-func Test_dict()
- let d = {'': 'empty', 'a': 'a', 0: 'zero'}
- call assert_equal('empty', d[''])
- call assert_equal('a', d['a'])
- call assert_equal('zero', d[0])
- call assert_true(has_key(d, ''))
- call assert_true(has_key(d, 'a'))
-
- let d[''] = 'none'
- let d['a'] = 'aaa'
- call assert_equal('none', d[''])
- call assert_equal('aaa', d['a'])
-endfunc
-
func Test_loop_over_null_list()
let null_list = submatch(1, 1)
for i in null_list
call assert_true(0, 'should not get here')
endfor
endfunc
+
+func Test_compare_null_dict()
+ call assert_fails('let x = v:_null_dict[10]')
+ call assert_equal({}, {})
+ call assert_equal(v:_null_dict, v:_null_dict)
+ call assert_notequal({}, v:_null_dict)
+endfunc
+
+func Test_set_reg_null_list()
+ call setreg('x', v:_null_list)
+endfunc
+
+func Test_special_char()
+ " The failure is only visible using valgrind.
+ call assert_fails('echo "\<C-">')
+endfunc
+
+func Test_setmatches()
+ hi def link 1 Comment
+ hi def link 2 PreProc
+ let set = [{"group": 1, "pattern": 2, "id": 3, "priority": 4, "conceal": 5}]
+ let exp = [{"group": '1', "pattern": '2', "id": 3, "priority": 4, "conceal": '5'}]
+ call setmatches(set)
+ call assert_equal(exp, getmatches())
+endfunc
diff --git a/src/nvim/testdir/test_gn.vim b/src/nvim/testdir/test_gn.vim
new file mode 100644
index 0000000000..3eca99bd99
--- /dev/null
+++ b/src/nvim/testdir/test_gn.vim
@@ -0,0 +1,93 @@
+" Test for gn command
+
+func Test_gn_command()
+ noa new
+ " replace a single char by itsself quoted:
+ call setline('.', 'abc x def x ghi x jkl')
+ let @/='x'
+ exe "norm! cgn'x'\<esc>.."
+ call assert_equal("abc 'x' def 'x' ghi 'x' jkl", getline('.'))
+ sil! %d_
+ " simple search match
+ call setline('.', 'foobar')
+ let @/='foobar'
+ exe "norm! gncsearchmatch"
+ call assert_equal('searchmatch', getline('.'))
+ sil! %d _
+ " replace a multi-line match
+ call setline('.', ['', 'one', 'two'])
+ let @/='one\_s*two\_s'
+ exe "norm! gnceins\<CR>zwei"
+ call assert_equal(['','eins','zwei'], getline(1,'$'))
+ sil! %d _
+ " test count argument
+ call setline('.', ['', 'abcdx | abcdx | abcdx'])
+ let @/='[a]bcdx'
+ exe "norm! 2gnd"
+ call assert_equal(['','abcdx | | abcdx'], getline(1,'$'))
+ sil! %d _
+ " join lines
+ call setline('.', ['join ', 'lines'])
+ let @/='$'
+ exe "norm! 0gnd"
+ call assert_equal(['join lines'], getline(1,'$'))
+ sil! %d _
+ " zero-width match
+ call setline('.', ['', 'zero width pattern'])
+ let @/='\>\zs'
+ exe "norm! 0gnd"
+ call assert_equal(['', 'zerowidth pattern'], getline(1,'$'))
+ sil! %d _
+ " delete first and last chars
+ call setline('.', ['delete first and last chars'])
+ let @/='^'
+ exe "norm! 0gnd$"
+ let @/='\zs'
+ exe "norm! gnd"
+ call assert_equal(['elete first and last char'], getline(1,'$'))
+ sil! %d _
+ " using visual mode
+ call setline('.', ['', 'uniquepattern uniquepattern'])
+ exe "norm! /[u]niquepattern/s\<cr>vlgnd"
+ call assert_equal(['', ' uniquepattern'], getline(1,'$'))
+ sil! %d _
+ " backwards search
+ call setline('.', ['my very excellent mother just served us nachos'])
+ let @/='mother'
+ exe "norm! $cgNmongoose"
+ call assert_equal(['my very excellent mongoose just served us nachos'], getline(1,'$'))
+ sil! %d _
+ " search for single char
+ call setline('.', ['','for (i=0; i<=10; i++)'])
+ let @/='i'
+ exe "norm! cgnj"
+ call assert_equal(['','for (j=0; i<=10; i++)'], getline(1,'$'))
+ sil! %d _
+ " search hex char
+ call setline('.', ['','Y'])
+ set noignorecase
+ let @/='\%x59'
+ exe "norm! gnd"
+ call assert_equal(['',''], getline(1,'$'))
+ sil! %d _
+ " test repeating gdn
+ call setline('.', ['', '1', 'Johnny', '2', 'Johnny', '3'])
+ let @/='Johnny'
+ exe "norm! dgn."
+ call assert_equal(['','1', '', '2', '', '3'], getline(1,'$'))
+ sil! %d _
+ " test repeating gUgn
+ call setline('.', ['', '1', 'Depp', '2', 'Depp', '3'])
+ let @/='Depp'
+ exe "norm! gUgn."
+ call assert_equal(['', '1', 'DEPP', '2', 'DEPP', '3'], getline(1,'$'))
+ sil! %d _
+ " test using look-ahead assertions
+ call setline('.', ['a:10', '', 'a:1', '', 'a:20'])
+ let @/='a:0\@!\zs\d\+'
+ exe "norm! 2nygno\<esc>p"
+ call assert_equal(['a:10', '', 'a:1', '1', '', 'a:20'], getline(1,'$'))
+ sil! %d _
+endfu
+
+" vim: tabstop=2 shiftwidth=0 expandtab
diff --git a/src/nvim/testdir/test_goto.vim b/src/nvim/testdir/test_goto.vim
index 2afd96b296..b6ac5720c3 100644
--- a/src/nvim/testdir/test_goto.vim
+++ b/src/nvim/testdir/test_goto.vim
@@ -18,3 +18,18 @@ func Test_gee_dee()
call assert_equal(14, col('.'))
quit!
endfunc
+
+" Check that setting 'cursorline' does not change curswant
+func Test_cursorline_keep_col()
+ new
+ call setline(1, ['long long long line', 'short line'])
+ normal ggfi
+ let pos = getcurpos()
+ normal j
+ set cursorline
+ normal k
+ call assert_equal(pos, getcurpos())
+ bwipe!
+ set nocursorline
+endfunc
+
diff --git a/src/nvim/testdir/test_syn_attr.vim b/src/nvim/testdir/test_syn_attr.vim
index 94d6b8735f..64aab3411d 100644
--- a/src/nvim/testdir/test_syn_attr.vim
+++ b/src/nvim/testdir/test_syn_attr.vim
@@ -20,7 +20,7 @@ func Test_missing_attr()
if fontname == ''
let fontname = 'something'
endif
- exe 'hi Mine guifg=blue guibg=red font=' . escape(fontname, ' \')
+ exe "hi Mine guifg=blue guibg=red font='" . fontname . "'"
call assert_equal('blue', synIDattr(hlID("Mine"), "fg", 'gui'))
call assert_equal('red', synIDattr(hlID("Mine"), "bg", 'gui'))
call assert_equal(fontname, synIDattr(hlID("Mine"), "font", 'gui'))
diff --git a/src/nvim/testdir/test_usercommands.vim b/src/nvim/testdir/test_usercommands.vim
index f593d16dbf..d0864ec64c 100644
--- a/src/nvim/testdir/test_usercommands.vim
+++ b/src/nvim/testdir/test_usercommands.vim
@@ -8,31 +8,57 @@ function Test_cmdmods()
MyCmd
aboveleft MyCmd
+ abo MyCmd
belowright MyCmd
+ bel MyCmd
botright MyCmd
+ bo MyCmd
browse MyCmd
+ bro MyCmd
confirm MyCmd
+ conf MyCmd
hide MyCmd
+ hid MyCmd
keepalt MyCmd
+ keepa MyCmd
keepjumps MyCmd
+ keepj MyCmd
keepmarks MyCmd
+ kee MyCmd
keeppatterns MyCmd
+ keepp MyCmd
+ leftabove MyCmd " results in :aboveleft
+ lefta MyCmd
lockmarks MyCmd
+ loc MyCmd
+ " noautocmd MyCmd
noswapfile MyCmd
+ nos MyCmd
+ rightbelow MyCmd " results in :belowright
+ rightb MyCmd
+ " sandbox MyCmd
silent MyCmd
+ sil MyCmd
tab MyCmd
topleft MyCmd
+ to MyCmd
+ " unsilent MyCmd
verbose MyCmd
+ verb MyCmd
vertical MyCmd
+ vert MyCmd
aboveleft belowright botright browse confirm hide keepalt keepjumps
\ keepmarks keeppatterns lockmarks noswapfile silent tab
\ topleft verbose vertical MyCmd
- call assert_equal(' aboveleft belowright botright browse confirm ' .
- \ 'hide keepalt keepjumps keepmarks keeppatterns lockmarks ' .
- \ 'noswapfile silent tab topleft verbose vertical aboveleft ' .
- \ 'belowright botright browse confirm hide keepalt keepjumps ' .
+ call assert_equal(' aboveleft aboveleft belowright belowright botright ' .
+ \ 'botright browse browse confirm confirm hide hide ' .
+ \ 'keepalt keepalt keepjumps keepjumps keepmarks keepmarks ' .
+ \ 'keeppatterns keeppatterns aboveleft aboveleft lockmarks lockmarks noswapfile ' .
+ \ 'noswapfile belowright belowright silent silent tab topleft topleft verbose verbose ' .
+ \ 'vertical vertical ' .
+ \ 'aboveleft belowright botright browse confirm hide keepalt keepjumps ' .
\ 'keepmarks keeppatterns lockmarks noswapfile silent tab topleft ' .
\ 'verbose vertical ', g:mods)
@@ -46,3 +72,33 @@ function Test_cmdmods()
delcommand MyQCmd
unlet g:mods
endfunction
+
+func Test_Ambiguous()
+ command Doit let g:didit = 'yes'
+ command Dothat let g:didthat = 'also'
+ call assert_fails('Do', 'E464:')
+ Doit
+ call assert_equal('yes', g:didit)
+ Dothat
+ call assert_equal('also', g:didthat)
+ unlet g:didit
+ unlet g:didthat
+
+ delcommand Doit
+ Do
+ call assert_equal('also', g:didthat)
+ delcommand Dothat
+endfunc
+
+func Test_CmdUndefined()
+ call assert_fails('Doit', 'E492:')
+ au CmdUndefined Doit :command Doit let g:didit = 'yes'
+ Doit
+ call assert_equal('yes', g:didit)
+ delcommand Doit
+
+ call assert_fails('Dothat', 'E492:')
+ au CmdUndefined * let g:didnot = 'yes'
+ call assert_fails('Dothat', 'E492:')
+ call assert_equal('yes', g:didnot)
+endfunc
diff --git a/src/nvim/types.h b/src/nvim/types.h
index 35a5d1e2bd..317bead3bb 100644
--- a/src/nvim/types.h
+++ b/src/nvim/types.h
@@ -14,4 +14,5 @@ typedef unsigned char char_u;
typedef uint32_t u8char_T;
typedef struct expand expand_T;
+
#endif // NVIM_TYPES_H
diff --git a/src/nvim/version.c b/src/nvim/version.c
index 05267028ee..6f519383ec 100644
--- a/src/nvim/version.c
+++ b/src/nvim/version.c
@@ -126,8 +126,8 @@ static int included_patches[] = {
// 2317,
// 2316 NA
// 2315,
- // 2314,
- // 2313,
+ 2314,
+ 2313,
2312,
// 2311 NA
// 2310 NA
@@ -140,7 +140,7 @@ static int included_patches[] = {
// 2303,
// 2302 NA
// 2301 NA
- // 2300,
+ 2300,
// 2299,
// 2298 NA
// 2297 NA
@@ -280,7 +280,7 @@ static int included_patches[] = {
2163,
2162,
// 2161,
- // 2160,
+ 2160,
// 2159,
2158,
// 2157 NA
@@ -313,7 +313,7 @@ static int included_patches[] = {
// 2130 NA
// 2129 NA
2128,
- // 2127,
+ 2127,
2126,
// 2125 NA
2124,
@@ -323,25 +323,25 @@ static int included_patches[] = {
// 2120,
// 2119,
// 2118 NA
- // 2117,
+ 2117,
// 2116 NA
// 2115 NA
// 2114 NA
// 2113,
2112,
// 2111,
- // 2110,
+ 2110,
2109,
// 2108 NA
// 2107,
// 2106,
// 2105 NA
// 2104,
- // 2103,
+ 2103,
// 2102 NA
// 2101,
- // 2100,
- // 2099,
+ 2100,
+ 2099,
// 2098,
// 2097,
// 2096,
@@ -363,9 +363,9 @@ static int included_patches[] = {
// 2080,
// 2079 NA
// 2078 NA
- // 2077,
+ 2077,
// 2076,
- // 2075,
+ 2075,
// 2074,
// 2073 NA
// 2072,
@@ -375,10 +375,10 @@ static int included_patches[] = {
// 2068,
// 2067,
2066,
- // 2065,
+ 2065,
// 2064,
// 2063 NA
- // 2062,
+ 2062,
// 2061,
// 2060 NA
// 2059 NA
@@ -397,7 +397,7 @@ static int included_patches[] = {
// 2046,
// 2045 NA
// 2044,
- // 2043,
+ 2043,
// 2042 NA
// 2041 NA
// 2040 NA
@@ -409,7 +409,7 @@ static int included_patches[] = {
// 2034 NA
2033,
// 2032 NA
- // 2031,
+ 2031,
// 2030 NA
// 2029,
2028,
@@ -425,16 +425,16 @@ static int included_patches[] = {
// 2018,
// 2017,
// 2016 NA
- // 2015,
+ 2015,
2014,
2013,
2012,
2011,
2010,
- // 2009,
- // 2008,
+ 2009,
+ 2008,
2007,
- // 2006,
+ 2006,
2005,
// 2004 NA
// 2003 NA
@@ -472,7 +472,7 @@ static int included_patches[] = {
1971,
1970,
// 1969 NA
- // 1968,
+ 1968,
1967,
1966,
// 1965 NA
@@ -515,7 +515,7 @@ static int included_patches[] = {
1928,
// 1927 NA
// 1926 NA
- // 1925 NA
+ 1925,
// 1924 NA
1923,
// 1922 NA
@@ -530,7 +530,7 @@ static int included_patches[] = {
1913,
1912,
// 1911 NA
- // 1910,
+ 1910,
1909,
// 1908 NA
// 1907 NA
@@ -551,7 +551,7 @@ static int included_patches[] = {
1892,
// 1891 NA
// 1890 NA
- // 1889,
+ 1889,
// 1888 NA
// 1887 NA
// 1886 NA
@@ -589,11 +589,11 @@ static int included_patches[] = {
// 1854 NA
// 1853 NA
// 1852 NA
- // 1851,
+ 1851,
// 1850 NA
// 1849 NA
// 1848 NA
- // 1847,
+ 1847,
// 1846 NA
// 1845 NA
// 1844 NA
@@ -602,7 +602,7 @@ static int included_patches[] = {
1841,
1840,
1839,
- // 1838 NA
+ 1838,
1837,
1836,
1835,
@@ -689,7 +689,6 @@ static int included_patches[] = {
1755,
1754,
1753,
- // 1753,
1752,
1751,
// 1750 NA
diff --git a/src/nvim/window.c b/src/nvim/window.c
index 00229ccca9..89228e1b0f 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -973,11 +973,12 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
/*
* make the new window the current window
*/
- win_enter(wp, false);
- if (flags & WSP_VERT)
+ win_enter_ext(wp, false, false, true, true, true);
+ if (flags & WSP_VERT) {
p_wiw = i;
- else
+ } else {
p_wh = i;
+ }
return OK;
}
@@ -1718,6 +1719,7 @@ close_windows (
{
tabpage_T *tp, *nexttp;
int h = tabline_height();
+ int count = tabpage_index(NULL);
++RedrawingDisabled;
@@ -1754,9 +1756,14 @@ close_windows (
--RedrawingDisabled;
- redraw_tabline = TRUE;
- if (h != tabline_height())
+ if (count != tabpage_index(NULL)) {
+ apply_autocmds(EVENT_TABCLOSED, NULL, NULL, false, curbuf);
+ }
+
+ redraw_tabline = true;
+ if (h != tabline_height()) {
shell_new_rows();
+ }
}
/// Check that current window is the last one.
@@ -2014,10 +2021,11 @@ int win_close(win_T *win, int free_buf)
}
if (close_curwin) {
- win_enter_ext(wp, false, TRUE, TRUE, TRUE);
- if (other_buffer)
- /* careful: after this wp and win may be invalid! */
- apply_autocmds(EVENT_BUFENTER, NULL, NULL, FALSE, curbuf);
+ win_enter_ext(wp, false, true, false, true, true);
+ if (other_buffer) {
+ // careful: after this wp and win may be invalid!
+ apply_autocmds(EVENT_BUFENTER, NULL, NULL, false, curbuf);
+ }
}
/*
@@ -3045,8 +3053,9 @@ int win_new_tabpage(int after, char_u *filename)
redraw_all_later(CLEAR);
- apply_autocmds(EVENT_TABNEW, filename, filename, false, curbuf);
+ apply_autocmds(EVENT_WINNEW, NULL, NULL, false, curbuf);
apply_autocmds(EVENT_WINENTER, NULL, NULL, false, curbuf);
+ apply_autocmds(EVENT_TABNEW, filename, filename, false, curbuf);
apply_autocmds(EVENT_TABENTER, NULL, NULL, false, curbuf);
return OK;
@@ -3204,8 +3213,8 @@ static void enter_tabpage(tabpage_T *tp, buf_T *old_curbuf, int trigger_enter_au
/* We would like doing the TabEnter event first, but we don't have a
* valid current window yet, which may break some commands.
* This triggers autocommands, thus may make "tp" invalid. */
- win_enter_ext(tp->tp_curwin, false, TRUE,
- trigger_enter_autocmds, trigger_leave_autocmds);
+ win_enter_ext(tp->tp_curwin, false, true, false,
+ trigger_enter_autocmds, trigger_leave_autocmds);
prevwin = next_prevwin;
last_status(FALSE); /* status line may appear or disappear */
@@ -3546,7 +3555,7 @@ end:
*/
void win_enter(win_T *wp, bool undo_sync)
{
- win_enter_ext(wp, undo_sync, FALSE, TRUE, TRUE);
+ win_enter_ext(wp, undo_sync, false, false, true, true);
}
/*
@@ -3554,7 +3563,9 @@ void win_enter(win_T *wp, bool undo_sync)
* Can be called with "curwin_invalid" TRUE, which means that curwin has just
* been closed and isn't valid.
*/
-static void win_enter_ext(win_T *wp, bool undo_sync, int curwin_invalid, int trigger_enter_autocmds, int trigger_leave_autocmds)
+static void win_enter_ext(win_T *wp, bool undo_sync, int curwin_invalid,
+ int trigger_new_autocmds, int trigger_enter_autocmds,
+ int trigger_leave_autocmds)
{
int other_buffer = FALSE;
@@ -3630,6 +3641,9 @@ static void win_enter_ext(win_T *wp, bool undo_sync, int curwin_invalid, int tri
shorten_fnames(TRUE);
}
+ if (trigger_new_autocmds) {
+ apply_autocmds(EVENT_WINNEW, NULL, NULL, false, curbuf);
+ }
if (trigger_enter_autocmds) {
apply_autocmds(EVENT_WINENTER, NULL, NULL, FALSE, curbuf);
if (other_buffer)