aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rwxr-xr-xsrc/clint.py14
-rw-r--r--src/nvim/README.md4
-rw-r--r--src/nvim/api/vim.c62
-rw-r--r--src/nvim/context.c383
-rw-r--r--src/nvim/context.h46
-rw-r--r--src/nvim/diff.c2
-rw-r--r--src/nvim/edit.c51
-rw-r--r--src/nvim/eval.c267
-rw-r--r--src/nvim/eval.h7
-rw-r--r--src/nvim/eval.lua5
-rw-r--r--src/nvim/eval/executor.c4
-rw-r--r--src/nvim/ex_cmds2.c17
-rw-r--r--src/nvim/ex_docmd.c2
-rw-r--r--src/nvim/ex_getln.c53
-rw-r--r--src/nvim/fileio.c10
-rw-r--r--src/nvim/getchar.c189
-rw-r--r--src/nvim/macros.h3
-rw-r--r--src/nvim/mark.c4
-rw-r--r--src/nvim/memory.c2
-rw-r--r--src/nvim/normal.c2
-rw-r--r--src/nvim/ops.c82
-rw-r--r--src/nvim/option.c6
-rw-r--r--src/nvim/popupmnu.c3
-rw-r--r--src/nvim/quickfix.c9
-rw-r--r--src/nvim/regexp.c4
-rw-r--r--src/nvim/screen.c1
-rw-r--r--src/nvim/search.c12
-rw-r--r--src/nvim/shada.c316
-rw-r--r--src/nvim/shada.h11
-rw-r--r--src/nvim/sign.c6
-rw-r--r--src/nvim/spell.c2
-rw-r--r--src/nvim/testdir/test_arglist.vim19
-rw-r--r--src/nvim/testdir/test_cmdline.vim12
-rw-r--r--src/nvim/testdir/test_getvar.vim44
-rw-r--r--src/nvim/testdir/test_maparg.vim4
-rw-r--r--src/nvim/testdir/test_partial.vim5
-rw-r--r--src/nvim/testdir/test_popup.vim34
-rw-r--r--src/nvim/testdir/test_vimscript.vim39
-rw-r--r--src/nvim/ui_compositor.c2
-rw-r--r--src/nvim/version.c13
-rw-r--r--src/nvim/viml/parser/expressions.c2
41 files changed, 1401 insertions, 352 deletions
diff --git a/src/clint.py b/src/clint.py
index 719142d523..3994ffbb14 100755
--- a/src/clint.py
+++ b/src/clint.py
@@ -48,7 +48,6 @@ from __future__ import unicode_literals
import codecs
import copy
-import fileinput
import getopt
import math # for log
import os
@@ -566,6 +565,7 @@ class _CppLintState(object):
return
self.record_errors_file = open(fname, 'w')
+
_cpplint_state = _CppLintState()
@@ -2123,7 +2123,7 @@ def CheckExpressionAlignment(filename, clean_lines, linenum, error, startpos=0):
'Inner expression indentation should be 4')
else:
if (pos != level_starts[depth][0] + 1
- + (level_starts[depth][2] == '{')):
+ + (level_starts[depth][2] == '{')):
if depth not in ignore_error_levels:
error(filename, linenum, 'whitespace/alignment', 2,
('Inner expression should be aligned '
@@ -2296,7 +2296,7 @@ def CheckSpacing(filename, clean_lines, linenum, nesting_state, error):
line = clean_lines.elided[linenum] # get rid of comments and strings
# Don't try to do spacing checks for operator methods
- line = re.sub(r'operator(==|!=|<|<<|<=|>=|>>|>)\(', 'operator\(', line)
+ line = re.sub(r'operator(==|!=|<|<<|<=|>=|>>|>)\(', r'operator\(', line)
# We allow no-spaces around = within an if: "if ( (a=Foo()) == 0 )".
# Otherwise not. Note we only check for non-spaces on *both* sides;
@@ -2597,8 +2597,8 @@ def CheckBraces(filename, clean_lines, linenum, error):
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')
+ '{ 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):
@@ -3178,8 +3178,8 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension,
if not Search(r'eval/typval\.[ch]$', filename):
match = Search(r'(?:\.|->)'
r'(?:lv_(?:first|last|refcount|len|watch|idx(?:_item)?'
- r'|copylist|lock)'
- r'|li_(?:next|prev|tv))\b', line)
+ r'|copylist|lock)'
+ r'|li_(?:next|prev|tv))\b', line)
if match:
error(filename, linenum, 'runtime/deprecated', 4,
'Accessing list_T internals directly is prohibited (hint: see commit d46e37cb4c71)')
diff --git a/src/nvim/README.md b/src/nvim/README.md
index 02464c2500..3c956cb2e9 100644
--- a/src/nvim/README.md
+++ b/src/nvim/README.md
@@ -60,9 +60,9 @@ Enable the sanitizer(s) via these environment variables:
# Change to detect_leaks=1 to detect memory leaks (slower).
export ASAN_OPTIONS="detect_leaks=0:log_path=$HOME/logs/asan"
- export ASAN_SYMBOLIZER_PATH=/usr/lib/llvm-5.0/bin/llvm-symbolizer
+ export ASAN_SYMBOLIZER_PATH=/usr/bin/llvm-symbolizer
- export MSAN_SYMBOLIZER_PATH=/usr/lib/llvm-5.0/bin/llvm-symbolizer
+ export MSAN_SYMBOLIZER_PATH=/usr/bin/llvm-symbolizer
export TSAN_OPTIONS="external_symbolizer_path=/usr/lib/llvm-5.0/bin/llvm-symbolizer log_path=${HOME}/logs/tsan"
Logs will be written to `${HOME}/logs/*san.PID`.
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index dbe3b66fd5..ed6a28bcda 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -21,6 +21,7 @@
#include "nvim/lua/executor.h"
#include "nvim/vim.h"
#include "nvim/buffer.h"
+#include "nvim/context.h"
#include "nvim/file_search.h"
#include "nvim/highlight.h"
#include "nvim/window.h"
@@ -1268,6 +1269,67 @@ Dictionary nvim_get_color_map(void)
return colors;
}
+/// Gets a map of the current editor state.
+///
+/// @param types Context types ("regs", "jumps", "buflist", "gvars", ...)
+/// to gather, or NIL for all.
+///
+/// @return map of global context
+Dictionary nvim_get_context(Array types)
+ FUNC_API_SINCE(6)
+{
+ int int_types = 0;
+ if (types.size == 1 && types.items[0].type == kObjectTypeNil) {
+ int_types = kCtxAll;
+ } else {
+ for (size_t i = 0; i < types.size; i++) {
+ if (types.items[i].type == kObjectTypeString) {
+ const char *const current = types.items[i].data.string.data;
+ if (strequal(current, "regs")) {
+ int_types |= kCtxRegs;
+ } else if (strequal(current, "jumps")) {
+ int_types |= kCtxJumps;
+ } else if (strequal(current, "buflist")) {
+ int_types |= kCtxBuflist;
+ } else if (strequal(current, "gvars")) {
+ int_types |= kCtxGVars;
+ } else if (strequal(current, "sfuncs")) {
+ int_types |= kCtxSFuncs;
+ } else if (strequal(current, "funcs")) {
+ int_types |= kCtxFuncs;
+ }
+ }
+ }
+ }
+
+ Context ctx = CONTEXT_INIT;
+ ctx_save(&ctx, int_types);
+ Dictionary dict = ctx_to_dict(&ctx);
+ ctx_free(&ctx);
+ return dict;
+}
+
+/// Sets the current editor state to that in given context dictionary.
+///
+/// @param ctx_dict Context dictionary.
+Object nvim_load_context(Dictionary dict)
+ FUNC_API_SINCE(6)
+{
+ Context ctx = CONTEXT_INIT;
+
+ int save_did_emsg = did_emsg;
+ did_emsg = false;
+
+ ctx_from_dict(dict, &ctx);
+ if (!did_emsg) {
+ ctx_restore(&ctx, kCtxAll);
+ }
+
+ ctx_free(&ctx);
+
+ did_emsg = save_did_emsg;
+ return (Object)OBJECT_INIT;
+}
/// Gets the current mode. |mode()|
/// "blocking" is true if Nvim is waiting for input.
diff --git a/src/nvim/context.c b/src/nvim/context.c
new file mode 100644
index 0000000000..b2a2fd3fd9
--- /dev/null
+++ b/src/nvim/context.c
@@ -0,0 +1,383 @@
+// This is an open source non-commercial project. Dear PVS-Studio, please check
+// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+
+// Context: snapshot of the entire editor state as one big object/map
+
+#include "nvim/context.h"
+#include "nvim/eval/encode.h"
+#include "nvim/ex_docmd.h"
+#include "nvim/option.h"
+#include "nvim/shada.h"
+#include "nvim/api/vim.h"
+#include "nvim/api/private/helpers.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "context.c.generated.h"
+#endif
+
+int kCtxAll = (kCtxRegs | kCtxJumps | kCtxBuflist | kCtxGVars | kCtxSFuncs
+ | kCtxFuncs);
+
+static ContextVec ctx_stack = KV_INITIAL_VALUE;
+
+/// Clears and frees the context stack
+void ctx_free_all(void)
+{
+ for (size_t i = 0; i < kv_size(ctx_stack); i++) {
+ ctx_free(&kv_A(ctx_stack, i));
+ }
+ kv_destroy(ctx_stack);
+}
+
+/// Returns the size of the context stack.
+size_t ctx_size(void)
+{
+ return kv_size(ctx_stack);
+}
+
+/// Returns pointer to Context object with given zero-based index from the top
+/// of context stack or NULL if index is out of bounds.
+Context *ctx_get(size_t index)
+{
+ if (index < kv_size(ctx_stack)) {
+ return &kv_Z(ctx_stack, index);
+ }
+ return NULL;
+}
+
+/// Free resources used by Context object.
+///
+/// param[in] ctx pointer to Context object to free.
+void ctx_free(Context *ctx)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (ctx->regs.data) {
+ msgpack_sbuffer_destroy(&ctx->regs);
+ }
+ if (ctx->jumps.data) {
+ msgpack_sbuffer_destroy(&ctx->jumps);
+ }
+ if (ctx->buflist.data) {
+ msgpack_sbuffer_destroy(&ctx->buflist);
+ }
+ if (ctx->gvars.data) {
+ msgpack_sbuffer_destroy(&ctx->gvars);
+ }
+ if (ctx->funcs.items) {
+ api_free_array(ctx->funcs);
+ }
+}
+
+/// Saves the editor state to a context.
+///
+/// If "context" is NULL, pushes context on context stack.
+/// Use "flags" to select particular types of context.
+///
+/// @param ctx Save to this context, or push on context stack if NULL.
+/// @param flags Flags, see ContextTypeFlags enum.
+void ctx_save(Context *ctx, const int flags)
+{
+ if (ctx == NULL) {
+ kv_push(ctx_stack, CONTEXT_INIT);
+ ctx = &kv_last(ctx_stack);
+ }
+
+ if (flags & kCtxRegs) {
+ ctx_save_regs(ctx);
+ }
+
+ if (flags & kCtxJumps) {
+ ctx_save_jumps(ctx);
+ }
+
+ if (flags & kCtxBuflist) {
+ ctx_save_buflist(ctx);
+ }
+
+ if (flags & kCtxGVars) {
+ ctx_save_gvars(ctx);
+ }
+
+ if (flags & kCtxFuncs) {
+ ctx_save_funcs(ctx, false);
+ } else if (flags & kCtxSFuncs) {
+ ctx_save_funcs(ctx, true);
+ }
+}
+
+/// Restores the editor state from a context.
+///
+/// If "context" is NULL, pops context from context stack.
+/// Use "flags" to select particular types of context.
+///
+/// @param ctx Restore from this context. Pop from context stack if NULL.
+/// @param flags Flags, see ContextTypeFlags enum.
+///
+/// @return true on success, false otherwise (i.e.: empty context stack).
+bool ctx_restore(Context *ctx, const int flags)
+{
+ bool free_ctx = false;
+ if (ctx == NULL) {
+ if (ctx_stack.size == 0) {
+ return false;
+ }
+ ctx = &kv_pop(ctx_stack);
+ free_ctx = true;
+ }
+
+ char_u *op_shada;
+ get_option_value((char_u *)"shada", NULL, &op_shada, OPT_GLOBAL);
+ set_option_value("shada", 0L, "!,'100,%", OPT_GLOBAL);
+
+ if (flags & kCtxRegs) {
+ ctx_restore_regs(ctx);
+ }
+
+ if (flags & kCtxJumps) {
+ ctx_restore_jumps(ctx);
+ }
+
+ if (flags & kCtxBuflist) {
+ ctx_restore_buflist(ctx);
+ }
+
+ if (flags & kCtxGVars) {
+ ctx_restore_gvars(ctx);
+ }
+
+ if (flags & kCtxFuncs) {
+ ctx_restore_funcs(ctx);
+ }
+
+ if (free_ctx) {
+ ctx_free(ctx);
+ }
+
+ set_option_value("shada", 0L, (char *)op_shada, OPT_GLOBAL);
+ xfree(op_shada);
+
+ return true;
+}
+
+/// Saves the global registers to a context.
+///
+/// @param ctx Save to this context.
+static inline void ctx_save_regs(Context *ctx)
+ FUNC_ATTR_NONNULL_ALL
+{
+ msgpack_sbuffer_init(&ctx->regs);
+ shada_encode_regs(&ctx->regs);
+}
+
+/// Restores the global registers from a context.
+///
+/// @param ctx Restore from this context.
+static inline void ctx_restore_regs(Context *ctx)
+ FUNC_ATTR_NONNULL_ALL
+{
+ shada_read_sbuf(&ctx->regs, kShaDaWantInfo | kShaDaForceit);
+}
+
+/// Saves the jumplist to a context.
+///
+/// @param ctx Save to this context.
+static inline void ctx_save_jumps(Context *ctx)
+ FUNC_ATTR_NONNULL_ALL
+{
+ msgpack_sbuffer_init(&ctx->jumps);
+ shada_encode_jumps(&ctx->jumps);
+}
+
+/// Restores the jumplist from a context.
+///
+/// @param ctx Restore from this context.
+static inline void ctx_restore_jumps(Context *ctx)
+ FUNC_ATTR_NONNULL_ALL
+{
+ shada_read_sbuf(&ctx->jumps, kShaDaWantInfo | kShaDaForceit);
+}
+
+/// Saves the buffer list to a context.
+///
+/// @param ctx Save to this context.
+static inline void ctx_save_buflist(Context *ctx)
+ FUNC_ATTR_NONNULL_ALL
+{
+ msgpack_sbuffer_init(&ctx->buflist);
+ shada_encode_buflist(&ctx->buflist);
+}
+
+/// Restores the buffer list from a context.
+///
+/// @param ctx Restore from this context.
+static inline void ctx_restore_buflist(Context *ctx)
+ FUNC_ATTR_NONNULL_ALL
+{
+ shada_read_sbuf(&ctx->buflist, kShaDaWantInfo | kShaDaForceit);
+}
+
+/// Saves global variables to a context.
+///
+/// @param ctx Save to this context.
+static inline void ctx_save_gvars(Context *ctx)
+ FUNC_ATTR_NONNULL_ALL
+{
+ msgpack_sbuffer_init(&ctx->gvars);
+ shada_encode_gvars(&ctx->gvars);
+}
+
+/// Restores global variables from a context.
+///
+/// @param ctx Restore from this context.
+static inline void ctx_restore_gvars(Context *ctx)
+ FUNC_ATTR_NONNULL_ALL
+{
+ shada_read_sbuf(&ctx->gvars, kShaDaWantInfo | kShaDaForceit);
+}
+
+/// Saves functions to a context.
+///
+/// @param ctx Save to this context.
+/// @param scriptonly Save script-local (s:) functions only.
+static inline void ctx_save_funcs(Context *ctx, bool scriptonly)
+ FUNC_ATTR_NONNULL_ALL
+{
+ ctx->funcs = (Array)ARRAY_DICT_INIT;
+ Error err = ERROR_INIT;
+
+ HASHTAB_ITER(&func_hashtab, hi, {
+ const char_u *const name = hi->hi_key;
+ bool islambda = (STRNCMP(name, "<lambda>", 8) == 0);
+ bool isscript = (name[0] == K_SPECIAL);
+
+ if (!islambda && (!scriptonly || isscript)) {
+ size_t cmd_len = sizeof("func! ") + STRLEN(name);
+ char *cmd = xmalloc(cmd_len);
+ snprintf(cmd, cmd_len, "func! %s", name);
+ String func_body = nvim_command_output(cstr_as_string(cmd), &err);
+ xfree(cmd);
+ if (!ERROR_SET(&err)) {
+ ADD(ctx->funcs, STRING_OBJ(func_body));
+ }
+ api_clear_error(&err);
+ }
+ });
+}
+
+/// Restores functions from a context.
+///
+/// @param ctx Restore from this context.
+static inline void ctx_restore_funcs(Context *ctx)
+ FUNC_ATTR_NONNULL_ALL
+{
+ for (size_t i = 0; i < ctx->funcs.size; i++) {
+ do_cmdline_cmd(ctx->funcs.items[i].data.string.data);
+ }
+}
+
+/// Convert msgpack_sbuffer to readfile()-style array.
+///
+/// @param[in] sbuf msgpack_sbuffer to convert.
+///
+/// @return readfile()-style array representation of "sbuf".
+static inline Array sbuf_to_array(msgpack_sbuffer sbuf)
+{
+ list_T *const list = tv_list_alloc(kListLenMayKnow);
+ tv_list_append_string(list, "", 0);
+ if (sbuf.size > 0) {
+ encode_list_write(list, sbuf.data, sbuf.size);
+ }
+
+ typval_T list_tv = (typval_T) {
+ .v_lock = VAR_UNLOCKED,
+ .v_type = VAR_LIST,
+ .vval.v_list = list
+ };
+
+ Array array = vim_to_object(&list_tv).data.array;
+ tv_clear(&list_tv);
+ return array;
+}
+
+/// Convert readfile()-style array to msgpack_sbuffer.
+///
+/// @param[in] array readfile()-style array to convert.
+///
+/// @return msgpack_sbuffer with conversion result.
+static inline msgpack_sbuffer array_to_sbuf(Array array)
+{
+ msgpack_sbuffer sbuf;
+ msgpack_sbuffer_init(&sbuf);
+
+ typval_T list_tv;
+ Error err = ERROR_INIT;
+ object_to_vim(ARRAY_OBJ(array), &list_tv, &err);
+
+ if (!encode_vim_list_to_buf(list_tv.vval.v_list, &sbuf.size, &sbuf.data)) {
+ EMSG(_("E474: Failed to convert list to msgpack string buffer"));
+ }
+ sbuf.alloc = sbuf.size;
+
+ tv_clear(&list_tv);
+ api_clear_error(&err);
+ return sbuf;
+}
+
+/// Converts Context to Dictionary representation.
+///
+/// @param[in] ctx Context to convert.
+///
+/// @return Dictionary representing "ctx".
+Dictionary ctx_to_dict(Context *ctx)
+ FUNC_ATTR_NONNULL_ALL
+{
+ assert(ctx != NULL);
+
+ Dictionary rv = ARRAY_DICT_INIT;
+
+ PUT(rv, "regs", ARRAY_OBJ(sbuf_to_array(ctx->regs)));
+ PUT(rv, "jumps", ARRAY_OBJ(sbuf_to_array(ctx->jumps)));
+ PUT(rv, "buflist", ARRAY_OBJ(sbuf_to_array(ctx->buflist)));
+ PUT(rv, "gvars", ARRAY_OBJ(sbuf_to_array(ctx->gvars)));
+ PUT(rv, "funcs", ARRAY_OBJ(copy_array(ctx->funcs)));
+
+ return rv;
+}
+
+/// Converts Dictionary representation of Context back to Context object.
+///
+/// @param[in] dict Context Dictionary representation.
+/// @param[out] ctx Context object to store conversion result into.
+///
+/// @return types of included context items.
+int ctx_from_dict(Dictionary dict, Context *ctx)
+ FUNC_ATTR_NONNULL_ALL
+{
+ assert(ctx != NULL);
+
+ int types = 0;
+ for (size_t i = 0; i < dict.size; i++) {
+ KeyValuePair item = dict.items[i];
+ if (item.value.type != kObjectTypeArray) {
+ continue;
+ }
+ if (strequal(item.key.data, "regs")) {
+ types |= kCtxRegs;
+ ctx->regs = array_to_sbuf(item.value.data.array);
+ } else if (strequal(item.key.data, "jumps")) {
+ types |= kCtxJumps;
+ ctx->jumps = array_to_sbuf(item.value.data.array);
+ } else if (strequal(item.key.data, "buflist")) {
+ types |= kCtxBuflist;
+ ctx->buflist = array_to_sbuf(item.value.data.array);
+ } else if (strequal(item.key.data, "gvars")) {
+ types |= kCtxGVars;
+ ctx->gvars = array_to_sbuf(item.value.data.array);
+ } else if (strequal(item.key.data, "funcs")) {
+ types |= kCtxFuncs;
+ ctx->funcs = copy_object(item.value).data.array;
+ }
+ }
+
+ return types;
+}
diff --git a/src/nvim/context.h b/src/nvim/context.h
new file mode 100644
index 0000000000..328e12c6a6
--- /dev/null
+++ b/src/nvim/context.h
@@ -0,0 +1,46 @@
+#ifndef NVIM_CONTEXT_H
+#define NVIM_CONTEXT_H
+
+#include <msgpack.h>
+#include "nvim/api/private/defs.h"
+#include "nvim/lib/kvec.h"
+
+typedef struct {
+ msgpack_sbuffer regs; ///< Registers.
+ msgpack_sbuffer jumps; ///< Jumplist.
+ msgpack_sbuffer buflist; ///< Buffer list.
+ msgpack_sbuffer gvars; ///< Global variables.
+ Array funcs; ///< Functions.
+} Context;
+typedef kvec_t(Context) ContextVec;
+
+#define MSGPACK_SBUFFER_INIT (msgpack_sbuffer) { \
+ .size = 0, \
+ .data = NULL, \
+ .alloc = 0, \
+}
+
+#define CONTEXT_INIT (Context) { \
+ .regs = MSGPACK_SBUFFER_INIT, \
+ .jumps = MSGPACK_SBUFFER_INIT, \
+ .buflist = MSGPACK_SBUFFER_INIT, \
+ .gvars = MSGPACK_SBUFFER_INIT, \
+ .funcs = ARRAY_DICT_INIT, \
+}
+
+typedef enum {
+ kCtxRegs = 1, ///< Registers
+ kCtxJumps = 2, ///< Jumplist
+ kCtxBuflist = 4, ///< Buffer list
+ kCtxGVars = 8, ///< Global variables
+ kCtxSFuncs = 16, ///< Script functions
+ kCtxFuncs = 32, ///< Functions
+} ContextTypeFlags;
+
+extern int kCtxAll;
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "context.h.generated.h"
+#endif
+
+#endif // NVIM_CONTEXT_H
diff --git a/src/nvim/diff.c b/src/nvim/diff.c
index f2b3abb526..4176769f85 100644
--- a/src/nvim/diff.c
+++ b/src/nvim/diff.c
@@ -2190,7 +2190,7 @@ int diffopt_changed(void)
}
diff_flags = diff_flags_new;
- diff_context = diff_context_new;
+ diff_context = diff_context_new == 0 ? 1 : diff_context_new;
diff_foldcolumn = diff_foldcolumn_new;
diff_algorithm = diff_algorithm_new;
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index 3305d281bd..5a9549cc89 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -2580,10 +2580,35 @@ static bool pum_enough_matches(void)
return i >= 2;
}
-/*
- * Show the popup menu for the list of matches.
- * Also adjusts "compl_shown_match" to an entry that is actually displayed.
- */
+static void trigger_complete_changed_event(int cur)
+{
+ static bool recursive = false;
+
+ if (recursive) {
+ return;
+ }
+
+ dict_T *v_event = get_vim_var_dict(VV_EVENT);
+ if (cur < 0) {
+ tv_dict_add_dict(v_event, S_LEN("completed_item"), tv_dict_alloc());
+ } else {
+ dict_T *item = ins_compl_dict_alloc(compl_curr_match);
+ tv_dict_add_dict(v_event, S_LEN("completed_item"), item);
+ }
+ pum_set_event_info(v_event);
+ tv_dict_set_keys_readonly(v_event);
+
+ recursive = true;
+ textlock++;
+ apply_autocmds(EVENT_COMPLETECHANGED, NULL, NULL, false, curbuf);
+ textlock--;
+ recursive = false;
+
+ tv_dict_clear(v_event);
+}
+
+/// Show the popup menu for the list of matches.
+/// Also adjusts "compl_shown_match" to an entry that is actually displayed.
void ins_compl_show_pum(void)
{
compl_T *compl;
@@ -2715,22 +2740,9 @@ void ins_compl_show_pum(void)
pum_display(compl_match_array, compl_match_arraysize, cur, array_changed, 0);
curwin->w_cursor.col = col;
- if (!has_event(EVENT_COMPLETECHANGED)) {
- return;
+ if (has_event(EVENT_COMPLETECHANGED)) {
+ trigger_complete_changed_event(cur);
}
- dict_T *dict = get_vim_var_dict(VV_EVENT);
- if (cur < 0) {
- tv_dict_add_dict(dict, S_LEN("completed_item"), tv_dict_alloc());
- } else {
- dict_T *item = ins_compl_dict_alloc(compl_curr_match);
- tv_dict_add_dict(dict, S_LEN("completed_item"), item);
- }
- pum_set_boundings(dict);
- tv_dict_set_keys_readonly(dict);
- textlock++;
- apply_autocmds(EVENT_COMPLETECHANGED, NULL, NULL, false, curbuf);
- textlock--;
- tv_dict_clear(dict);
}
#define DICT_FIRST (1) /* use just first element in "dict" */
@@ -3110,6 +3122,7 @@ void get_complete_info(list_T *what_list, dict_T *retdict)
? compl_curr_match->cp_number - 1 : -1);
}
+ (void)ret;
// TODO(vim):
// if (ret == OK && (what_flag & CI_WHAT_INSERTED))
}
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index f91552bc1c..cefd351dd7 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -26,6 +26,7 @@
#include "nvim/buffer.h"
#include "nvim/channel.h"
#include "nvim/charset.h"
+#include "nvim/context.h"
#include "nvim/cursor.h"
#include "nvim/diff.h"
#include "nvim/edit.h"
@@ -303,15 +304,6 @@ typedef struct {
list_T *fi_list; /* list being used */
} forinfo_T;
-/*
- * enum used by var_flavour()
- */
-typedef enum {
- VAR_FLAVOUR_DEFAULT, /* doesn't start with uppercase */
- VAR_FLAVOUR_SESSION, /* starts with uppercase, some lower */
- VAR_FLAVOUR_SHADA /* all uppercase */
-} var_flavour_T;
-
/* values for vv_flags: */
#define VV_COMPAT 1 /* compatible, also used without "v:" */
#define VV_RO 2 /* read-only */
@@ -534,6 +526,35 @@ const list_T *eval_msgpack_type_lists[] = {
[kMPExt] = NULL,
};
+// Return "n1" divided by "n2", taking care of dividing by zero.
+varnumber_T num_divide(varnumber_T n1, varnumber_T n2)
+ FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ varnumber_T result;
+
+ if (n2 == 0) { // give an error message?
+ if (n1 == 0) {
+ result = VARNUMBER_MIN; // similar to NaN
+ } else if (n1 < 0) {
+ result = -VARNUMBER_MAX;
+ } else {
+ result = VARNUMBER_MAX;
+ }
+ } else {
+ result = n1 / n2;
+ }
+
+ return result;
+}
+
+// Return "n1" modulus "n2", taking care of dividing by zero.
+varnumber_T num_modulus(varnumber_T n1, varnumber_T n2)
+ FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ // Give an error when n2 is 0?
+ return (n2 == 0) ? 0 : (n1 % n2);
+}
+
/*
* Initialize the global and v: variables.
*/
@@ -2019,8 +2040,8 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv,
case '+': n = numval + n; break;
case '-': n = numval - n; break;
case '*': n = numval * n; break;
- case '/': n = numval / n; break;
- case '%': n = numval % n; break;
+ case '/': n = num_divide(numval, n); break;
+ case '%': n = num_modulus(numval, n); break;
}
} else if (opt_type == 0 && stringval != NULL) { // string
char *const oldstringval = stringval;
@@ -4150,22 +4171,9 @@ static int eval6(char_u **arg, typval_T *rettv, int evaluate, int want_string)
if (op == '*') {
n1 = n1 * n2;
} else if (op == '/') {
- if (n2 == 0) { // give an error message?
- if (n1 == 0) {
- n1 = VARNUMBER_MIN; // similar to NaN
- } else if (n1 < 0) {
- n1 = -VARNUMBER_MAX;
- } else {
- n1 = VARNUMBER_MAX;
- }
- } else {
- n1 = n1 / n2;
- }
+ n1 = num_divide(n1, n2);
} else {
- if (n2 == 0) /* give an error message? */
- n1 = 0;
- else
- n1 = n1 % n2;
+ n1 = num_modulus(n1, n2);
}
rettv->v_type = VAR_NUMBER;
rettv->vval.v_number = n1;
@@ -5225,7 +5233,7 @@ bool garbage_collect(bool testing)
yankreg_T reg;
char name = NUL;
bool is_unnamed = false;
- reg_iter = op_register_iter(reg_iter, &name, &reg, &is_unnamed);
+ reg_iter = op_global_reg_iter(reg_iter, &name, &reg, &is_unnamed);
if (name != NUL) {
ABORTING(set_ref_dict)(reg.additional_data, copyID);
}
@@ -7987,6 +7995,116 @@ static void f_cscope_connection(typval_T *argvars, typval_T *rettv, FunPtr fptr)
(char_u *)prepend);
}
+/// "ctxget([{index}])" function
+static void f_ctxget(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ size_t index = 0;
+ if (argvars[0].v_type == VAR_NUMBER) {
+ index = argvars[0].vval.v_number;
+ } else if (argvars[0].v_type != VAR_UNKNOWN) {
+ EMSG2(_(e_invarg2), "expected nothing or a Number as an argument");
+ return;
+ }
+
+ Context *ctx = ctx_get(index);
+ if (ctx == NULL) {
+ EMSG3(_(e_invargNval), "index", "out of bounds");
+ return;
+ }
+
+ Dictionary ctx_dict = ctx_to_dict(ctx);
+ Error err = ERROR_INIT;
+ object_to_vim(DICTIONARY_OBJ(ctx_dict), rettv, &err);
+ api_free_dictionary(ctx_dict);
+ api_clear_error(&err);
+}
+
+/// "ctxpop()" function
+static void f_ctxpop(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (!ctx_restore(NULL, kCtxAll)) {
+ EMSG(_("Context stack is empty"));
+ }
+}
+
+/// "ctxpush([{types}])" function
+static void f_ctxpush(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int types = kCtxAll;
+ if (argvars[0].v_type == VAR_LIST) {
+ types = 0;
+ TV_LIST_ITER(argvars[0].vval.v_list, li, {
+ typval_T *tv_li = TV_LIST_ITEM_TV(li);
+ if (tv_li->v_type == VAR_STRING) {
+ if (strequal((char *)tv_li->vval.v_string, "regs")) {
+ types |= kCtxRegs;
+ } else if (strequal((char *)tv_li->vval.v_string, "jumps")) {
+ types |= kCtxJumps;
+ } else if (strequal((char *)tv_li->vval.v_string, "buflist")) {
+ types |= kCtxBuflist;
+ } else if (strequal((char *)tv_li->vval.v_string, "gvars")) {
+ types |= kCtxGVars;
+ } else if (strequal((char *)tv_li->vval.v_string, "sfuncs")) {
+ types |= kCtxSFuncs;
+ } else if (strequal((char *)tv_li->vval.v_string, "funcs")) {
+ types |= kCtxFuncs;
+ }
+ }
+ });
+ } else if (argvars[0].v_type != VAR_UNKNOWN) {
+ EMSG2(_(e_invarg2), "expected nothing or a List as an argument");
+ return;
+ }
+ ctx_save(NULL, types);
+}
+
+/// "ctxset({context}[, {index}])" function
+static void f_ctxset(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (argvars[0].v_type != VAR_DICT) {
+ EMSG2(_(e_invarg2), "expected dictionary as first argument");
+ return;
+ }
+
+ size_t index = 0;
+ if (argvars[1].v_type == VAR_NUMBER) {
+ index = argvars[1].vval.v_number;
+ } else if (argvars[1].v_type != VAR_UNKNOWN) {
+ EMSG2(_(e_invarg2), "expected nothing or a Number as second argument");
+ return;
+ }
+
+ Context *ctx = ctx_get(index);
+ if (ctx == NULL) {
+ EMSG3(_(e_invargNval), "index", "out of bounds");
+ return;
+ }
+
+ int save_did_emsg = did_emsg;
+ did_emsg = false;
+
+ Dictionary dict = vim_to_object(&argvars[0]).data.dictionary;
+ Context tmp = CONTEXT_INIT;
+ ctx_from_dict(dict, &tmp);
+
+ if (did_emsg) {
+ ctx_free(&tmp);
+ } else {
+ ctx_free(ctx);
+ *ctx = tmp;
+ }
+
+ api_free_dictionary(dict);
+ did_emsg = save_did_emsg;
+}
+
+/// "ctxsize()" function
+static void f_ctxsize(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = ctx_size();
+}
+
/// "cursor(lnum, col)" function, or
/// "cursor(list)"
///
@@ -9462,6 +9580,7 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr)
dictitem_T *di;
dict_T *d;
typval_T *tv = NULL;
+ bool what_is_dict = false;
if (argvars[0].v_type == VAR_LIST) {
if ((l = argvars[0].vval.v_list) != NULL) {
@@ -9503,7 +9622,10 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr)
func_ref(rettv->vval.v_string);
}
} else if (strcmp(what, "dict") == 0) {
- tv_dict_set_ret(rettv, pt->pt_dict);
+ what_is_dict = true;
+ if (pt->pt_dict != NULL) {
+ tv_dict_set_ret(rettv, pt->pt_dict);
+ }
} else if (strcmp(what, "args") == 0) {
rettv->v_type = VAR_LIST;
if (tv_list_alloc_ret(rettv, pt->pt_argc) != NULL) {
@@ -9514,7 +9636,12 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr)
} else {
EMSG2(_(e_invarg2), what);
}
- return;
+
+ // When {what} == "dict" and pt->pt_dict == NULL, evaluate the
+ // third argument
+ if (!what_is_dict) {
+ return;
+ }
}
} else {
EMSG2(_(e_listdictarg), "get()");
@@ -15037,15 +15164,15 @@ static void set_buffer_lines(buf_T *buf, linenr_T lnum, typval_T *lines,
}
rettv->vval.v_number = 1; // FAIL
- if (line == NULL || lnum < 1 || lnum > curbuf->b_ml.ml_line_count + 1) {
+ if (line == NULL || lnum > curbuf->b_ml.ml_line_count + 1) {
break;
}
- /* When coming here from Insert mode, sync undo, so that this can be
- * undone separately from what was previously inserted. */
+ // When coming here from Insert mode, sync undo, so that this can be
+ // undone separately from what was previously inserted.
if (u_sync_once == 2) {
- u_sync_once = 1; /* notify that u_sync() was called */
- u_sync(TRUE);
+ u_sync_once = 1; // notify that u_sync() was called
+ u_sync(true);
}
if (lnum <= curbuf->b_ml.ml_line_count) {
@@ -15582,7 +15709,7 @@ free_lstval:
if (set_unnamed) {
// Discard the result. We already handle the error case.
- if (op_register_set_previous(regname)) { }
+ if (op_reg_set_previous(regname)) { }
}
}
@@ -15869,10 +15996,12 @@ static void f_sign_getplaced(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
if ((di = tv_dict_find(dict, "lnum", -1)) != NULL) {
// get signs placed at this line
- lnum = tv_get_lnum(&di->di_tv);
- if (lnum <= 0) {
+ lnum = (linenr_T)tv_get_number_chk(&di->di_tv, &notanum);
+ if (notanum) {
return;
}
+ (void)lnum;
+ lnum = tv_get_lnum(&di->di_tv);
}
if ((di = tv_dict_find(dict, "id", -1)) != NULL) {
// get sign placed with this identifier
@@ -15926,9 +16055,6 @@ static void f_sign_jump(typval_T *argvars, typval_T *rettv, FunPtr fptr)
sign_group = NULL; // global sign group
} else {
sign_group = xstrdup(sign_group_chk);
- if (sign_group == NULL) {
- return;
- }
}
// Buffer to place the sign
@@ -15977,9 +16103,6 @@ static void f_sign_place(typval_T *argvars, typval_T *rettv, FunPtr fptr)
group = NULL; // global sign group
} else {
group = vim_strsave((const char_u *)group_chk);
- if (group == NULL) {
- return;
- }
}
// Sign name
@@ -16007,6 +16130,7 @@ static void f_sign_place(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (notanum) {
goto cleanup;
}
+ (void)lnum;
lnum = tv_get_lnum(&di->di_tv);
}
if ((di = tv_dict_find(dict, "priority", -1)) != NULL) {
@@ -16072,9 +16196,6 @@ static void f_sign_unplace(typval_T *argvars, typval_T *rettv, FunPtr fptr)
group = NULL; // global sign group
} else {
group = vim_strsave((const char_u *)group_chk);
- if (group == NULL) {
- return;
- }
}
if (argvars[1].v_type != VAR_UNKNOWN) {
@@ -20847,7 +20968,7 @@ void ex_function(exarg_T *eap)
continue;
}
if (!func_name_refcount(fp->uf_name)) {
- list_func_head(fp, false);
+ list_func_head(fp, false, false);
}
}
}
@@ -20878,7 +20999,7 @@ void ex_function(exarg_T *eap)
fp = HI2UF(hi);
if (!isdigit(*fp->uf_name)
&& vim_regexec(&regmatch, fp->uf_name, 0))
- list_func_head(fp, FALSE);
+ list_func_head(fp, false, false);
}
}
vim_regfree(regmatch.regprog);
@@ -20928,9 +21049,12 @@ void ex_function(exarg_T *eap)
saved_did_emsg = did_emsg;
did_emsg = FALSE;
- /*
- * ":function func" with only function name: list function.
- */
+ //
+ // ":function func" with only function name: list function.
+ // If bang is given:
+ // - include "!" in function head
+ // - exclude line numbers from function body
+ //
if (!paren) {
if (!ends_excmd(*skipwhite(p))) {
EMSG(_(e_trailing));
@@ -20942,17 +21066,20 @@ void ex_function(exarg_T *eap)
if (!eap->skip && !got_int) {
fp = find_func(name);
if (fp != NULL) {
- list_func_head(fp, TRUE);
- for (int j = 0; j < fp->uf_lines.ga_len && !got_int; ++j) {
- if (FUNCLINE(fp, j) == NULL)
+ list_func_head(fp, !eap->forceit, eap->forceit);
+ for (int j = 0; j < fp->uf_lines.ga_len && !got_int; j++) {
+ if (FUNCLINE(fp, j) == NULL) {
continue;
- msg_putchar('\n');
- msg_outnum((long)j + 1);
- if (j < 9) {
- msg_putchar(' ');
}
- if (j < 99) {
- msg_putchar(' ');
+ msg_putchar('\n');
+ if (!eap->forceit) {
+ msg_outnum((long)j + 1);
+ if (j < 9) {
+ msg_putchar(' ');
+ }
+ if (j < 99) {
+ msg_putchar(' ');
+ }
}
msg_prt_line(FUNCLINE(fp, j), false);
ui_flush(); // show a line at a time
@@ -20960,7 +21087,7 @@ void ex_function(exarg_T *eap)
}
if (!got_int) {
msg_putchar('\n');
- msg_puts(" endfunction");
+ msg_puts(eap->forceit ? "endfunction" : " endfunction");
}
} else
emsg_funcname(N_("E123: Undefined function: %s"), name);
@@ -21650,15 +21777,17 @@ static inline bool eval_fname_sid(const char *const name)
return *name == 's' || TOUPPER_ASC(name[2]) == 'I';
}
-/*
- * List the head of the function: "name(arg1, arg2)".
- */
-static void list_func_head(ufunc_T *fp, int indent)
+/// List the head of the function: "name(arg1, arg2)".
+///
+/// @param[in] fp Function pointer.
+/// @param[in] indent Indent line.
+/// @param[in] force Include bang "!" (i.e.: "function!").
+static void list_func_head(ufunc_T *fp, int indent, bool force)
{
msg_start();
if (indent)
MSG_PUTS(" ");
- MSG_PUTS("function ");
+ MSG_PUTS(force ? "function! " : "function ");
if (fp->uf_name[0] == K_SPECIAL) {
MSG_PUTS_ATTR("<SNR>", HL_ATTR(HLF_8));
msg_puts((const char *)fp->uf_name + 3);
@@ -23242,7 +23371,7 @@ dictitem_T *find_var_in_scoped_ht(const char *name, const size_t namelen,
/// @return Pointer that needs to be passed to next `var_shada_iter` invocation
/// or NULL to indicate that iteration is over.
const void *var_shada_iter(const void *const iter, const char **const name,
- typval_T *rettv)
+ typval_T *rettv, var_flavour_T flavour)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(2, 3)
{
const hashitem_T *hi;
@@ -23253,7 +23382,7 @@ const void *var_shada_iter(const void *const iter, const char **const name,
hi = globvarht.ht_array;
while ((size_t) (hi - hifirst) < hinum
&& (HASHITEM_EMPTY(hi)
- || var_flavour(hi->hi_key) != VAR_FLAVOUR_SHADA)) {
+ || !(var_flavour(hi->hi_key) & flavour))) {
hi++;
}
if ((size_t) (hi - hifirst) == hinum) {
@@ -23265,7 +23394,7 @@ const void *var_shada_iter(const void *const iter, const char **const name,
*name = (char *)TV_DICT_HI2DI(hi)->di_key;
tv_copy(&TV_DICT_HI2DI(hi)->di_tv, rettv);
while ((size_t)(++hi - hifirst) < hinum) {
- if (!HASHITEM_EMPTY(hi) && var_flavour(hi->hi_key) == VAR_FLAVOUR_SHADA) {
+ if (!HASHITEM_EMPTY(hi) && (var_flavour(hi->hi_key) & flavour)) {
return hi;
}
}
diff --git a/src/nvim/eval.h b/src/nvim/eval.h
index 149dae688e..abe032a96e 100644
--- a/src/nvim/eval.h
+++ b/src/nvim/eval.h
@@ -24,6 +24,13 @@ EXTERN ufunc_T dumuf;
#define HIKEY2UF(p) ((ufunc_T *)(p - offsetof(ufunc_T, uf_name)))
#define HI2UF(hi) HIKEY2UF((hi)->hi_key)
+/// enum used by var_flavour()
+typedef enum {
+ VAR_FLAVOUR_DEFAULT = 1, // doesn't start with uppercase
+ VAR_FLAVOUR_SESSION = 2, // starts with uppercase, some lower
+ VAR_FLAVOUR_SHADA = 4 // all uppercase
+} var_flavour_T;
+
/// Defines for Vim variables
typedef enum {
VV_COUNT,
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index 089b08d5d1..0b77a24f7a 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -74,6 +74,11 @@ return {
cosh={args=1, func="float_op_wrapper", data="&cosh"},
count={args={2, 4}},
cscope_connection={args={0, 3}},
+ ctxget={args={0, 1}},
+ ctxpop={},
+ ctxpush={args={0, 1}},
+ ctxset={args={1, 2}},
+ ctxsize={},
cursor={args={1, 3}},
deepcopy={args={1, 2}},
delete={args={1,2}},
diff --git a/src/nvim/eval/executor.c b/src/nvim/eval/executor.c
index e972c506dd..8cd21f8d62 100644
--- a/src/nvim/eval/executor.c
+++ b/src/nvim/eval/executor.c
@@ -74,8 +74,8 @@ int eexe_mod_op(typval_T *const tv1, const typval_T *const tv2,
case '+': n += tv_get_number(tv2); break;
case '-': n -= tv_get_number(tv2); break;
case '*': n *= tv_get_number(tv2); break;
- case '/': n /= tv_get_number(tv2); break;
- case '%': n %= tv_get_number(tv2); break;
+ case '/': n = num_divide(n, tv_get_number(tv2)); break;
+ case '%': n = num_modulus(n, tv_get_number(tv2)); break;
}
tv_clear(tv1);
tv1->v_type = VAR_NUMBER;
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index cd0b05c6c9..73e6f85627 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -1793,17 +1793,14 @@ void ex_args(exarg_T *eap)
// ":args": list arguments.
if (ARGCOUNT > 0) {
char_u **items = xmalloc(sizeof(char_u *) * (size_t)ARGCOUNT);
-
- if (items != NULL) {
- // Overwrite the command, for a short list there is no scrolling
- // required and no wait_return().
- gotocmdline(true);
- for (int i = 0; i < ARGCOUNT; i++) {
- items[i] = alist_name(&ARGLIST[i]);
- }
- list_in_columns(items, ARGCOUNT, curwin->w_arg_idx);
- xfree(items);
+ // Overwrite the command, for a short list there is no scrolling
+ // required and no wait_return().
+ gotocmdline(true);
+ for (int i = 0; i < ARGCOUNT; i++) {
+ items[i] = alist_name(&ARGLIST[i]);
}
+ list_in_columns(items, ARGCOUNT, curwin->w_arg_idx);
+ xfree(items);
}
} else if (eap->cmdidx == CMD_arglocal) {
garray_T *gap = &curwin->w_alist->al_ga;
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index c8edf7173e..5365270e0b 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -7520,7 +7520,7 @@ static void ex_operators(exarg_T *eap)
case CMD_yank:
oa.op_type = OP_YANK;
- (void)op_yank(&oa, true);
+ (void)op_yank(&oa, true, false);
break;
default: /* CMD_rshift or CMD_lshift */
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index ec9f978416..38432a34db 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -4946,7 +4946,7 @@ static void expand_shellcmd(char_u *filepat, int *num_file, char_u ***file,
{
char_u *pat;
int i;
- char_u *path;
+ char_u *path = NULL;
garray_T ga;
char_u *buf = xmalloc(MAXPATHL);
size_t l;
@@ -4965,15 +4965,14 @@ static void expand_shellcmd(char_u *filepat, int *num_file, char_u ***file,
flags |= EW_FILE | EW_EXEC | EW_SHELLCMD;
bool mustfree = false; // Track memory allocation for *path.
- // For an absolute name we don't use $PATH.
- if (path_is_absolute(pat)) {
- path = (char_u *)" ";
- } else if (pat[0] == '.' && (vim_ispathsep(pat[1])
- || (pat[1] == '.'
- && vim_ispathsep(pat[2])))) {
+ if (pat[0] == '.' && (vim_ispathsep(pat[1])
+ || (pat[1] == '.' && vim_ispathsep(pat[2])))) {
path = (char_u *)".";
} else {
- path = (char_u *)vim_getenv("PATH");
+ // For an absolute name we don't use $PATH.
+ if (!path_is_absolute(pat)) {
+ path = (char_u *)vim_getenv("PATH");
+ }
if (path == NULL) {
path = (char_u *)"";
} else {
@@ -4987,6 +4986,8 @@ static void expand_shellcmd(char_u *filepat, int *num_file, char_u ***file,
* current directory, to find "subdir/cmd".
*/
ga_init(&ga, (int)sizeof(char *), 10);
+ hashtab_T found_ht;
+ hash_init(&found_ht);
for (s = path; ; s = e) {
if (*s == NUL) {
if (did_curdir) {
@@ -4998,13 +4999,10 @@ static void expand_shellcmd(char_u *filepat, int *num_file, char_u ***file,
did_curdir = true;
}
- if (*s == ' ') {
- s++; // Skip space used for absolute path name.
- }
-
- e = vim_strchr(s, ':');
- if (e == NULL)
+ e = vim_strchr(s, ENV_SEPCHAR);
+ if (e == NULL) {
e = s + STRLEN(s);
+ }
l = (size_t)(e - s);
if (l > MAXPATHL - 5) {
@@ -5020,14 +5018,24 @@ static void expand_shellcmd(char_u *filepat, int *num_file, char_u ***file,
if (ret == OK) {
ga_grow(&ga, *num_file);
{
- for (i = 0; i < *num_file; ++i) {
- s = (*file)[i];
- if (STRLEN(s) > l) {
- /* Remove the path again. */
- STRMOVE(s, s + l);
- ((char_u **)ga.ga_data)[ga.ga_len++] = s;
- } else
- xfree(s);
+ for (i = 0; i < *num_file; i++) {
+ char_u *name = (*file)[i];
+
+ if (STRLEN(name) > l) {
+ // Check if this name was already found.
+ hash_T hash = hash_hash(name + l);
+ hashitem_T *hi =
+ hash_lookup(&found_ht, (const char *)(name + l),
+ STRLEN(name + l), hash);
+ if (HASHITEM_EMPTY(hi)) {
+ // Remove the path that was prepended.
+ STRMOVE(name, name + l);
+ ((char_u **)ga.ga_data)[ga.ga_len++] = name;
+ hash_add_item(&found_ht, hi, name, hash);
+ name = NULL;
+ }
+ }
+ xfree(name);
}
xfree(*file);
}
@@ -5043,6 +5051,7 @@ static void expand_shellcmd(char_u *filepat, int *num_file, char_u ***file,
if (mustfree) {
xfree(path);
}
+ hash_clear(&found_ht);
}
/// Call "user_expand_func()" to invoke a user defined Vim script function and
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index 387475c9e2..907e6c978a 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -4248,15 +4248,13 @@ void shorten_buf_fname(buf_T *buf, char_u *dirname, int force)
buf->b_sfname = vim_strsave(p);
buf->b_fname = buf->b_sfname;
}
- if (p == NULL || buf->b_fname == NULL) {
+ if (p == NULL) {
buf->b_fname = buf->b_ffname;
}
}
}
-/*
- * Shorten filenames for all buffers.
- */
+/// Shorten filenames for all buffers.
void shorten_fnames(int force)
{
char_u dirname[MAXPATHL];
@@ -4265,8 +4263,8 @@ void shorten_fnames(int force)
FOR_ALL_BUFFERS(buf) {
shorten_buf_fname(buf, dirname, force);
- /* Always make the swap file name a full path, a "nofile" buffer may
- * also have a swap file. */
+ // Always make the swap file name a full path, a "nofile" buffer may
+ // also have a swap file.
mf_fullname(buf->b_ml.ml_mfp);
}
status_redraw_all();
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index 28d37f3581..7d8342c4dd 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -3288,29 +3288,36 @@ char *map_mode_to_chars(int mode)
ga_init(&mapmode, 1, 7);
- if ((mode & (INSERT + CMDLINE)) == INSERT + CMDLINE)
- ga_append(&mapmode, '!'); /* :map! */
- else if (mode & INSERT)
- ga_append(&mapmode, 'i'); /* :imap */
- else if (mode & LANGMAP)
- ga_append(&mapmode, 'l'); /* :lmap */
- else if (mode & CMDLINE)
- ga_append(&mapmode, 'c'); /* :cmap */
- else if ((mode & (NORMAL + VISUAL + SELECTMODE + OP_PENDING))
- == NORMAL + VISUAL + SELECTMODE + OP_PENDING)
- ga_append(&mapmode, ' '); /* :map */
- else {
- if (mode & NORMAL)
- ga_append(&mapmode, 'n'); /* :nmap */
- if (mode & OP_PENDING)
- ga_append(&mapmode, 'o'); /* :omap */
- if ((mode & (VISUAL + SELECTMODE)) == VISUAL + SELECTMODE)
- ga_append(&mapmode, 'v'); /* :vmap */
- else {
- if (mode & VISUAL)
- ga_append(&mapmode, 'x'); /* :xmap */
- if (mode & SELECTMODE)
- ga_append(&mapmode, 's'); /* :smap */
+ if ((mode & (INSERT + CMDLINE)) == INSERT + CMDLINE) {
+ ga_append(&mapmode, '!'); // :map!
+ } else if (mode & INSERT) {
+ ga_append(&mapmode, 'i'); // :imap
+ } else if (mode & LANGMAP) {
+ ga_append(&mapmode, 'l'); // :lmap
+ } else if (mode & CMDLINE) {
+ ga_append(&mapmode, 'c'); // :cmap
+ } else if ((mode & (NORMAL + VISUAL + SELECTMODE + OP_PENDING))
+ == NORMAL + VISUAL + SELECTMODE + OP_PENDING) {
+ ga_append(&mapmode, ' '); // :map
+ } else {
+ if (mode & NORMAL) {
+ ga_append(&mapmode, 'n'); // :nmap
+ }
+ if (mode & OP_PENDING) {
+ ga_append(&mapmode, 'o'); // :omap
+ }
+ if (mode & TERM_FOCUS) {
+ ga_append(&mapmode, 't'); // :tmap
+ }
+ if ((mode & (VISUAL + SELECTMODE)) == VISUAL + SELECTMODE) {
+ ga_append(&mapmode, 'v'); // :vmap
+ } else {
+ if (mode & VISUAL) {
+ ga_append(&mapmode, 'x'); // :xmap
+ }
+ if (mode & SELECTMODE) {
+ ga_append(&mapmode, 's'); // :smap
+ }
}
}
@@ -3441,7 +3448,7 @@ int map_to_exists_mode(const char *const rhs, const int mode, const bool abbr)
{
mapblock_T *mp;
int hash;
- bool expand_buffer = false;
+ bool exp_buffer = false;
validate_maphash();
@@ -3452,12 +3459,12 @@ int map_to_exists_mode(const char *const rhs, const int mode, const bool abbr)
if (hash > 0) { // There is only one abbr list.
break;
}
- if (expand_buffer) {
+ if (exp_buffer) {
mp = curbuf->b_first_abbr;
} else {
mp = first_abbr;
}
- } else if (expand_buffer) {
+ } else if (exp_buffer) {
mp = curbuf->b_maphash[hash];
} else {
mp = maphash[hash];
@@ -3469,10 +3476,10 @@ int map_to_exists_mode(const char *const rhs, const int mode, const bool abbr)
}
}
}
- if (expand_buffer) {
+ if (exp_buffer) {
break;
}
- expand_buffer = true;
+ exp_buffer = true;
}
return false;
@@ -3947,10 +3954,10 @@ void vim_unescape_csi(char_u *p)
* Write map commands for the current mappings to an .exrc file.
* Return FAIL on error, OK otherwise.
*/
-int
-makemap (
+int
+makemap(
FILE *fd,
- buf_T *buf /* buffer for local mappings or NULL */
+ buf_T *buf // buffer for local mappings or NULL
)
{
mapblock_T *mp;
@@ -3963,50 +3970,56 @@ makemap (
validate_maphash();
- /*
- * Do the loop twice: Once for mappings, once for abbreviations.
- * Then loop over all map hash lists.
- */
- for (abbr = 0; abbr < 2; ++abbr)
- for (hash = 0; hash < 256; ++hash) {
+ // Do the loop twice: Once for mappings, once for abbreviations.
+ // Then loop over all map hash lists.
+ for (abbr = 0; abbr < 2; abbr++) {
+ for (hash = 0; hash < 256; hash++) {
if (abbr) {
- if (hash > 0) /* there is only one abbr list */
+ if (hash > 0) { // there is only one abbr list
break;
- if (buf != NULL)
+ }
+ if (buf != NULL) {
mp = buf->b_first_abbr;
- else
+ } else {
mp = first_abbr;
+ }
} else {
- if (buf != NULL)
+ if (buf != NULL) {
mp = buf->b_maphash[hash];
- else
+ } else {
mp = maphash[hash];
+ }
}
for (; mp; mp = mp->m_next) {
- /* skip script-local mappings */
- if (mp->m_noremap == REMAP_SCRIPT)
+ // skip script-local mappings
+ if (mp->m_noremap == REMAP_SCRIPT) {
continue;
+ }
- /* skip mappings that contain a <SNR> (script-local thing),
- * they probably don't work when loaded again */
- for (p = mp->m_str; *p != NUL; ++p)
+ // skip mappings that contain a <SNR> (script-local thing),
+ // they probably don't work when loaded again
+ for (p = mp->m_str; *p != NUL; p++) {
if (p[0] == K_SPECIAL && p[1] == KS_EXTRA
- && p[2] == (int)KE_SNR)
+ && p[2] == (int)KE_SNR) {
break;
- if (*p != NUL)
+ }
+ }
+ if (*p != NUL) {
continue;
+ }
- /* It's possible to create a mapping and then ":unmap" certain
- * modes. We recreate this here by mapping the individual
- * modes, which requires up to three of them. */
+ // It's possible to create a mapping and then ":unmap" certain
+ // modes. We recreate this here by mapping the individual
+ // modes, which requires up to three of them.
c1 = NUL;
c2 = NUL;
c3 = NUL;
- if (abbr)
+ if (abbr) {
cmd = "abbr";
- else
+ } else {
cmd = "map";
+ }
switch (mp->m_mode) {
case NORMAL + VISUAL + SELECTMODE + OP_PENDING:
break;
@@ -4064,8 +4077,9 @@ makemap (
c2 = 'o';
break;
case CMDLINE + INSERT:
- if (!abbr)
+ if (!abbr) {
cmd = "map!";
+ }
break;
case CMDLINE:
c1 = 'c';
@@ -4083,9 +4097,10 @@ makemap (
IEMSG(_("E228: makemap: Illegal mode"));
return FAIL;
}
- do { /* do this twice if c2 is set, 3 times with c3 */
- /* When outputting <> form, need to make sure that 'cpo'
- * is set to the Vim default. */
+ do {
+ // do this twice if c2 is set, 3 times with c3 */
+ // When outputting <> form, need to make sure that 'cpo'
+ // is set to the Vim default.
if (!did_cpo) {
if (*mp->m_str == NUL) { // Will use <Nop>.
did_cpo = true;
@@ -4100,63 +4115,69 @@ makemap (
if (fprintf(fd, "let s:cpo_save=&cpo") < 0
|| put_eol(fd) < 0
|| fprintf(fd, "set cpo&vim") < 0
- || put_eol(fd) < 0)
+ || put_eol(fd) < 0) {
return FAIL;
+ }
}
}
- if (c1 && putc(c1, fd) < 0)
- return FAIL;
- if (mp->m_noremap != REMAP_YES && fprintf(fd, "nore") < 0)
+ if (c1 && putc(c1, fd) < 0) {
return FAIL;
- if (fputs(cmd, fd) < 0)
+ }
+ if (mp->m_noremap != REMAP_YES && fprintf(fd, "nore") < 0) {
return FAIL;
- if (buf != NULL && fputs(" <buffer>", fd) < 0)
+ }
+ if (fputs(cmd, fd) < 0) {
return FAIL;
- if (mp->m_nowait && fputs(" <nowait>", fd) < 0)
+ }
+ if (buf != NULL && fputs(" <buffer>", fd) < 0) {
return FAIL;
- if (mp->m_silent && fputs(" <silent>", fd) < 0)
+ }
+ if (mp->m_nowait && fputs(" <nowait>", fd) < 0) {
return FAIL;
- if (mp->m_noremap == REMAP_SCRIPT
- && fputs("<script>", fd) < 0)
+ }
+ if (mp->m_silent && fputs(" <silent>", fd) < 0) {
return FAIL;
- if (mp->m_expr && fputs(" <expr>", fd) < 0)
+ }
+ if (mp->m_expr && fputs(" <expr>", fd) < 0) {
return FAIL;
+ }
- if ( putc(' ', fd) < 0
- || put_escstr(fd, mp->m_keys, 0) == FAIL
- || putc(' ', fd) < 0
- || put_escstr(fd, mp->m_str, 1) == FAIL
- || put_eol(fd) < 0)
+ if (putc(' ', fd) < 0
+ || put_escstr(fd, mp->m_keys, 0) == FAIL
+ || putc(' ', fd) < 0
+ || put_escstr(fd, mp->m_str, 1) == FAIL
+ || put_eol(fd) < 0) {
return FAIL;
+ }
c1 = c2;
c2 = c3;
c3 = NUL;
} while (c1 != NUL);
}
}
-
- if (did_cpo)
+ }
+ if (did_cpo) {
if (fprintf(fd, "let &cpo=s:cpo_save") < 0
|| put_eol(fd) < 0
|| fprintf(fd, "unlet s:cpo_save") < 0
- || put_eol(fd) < 0)
+ || put_eol(fd) < 0) {
return FAIL;
+ }
+ }
return OK;
}
-/*
- * write escape string to file
- * "what": 0 for :map lhs, 1 for :map rhs, 2 for :set
- *
- * return FAIL for failure, OK otherwise
- */
+// write escape string to file
+// "what": 0 for :map lhs, 1 for :map rhs, 2 for :set
+//
+// return FAIL for failure, OK otherwise
int put_escstr(FILE *fd, char_u *strstart, int what)
{
char_u *str = strstart;
int c;
int modifiers;
- /* :map xx <Nop> */
+ // :map xx <Nop>
if (*str == NUL && what == 1) {
if (fprintf(fd, "<Nop>") < 0)
return FAIL;
diff --git a/src/nvim/macros.h b/src/nvim/macros.h
index c758e000a9..f2ba91335d 100644
--- a/src/nvim/macros.h
+++ b/src/nvim/macros.h
@@ -163,7 +163,8 @@
# define NVIM_HAS_ATTRIBUTE __has_attribute
#endif
-#if NVIM_HAS_ATTRIBUTE(fallthrough)
+#if NVIM_HAS_ATTRIBUTE(fallthrough) \
+ && (!defined(__apple_build_version__) || __apple_build_version__ >= 7000000)
# define FALLTHROUGH __attribute__((fallthrough))
#else
# define FALLTHROUGH
diff --git a/src/nvim/mark.c b/src/nvim/mark.c
index 3736004527..9f357575d0 100644
--- a/src/nvim/mark.c
+++ b/src/nvim/mark.c
@@ -1213,8 +1213,8 @@ void cleanup_jumplist(win_T *wp, bool loadfiles)
// When pointer is below last jump, remove the jump if it matches the current
// line. This avoids useless/phantom jumps. #9805
- if (wp->w_jumplistlen
- && wp->w_jumplistidx == wp->w_jumplistlen) {
+ if (loadfiles // otherwise (i.e.: Shada), last entry should be kept
+ && wp->w_jumplistlen && wp->w_jumplistidx == wp->w_jumplistlen) {
const xfmark_T *fm_last = &wp->w_jumplist[wp->w_jumplistlen - 1];
if (fm_last->fmark.fnum == curbuf->b_fnum
&& fm_last->fmark.mark.lnum == wp->w_cursor.lnum) {
diff --git a/src/nvim/memory.c b/src/nvim/memory.c
index 1384aa177b..64aae71433 100644
--- a/src/nvim/memory.c
+++ b/src/nvim/memory.c
@@ -9,6 +9,7 @@
#include <stdbool.h>
#include "nvim/vim.h"
+#include "nvim/context.h"
#include "nvim/eval.h"
#include "nvim/highlight.h"
#include "nvim/memfile.h"
@@ -671,6 +672,7 @@ void free_all_mem(void)
eval_clear();
api_vim_free_all_mem();
+ ctx_free_all();
// Free all buffers. Reset 'autochdir' to avoid accessing things that
// were freed already.
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 286159ac0f..c3b4f4e376 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -1817,7 +1817,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
}
} else {
curwin->w_p_lbr = lbr_saved;
- (void)op_yank(oap, !gui_yank);
+ (void)op_yank(oap, !gui_yank, false);
}
check_cursor_col();
break;
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index 40c96eb333..6709e52679 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -1400,9 +1400,11 @@ int op_delete(oparg_T *oap)
*/
if (oap->regname != '_') {
yankreg_T *reg = NULL;
+ int did_yank = false;
if (oap->regname != 0) {
- //yank without message
- if (!op_yank(oap, false)) {
+ // yank without message
+ did_yank = op_yank(oap, false, true);
+ if (!did_yank) {
// op_yank failed, don't do anything
return OK;
}
@@ -1423,6 +1425,7 @@ int op_delete(oparg_T *oap)
y_regs[1].y_array = NULL; // set register "1 to empty
reg = &y_regs[1];
op_yank_reg(oap, false, reg, false);
+ did_yank = true;
}
/* Yank into small delete register when no named register specified
@@ -1431,13 +1434,14 @@ int op_delete(oparg_T *oap)
&& oap->line_count == 1) {
reg = get_yank_register('-', YREG_YANK);
op_yank_reg(oap, false, reg, false);
+ did_yank = true;
}
- if (oap->regname == 0) {
+ if (did_yank || oap->regname == 0) {
if (reg == NULL) {
abort();
}
- set_clipboard(0, reg);
+ set_clipboard(oap->regname, reg);
do_autocmd_textyankpost(oap, reg);
}
@@ -2376,8 +2380,9 @@ void free_register(yankreg_T *reg)
///
/// @param oap operator arguments
/// @param message show message when more than `&report` lines are yanked.
+/// @param deleting whether the function was called from a delete operation.
/// @returns whether the operation register was writable.
-bool op_yank(oparg_T *oap, bool message)
+bool op_yank(oparg_T *oap, bool message, int deleting)
FUNC_ATTR_NONNULL_ALL
{
// check for read-only register
@@ -2391,8 +2396,11 @@ 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);
- do_autocmd_textyankpost(oap, reg);
+ // op_delete will set_clipboard and do_autocmd
+ if (!deleting) {
+ set_clipboard(oap->regname, reg);
+ do_autocmd_textyankpost(oap, reg);
+ }
return true;
}
@@ -3439,7 +3447,7 @@ void ex_display(exarg_T *eap)
MSG_PUTS_ATTR("^J", attr);
n -= 2;
}
- for (p = yb->y_array[j]; *p && (n -= ptr2cells(p)) >= 0; p++) { // -V1019
+ for (p = yb->y_array[j]; *p && (n -= ptr2cells(p)) >= 0; p++) { // -V1019 NOLINT(whitespace/line_length)
clen = (*mb_ptr2len)(p);
msg_outtrans_len(p, clen);
p += clen - 1;
@@ -3516,8 +3524,8 @@ void ex_display(exarg_T *eap)
* display a string for do_dis()
* truncate at end of screen line
*/
-static void
-dis_msg (
+static void
+dis_msg(
char_u *p,
int skip_esc /* if TRUE, ignore trailing ESC */
)
@@ -3857,8 +3865,8 @@ static int same_leader(linenr_T lnum, int leader1_len, char_u *leader1_flags, in
/*
* Implementation of the format operator 'gq'.
*/
-void
-op_format (
+void
+op_format(
oparg_T *oap,
int keep_cursor /* keep cursor on same text char */
)
@@ -3937,8 +3945,8 @@ void op_formatexpr(oparg_T *oap)
op_format(oap, FALSE);
}
-int
-fex_format (
+int
+fex_format(
linenr_T lnum,
long count,
int c /* character to be inserted */
@@ -3980,8 +3988,8 @@ fex_format (
* Lines after the cursor line are saved for undo, caller must have saved the
* first line.
*/
-void
-format_lines (
+void
+format_lines(
linenr_T line_count,
int avoid_fex /* don't use 'formatexpr' */
)
@@ -5892,33 +5900,45 @@ static inline bool reg_empty(const yankreg_T *const reg)
&& *(reg->y_array[0]) == NUL));
}
-/// Iterate over registerrs
+/// Iterate over global registers.
+///
+/// @see op_register_iter
+const void *op_global_reg_iter(const void *const iter, char *const name,
+ yankreg_T *const reg, bool *is_unnamed)
+ FUNC_ATTR_NONNULL_ARG(2, 3, 4) FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ return op_reg_iter(iter, y_regs, name, reg, is_unnamed);
+}
+
+/// Iterate over registers `regs`.
///
/// @param[in] iter Iterator. Pass NULL to start iteration.
+/// @param[in] regs Registers list to be iterated.
/// @param[out] name Register name.
/// @param[out] reg Register contents.
///
-/// @return Pointer that needs to be passed to next `op_register_iter` call or
+/// @return Pointer that must be passed to next `op_register_iter` call or
/// NULL if iteration is over.
-const void *op_register_iter(const void *const iter, char *const name,
- yankreg_T *const reg, bool *is_unnamed)
- FUNC_ATTR_NONNULL_ARG(2, 3) FUNC_ATTR_WARN_UNUSED_RESULT
+const void *op_reg_iter(const void *const iter, const yankreg_T *const regs,
+ char *const name, yankreg_T *const reg,
+ bool *is_unnamed)
+ FUNC_ATTR_NONNULL_ARG(3, 4, 5) FUNC_ATTR_WARN_UNUSED_RESULT
{
*name = NUL;
const yankreg_T *iter_reg = (iter == NULL
- ? &(y_regs[0])
- : (const yankreg_T *const) iter);
- while (iter_reg - &(y_regs[0]) < NUM_SAVED_REGISTERS && reg_empty(iter_reg)) {
+ ? &(regs[0])
+ : (const yankreg_T *const)iter);
+ while (iter_reg - &(regs[0]) < NUM_SAVED_REGISTERS && reg_empty(iter_reg)) {
iter_reg++;
}
- if (iter_reg - &(y_regs[0]) == NUM_SAVED_REGISTERS || reg_empty(iter_reg)) {
+ if (iter_reg - &(regs[0]) == NUM_SAVED_REGISTERS || reg_empty(iter_reg)) {
return NULL;
}
- int iter_off = (int)(iter_reg - &(y_regs[0]));
+ int iter_off = (int)(iter_reg - &(regs[0]));
*name = (char)get_register_name(iter_off);
*reg = *iter_reg;
*is_unnamed = (iter_reg == y_previous);
- while (++iter_reg - &(y_regs[0]) < NUM_SAVED_REGISTERS) {
+ while (++iter_reg - &(regs[0]) < NUM_SAVED_REGISTERS) {
if (!reg_empty(iter_reg)) {
return (void *) iter_reg;
}
@@ -5927,7 +5947,7 @@ const void *op_register_iter(const void *const iter, char *const name,
}
/// Get a number of non-empty registers
-size_t op_register_amount(void)
+size_t op_reg_amount(void)
FUNC_ATTR_WARN_UNUSED_RESULT
{
size_t ret = 0;
@@ -5946,7 +5966,7 @@ size_t op_register_amount(void)
/// @param[in] is_unnamed Whether to set the unnamed regiseter to reg
///
/// @return true on success, false on failure.
-bool op_register_set(const char name, const yankreg_T reg, bool is_unnamed)
+bool op_reg_set(const char name, const yankreg_T reg, bool is_unnamed)
{
int i = op_reg_index(name);
if (i == -1) {
@@ -5966,7 +5986,7 @@ bool op_register_set(const char name, const yankreg_T reg, bool is_unnamed)
/// @param[in] name Register name.
///
/// @return Pointer to the register contents or NULL.
-const yankreg_T *op_register_get(const char name)
+const yankreg_T *op_reg_get(const char name)
{
int i = op_reg_index(name);
if (i == -1) {
@@ -5980,7 +6000,7 @@ const yankreg_T *op_register_get(const char name)
/// @param[in] name Register name.
///
/// @return true on success, false on failure.
-bool op_register_set_previous(const char name)
+bool op_reg_set_previous(const char name)
FUNC_ATTR_WARN_UNUSED_RESULT
{
int i = op_reg_index(name);
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 35151ab81c..40c1358fa5 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -3508,7 +3508,9 @@ static char_u *set_chars_option(win_T *wp, char_u **varp)
{
int round, i, len, entries;
char_u *p, *s;
- int c1 = 0, c2 = 0, c3 = 0;
+ int c1;
+ int c2 = 0;
+ int c3 = 0;
struct chars_tab {
int *cp; ///< char value
@@ -3575,7 +3577,7 @@ static char_u *set_chars_option(win_T *wp, char_u **varp)
if (STRNCMP(p, tab[i].name, len) == 0
&& p[len] == ':'
&& p[len + 1] != NUL) {
- c1 = c2 = c3 = 0;
+ c2 = c3 = 0;
s = p + len + 1;
// TODO(bfredl): use schar_T representation and utfc_ptr2len
diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c
index ef4330003f..ce40bc15e0 100644
--- a/src/nvim/popupmnu.c
+++ b/src/nvim/popupmnu.c
@@ -855,7 +855,8 @@ int pum_get_height(void)
return pum_height;
}
-void pum_set_boundings(dict_T *dict)
+/// Add size information about the pum to "dict".
+void pum_set_event_info(dict_T *dict)
{
if (!pum_visible()) {
return;
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index cc4a2a90b9..1eb616bca7 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -1326,7 +1326,7 @@ static int qf_parse_multiline_pfx(qf_info_T *qi, int qf_idx, int idx,
if (qfprev == NULL) {
return QF_FAIL;
}
- if (*fields->errmsg && !qfl->qf_multiignore) {
+ if (*fields->errmsg) {
size_t textlen = strlen((char *)qfprev->qf_text);
size_t errlen = strlen((char *)fields->errmsg);
qfprev->qf_text = xrealloc(qfprev->qf_text, textlen + errlen + 2);
@@ -2056,7 +2056,7 @@ static int qf_jump_to_usable_window(int qf_fnum, int *opened_window)
win_T *usable_win_ptr = NULL;
int usable_win;
int flags;
- win_T *win = NULL;
+ win_T *win;
win_T *altwin;
usable_win = 0;
@@ -2079,7 +2079,6 @@ static int qf_jump_to_usable_window(int qf_fnum, int *opened_window)
// Locate a window showing a normal buffer
FOR_ALL_WINDOWS_IN_TAB(win2, curtab) {
if (win2->w_buffer->b_p_bt[0] == NUL) {
- win = win2;
usable_win = 1;
break;
}
@@ -2212,7 +2211,6 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit,
oldwin == curwin ? curwin : NULL);
}
} else {
- int old_qf_curlist = qi->qf_curlist;
unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id;
retval = buflist_getfile(qf_ptr->qf_fnum, (linenr_T)1,
@@ -2229,8 +2227,7 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit,
EMSG(_(e_loc_list_changed));
*abort = true;
}
- } else if (old_qf_curlist != qi->qf_curlist
- || !is_qf_entry_present(qi, qf_ptr)) {
+ } else if (!is_qf_entry_present(qi, qf_ptr)) {
if (IS_QF_STACK(qi)) {
EMSG(_("E925: Current quickfix was changed"));
} else {
diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c
index 414472a8f3..06b99d0b75 100644
--- a/src/nvim/regexp.c
+++ b/src/nvim/regexp.c
@@ -4323,8 +4323,10 @@ static int regmatch(
/* Still at same position as last time, fail. */
status = RA_NOMATCH;
- if (status != RA_FAIL && status != RA_NOMATCH)
+ assert(status != RA_FAIL);
+ if (status != RA_NOMATCH) {
reg_save(&bp[i].bp_pos, &backpos);
+ }
}
break;
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index 877bd2db21..06f886d411 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -3143,7 +3143,6 @@ win_line (
c = '>';
mb_c = c;
mb_l = 1;
- mb_utf8 = false;
multi_attr = win_hl_attr(wp, HLF_AT);
// put the pointer back to output the double-width
diff --git a/src/nvim/search.c b/src/nvim/search.c
index c1770fd80d..85de7f26de 100644
--- a/src/nvim/search.c
+++ b/src/nvim/search.c
@@ -864,13 +864,11 @@ int searchit(
}
at_first_line = FALSE;
- /*
- * Stop the search if wrapscan isn't set, "stop_lnum" is
- * specified, after an interrupt, after a match and after looping
- * twice.
- */
+ // Stop the search if wrapscan isn't set, "stop_lnum" is
+ // specified, after an interrupt, after a match and after looping
+ // twice.
if (!p_ws || stop_lnum != 0 || got_int || called_emsg
- || (timed_out != NULL && timed_out)
+ || (timed_out != NULL && *timed_out)
|| break_loop
|| found || loop) {
break;
@@ -1139,7 +1137,7 @@ int do_search(
// Get the offset, so we know how long it is.
if (spats[0].off.line || spats[0].off.end || spats[0].off.off) {
- p = off_buf;
+ p = off_buf; // -V507
*p++ = dirc;
if (spats[0].off.end) {
*p++ = 'e';
diff --git a/src/nvim/shada.c b/src/nvim/shada.c
index 4aafc669dc..3ebe75b980 100644
--- a/src/nvim/shada.c
+++ b/src/nvim/shada.c
@@ -151,15 +151,6 @@ KHASH_SET_INIT_STR(strset)
/// 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
- kShaDaWantMarks = 2, ///< Load local file marks and change list
- kShaDaForceit = 4, ///< Overwrite info already read
- kShaDaGetOldfiles = 8, ///< Load v:oldfiles.
- kShaDaMissingError = 16, ///< Error out when os_open returns -ENOENT.
-} ShaDaReadFileFlags;
-
/// Possible ShaDa entry types
///
/// @warning Enum values are part of the API and must not be altered.
@@ -283,7 +274,7 @@ typedef struct {
char sep;
list_T *additional_elements;
} history_item;
- struct reg {
+ struct reg { // yankreg_T
char name;
MotionType type;
char **contents;
@@ -1328,13 +1319,13 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags)
break;
}
if (!force) {
- const yankreg_T *const reg = op_register_get(cur_entry.data.reg.name);
+ const yankreg_T *const reg = op_reg_get(cur_entry.data.reg.name);
if (reg == NULL || reg->timestamp >= cur_entry.timestamp) {
shada_free_shada_entry(&cur_entry);
break;
}
}
- if (!op_register_set(cur_entry.data.reg.name, (yankreg_T) {
+ if (!op_reg_set(cur_entry.data.reg.name, (yankreg_T) {
.y_array = (char_u **)cur_entry.data.reg.contents,
.y_size = cur_entry.data.reg.contents_size,
.y_type = cur_entry.data.reg.type,
@@ -2496,7 +2487,7 @@ static inline void shada_initialize_registers(WriteMergerState *const wms,
yankreg_T reg;
char name = NUL;
bool is_unnamed = false;
- reg_iter = op_register_iter(reg_iter, &name, &reg, &is_unnamed);
+ reg_iter = op_global_reg_iter(reg_iter, &name, &reg, &is_unnamed);
if (name == NUL) {
break;
}
@@ -2552,6 +2543,19 @@ static inline void replace_numbered_mark(WriteMergerState *const wms,
wms->numbered_marks[idx].data.data.filemark.name = (char)('0' + (int)idx);
}
+/// Find buffers ignored due to their location.
+///
+/// @param[out] removable_bufs Cache of buffers ignored due to their location.
+static inline void find_removable_bufs(khash_t(bufset) *removable_bufs)
+{
+ FOR_ALL_BUFFERS(buf) {
+ if (buf->b_ffname != NULL && shada_removable((char *)buf->b_ffname)) {
+ int kh_ret;
+ (void)kh_put(bufset, removable_bufs, (uintptr_t)buf, &kh_ret);
+ }
+ }
+}
+
/// Write ShaDa file
///
/// @param[in] sd_writer Structure containing file writer definition.
@@ -2621,12 +2625,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer,
set_last_cursor(wp);
}
- FOR_ALL_BUFFERS(buf) {
- if (buf->b_ffname != NULL && shada_removable((char *) buf->b_ffname)) {
- int kh_ret;
- (void) kh_put(bufset, &removable_bufs, (uintptr_t) buf, &kh_ret);
- }
- }
+ find_removable_bufs(&removable_bufs);
// Write header
if (shada_pack_entry(packer, (ShadaEntry) {
@@ -2673,7 +2672,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer,
do {
typval_T vartv;
const char *name = NULL;
- var_iter = var_shada_iter(var_iter, &name, &vartv);
+ var_iter = var_shada_iter(var_iter, &name, &vartv, VAR_FLAVOUR_SHADA);
if (name == NULL) {
break;
}
@@ -2737,49 +2736,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer,
}
// Initialize jump list
- const void *jump_iter = NULL;
- cleanup_jumplist(curwin, false);
- setpcmark();
- do {
- xfmark_T fm;
- jump_iter = mark_jumplist_iter(jump_iter, curwin, &fm);
-
- if (fm.fmark.mark.lnum == 0) {
- iemsgf("ShaDa: mark lnum zero (ji:%p, js:%p, len:%i)",
- (void *)jump_iter, (void *)&curwin->w_jumplist[0],
- curwin->w_jumplistlen);
- continue;
- }
- const buf_T *const buf = (fm.fmark.fnum == 0
- ? NULL
- : buflist_findnr(fm.fmark.fnum));
- if (buf != NULL
- ? in_bufset(&removable_bufs, buf)
- : fm.fmark.fnum != 0) {
- continue;
- }
- const char *const fname = (char *) (fm.fmark.fnum == 0
- ? (fm.fname == NULL ? NULL : fm.fname)
- : buf->b_ffname);
- if (fname == NULL) {
- continue;
- }
- wms->jumps[wms->jumps_size++] = (PossiblyFreedShadaEntry) {
- .can_free_entry = false,
- .data = {
- .type = kSDItemJump,
- .timestamp = fm.fmark.timestamp,
- .data = {
- .filemark = {
- .name = NUL,
- .mark = fm.fmark.mark,
- .fname = (char *) fname,
- .additional_data = fm.fmark.additional_data,
- }
- }
- }
- };
- } while (jump_iter != NULL);
+ wms->jumps_size = shada_init_jumps(wms->jumps, &removable_bufs);
// Initialize global marks
if (dump_global_marks) {
@@ -4117,3 +4074,236 @@ static bool shada_removable(const char *name)
xfree(new_name);
return retval;
}
+
+/// Initialize ShaDa jumplist entries.
+///
+/// @param[in,out] jumps Array of ShaDa entries to set.
+/// @param[in] removable_bufs Cache of buffers ignored due to their
+/// location.
+///
+/// @return number of jumplist entries
+static inline size_t shada_init_jumps(
+ PossiblyFreedShadaEntry *jumps, khash_t(bufset) *const removable_bufs)
+{
+ // Initialize jump list
+ size_t jumps_size = 0;
+ const void *jump_iter = NULL;
+ setpcmark();
+ cleanup_jumplist(curwin, false);
+ do {
+ xfmark_T fm;
+ jump_iter = mark_jumplist_iter(jump_iter, curwin, &fm);
+
+ if (fm.fmark.mark.lnum == 0) {
+ iemsgf("ShaDa: mark lnum zero (ji:%p, js:%p, len:%i)",
+ (void *)jump_iter, (void *)&curwin->w_jumplist[0],
+ curwin->w_jumplistlen);
+ continue;
+ }
+ const buf_T *const buf = (fm.fmark.fnum == 0
+ ? NULL
+ : buflist_findnr(fm.fmark.fnum));
+ if (buf != NULL
+ ? in_bufset(removable_bufs, buf)
+ : fm.fmark.fnum != 0) {
+ continue;
+ }
+ const char *const fname = (char *) (fm.fmark.fnum == 0
+ ? (fm.fname == NULL ? NULL : fm.fname)
+ : buf->b_ffname);
+ if (fname == NULL) {
+ continue;
+ }
+ jumps[jumps_size++] = (PossiblyFreedShadaEntry) {
+ .can_free_entry = false,
+ .data = {
+ .type = kSDItemJump,
+ .timestamp = fm.fmark.timestamp,
+ .data = {
+ .filemark = {
+ .name = NUL,
+ .mark = fm.fmark.mark,
+ .fname = (char *) fname,
+ .additional_data = fm.fmark.additional_data,
+ }
+ }
+ }
+ };
+ } while (jump_iter != NULL);
+ return jumps_size;
+}
+
+/// Write registers ShaDa entries in given msgpack_sbuffer.
+///
+/// @param[in] sbuf target msgpack_sbuffer to write to.
+void shada_encode_regs(msgpack_sbuffer *const sbuf)
+ FUNC_ATTR_NONNULL_ALL
+{
+ WriteMergerState *const wms = xcalloc(1, sizeof(*wms));
+ shada_initialize_registers(wms, -1);
+ msgpack_packer packer;
+ msgpack_packer_init(&packer, sbuf, msgpack_sbuffer_write);
+ for (size_t i = 0; i < ARRAY_SIZE(wms->registers); i++) {
+ if (wms->registers[i].data.type == kSDItemRegister) {
+ if (kSDWriteFailed
+ == shada_pack_pfreed_entry(&packer, wms->registers[i], 0)) {
+ abort();
+ }
+ }
+ }
+ xfree(wms);
+}
+
+/// Write jumplist ShaDa entries in given msgpack_sbuffer.
+///
+/// @param[in] sbuf target msgpack_sbuffer to write to.
+void shada_encode_jumps(msgpack_sbuffer *const sbuf)
+ FUNC_ATTR_NONNULL_ALL
+{
+ khash_t(bufset) removable_bufs = KHASH_EMPTY_TABLE(bufset);
+ find_removable_bufs(&removable_bufs);
+ PossiblyFreedShadaEntry jumps[JUMPLISTSIZE];
+ size_t jumps_size = shada_init_jumps(jumps, &removable_bufs);
+ msgpack_packer packer;
+ msgpack_packer_init(&packer, sbuf, msgpack_sbuffer_write);
+ for (size_t i = 0; i < jumps_size; i++) {
+ if (kSDWriteFailed == shada_pack_pfreed_entry(&packer, jumps[i], 0)) {
+ abort();
+ }
+ }
+}
+
+/// Write buffer list ShaDa entry in given msgpack_sbuffer.
+///
+/// @param[in] sbuf target msgpack_sbuffer to write to.
+void shada_encode_buflist(msgpack_sbuffer *const sbuf)
+ FUNC_ATTR_NONNULL_ALL
+{
+ khash_t(bufset) removable_bufs = KHASH_EMPTY_TABLE(bufset);
+ find_removable_bufs(&removable_bufs);
+ ShadaEntry buflist_entry = shada_get_buflist(&removable_bufs);
+ msgpack_packer packer;
+ msgpack_packer_init(&packer, sbuf, msgpack_sbuffer_write);
+ if (kSDWriteFailed == shada_pack_entry(&packer, buflist_entry, 0)) {
+ abort();
+ }
+ xfree(buflist_entry.data.buffer_list.buffers);
+}
+
+/// Write global variables ShaDa entries in given msgpack_sbuffer.
+///
+/// @param[in] sbuf target msgpack_sbuffer to write to.
+void shada_encode_gvars(msgpack_sbuffer *const sbuf)
+ FUNC_ATTR_NONNULL_ALL
+{
+ msgpack_packer packer;
+ msgpack_packer_init(&packer, sbuf, msgpack_sbuffer_write);
+ const void *var_iter = NULL;
+ const Timestamp cur_timestamp = os_time();
+ do {
+ typval_T vartv;
+ const char *name = NULL;
+ var_iter = var_shada_iter(
+ var_iter, &name, &vartv,
+ VAR_FLAVOUR_DEFAULT | VAR_FLAVOUR_SESSION | VAR_FLAVOUR_SHADA);
+ if (name == NULL) {
+ break;
+ }
+ if (vartv.v_type != VAR_FUNC && vartv.v_type != VAR_PARTIAL) {
+ typval_T tgttv;
+ tv_copy(&vartv, &tgttv);
+ ShaDaWriteResult r = shada_pack_entry(&packer, (ShadaEntry) {
+ .type = kSDItemVariable,
+ .timestamp = cur_timestamp,
+ .data = {
+ .global_var = {
+ .name = (char *)name,
+ .value = tgttv,
+ .additional_elements = NULL,
+ }
+ }
+ }, 0);
+ if (kSDWriteFailed == r) {
+ abort();
+ }
+ tv_clear(&tgttv);
+ }
+ tv_clear(&vartv);
+ } while (var_iter != NULL);
+}
+
+/// Wrapper for reading from msgpack_sbuffer.
+///
+/// @return number of bytes read.
+static ptrdiff_t read_sbuf(ShaDaReadDef *const sd_reader, void *const dest,
+ const size_t size)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ msgpack_sbuffer *sbuf = (msgpack_sbuffer *)sd_reader->cookie;
+ const uintmax_t bytes_read = MIN(size, sbuf->size - sd_reader->fpos);
+ if (bytes_read < size) {
+ sd_reader->eof = true;
+ }
+ memcpy(dest, sbuf->data + sd_reader->fpos, (size_t)bytes_read);
+ sd_reader->fpos += bytes_read;
+ return (ptrdiff_t)bytes_read;
+}
+
+/// Wrapper for read that ignores bytes read from msgpack_sbuffer.
+///
+/// Used for skipping.
+///
+/// @param[in,out] sd_reader ShaDaReadDef with msgpack_sbuffer.
+/// @param[in] offset Amount of bytes to skip.
+///
+/// @return FAIL in case of failure, OK in case of success. May set
+/// sd_reader->eof.
+static int sd_sbuf_reader_skip_read(ShaDaReadDef *const sd_reader,
+ const size_t offset)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ msgpack_sbuffer *sbuf = (msgpack_sbuffer *)sd_reader->cookie;
+ assert(sbuf->size >= sd_reader->fpos);
+ const uintmax_t skip_bytes = MIN(offset, sbuf->size - sd_reader->fpos);
+ if (skip_bytes < offset) {
+ sd_reader->eof = true;
+ return FAIL;
+ }
+ sd_reader->fpos += offset;
+ return OK;
+}
+
+/// Prepare ShaDaReadDef with msgpack_sbuffer for reading.
+///
+/// @param[in] sbuf msgpack_sbuffer to read from.
+/// @param[out] sd_reader Location where reader structure will be saved.
+static void open_shada_sbuf_for_reading(const msgpack_sbuffer *const sbuf,
+ ShaDaReadDef *sd_reader)
+ FUNC_ATTR_NONNULL_ALL
+{
+ *sd_reader = (ShaDaReadDef) {
+ .read = &read_sbuf,
+ .close = NULL,
+ .skip = &sd_sbuf_reader_skip_read,
+ .error = NULL,
+ .eof = false,
+ .fpos = 0,
+ .cookie = (void *)sbuf,
+ };
+}
+
+/// Read ShaDa from msgpack_sbuffer.
+///
+/// @param[in] file msgpack_sbuffer to read from.
+/// @param[in] flags Flags, see ShaDaReadFileFlags enum.
+void shada_read_sbuf(msgpack_sbuffer *const sbuf, const int flags)
+ FUNC_ATTR_NONNULL_ALL
+{
+ assert(sbuf != NULL);
+ if (sbuf->data == NULL) {
+ return;
+ }
+ ShaDaReadDef sd_reader;
+ open_shada_sbuf_for_reading(sbuf, &sd_reader);
+ shada_read(&sd_reader, flags);
+}
diff --git a/src/nvim/shada.h b/src/nvim/shada.h
index 49986ac1c1..2a945a06bc 100644
--- a/src/nvim/shada.h
+++ b/src/nvim/shada.h
@@ -1,6 +1,17 @@
#ifndef NVIM_SHADA_H
#define NVIM_SHADA_H
+#include <msgpack.h>
+
+/// Flags for shada_read_file and children
+typedef enum {
+ kShaDaWantInfo = 1, ///< Load non-mark information
+ kShaDaWantMarks = 2, ///< Load local file marks and change list
+ kShaDaForceit = 4, ///< Overwrite info already read
+ kShaDaGetOldfiles = 8, ///< Load v:oldfiles.
+ kShaDaMissingError = 16, ///< Error out when os_open returns -ENOENT.
+} ShaDaReadFileFlags;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "shada.h.generated.h"
#endif
diff --git a/src/nvim/sign.c b/src/nvim/sign.c
index 0f399d9922..16ab00a52b 100644
--- a/src/nvim/sign.c
+++ b/src/nvim/sign.c
@@ -253,11 +253,7 @@ char_u * sign_typenr2name(int typenr)
/// Return information about a sign in a Dict
dict_T * sign_get_info(signlist_T *sign)
{
- dict_T *d;
-
- if ((d = tv_dict_alloc()) == NULL) {
- return NULL;
- }
+ dict_T *d = tv_dict_alloc();
tv_dict_add_nr(d, S_LEN("id"), sign->id);
tv_dict_add_str(d, S_LEN("group"), ((sign->group == NULL)
? (char *)""
diff --git a/src/nvim/spell.c b/src/nvim/spell.c
index 893a13b9f0..cc214616f4 100644
--- a/src/nvim/spell.c
+++ b/src/nvim/spell.c
@@ -4500,7 +4500,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so
sp->ts_state = STATE_SWAP3;
break;
}
- if (c2 != NUL && TRY_DEEPER(su, stack, depth, SCORE_SWAP)) {
+ if (TRY_DEEPER(su, stack, depth, SCORE_SWAP)) {
go_deeper(stack, depth, SCORE_SWAP);
#ifdef DEBUG_TRIEWALK
snprintf(changename[depth], sizeof(changename[0]),
diff --git a/src/nvim/testdir/test_arglist.vim b/src/nvim/testdir/test_arglist.vim
index 8139e7a239..fbe4fcc50f 100644
--- a/src/nvim/testdir/test_arglist.vim
+++ b/src/nvim/testdir/test_arglist.vim
@@ -150,6 +150,25 @@ func Test_argument()
let &hidden = save_hidden
+ let save_columns = &columns
+ let &columns = 79
+ exe 'args ' .. join(range(1, 81))
+ call assert_equal(join([
+ \ '',
+ \ '[1] 6 11 16 21 26 31 36 41 46 51 56 61 66 71 76 81 ',
+ \ '2 7 12 17 22 27 32 37 42 47 52 57 62 67 72 77 ',
+ \ '3 8 13 18 23 28 33 38 43 48 53 58 63 68 73 78 ',
+ \ '4 9 14 19 24 29 34 39 44 49 54 59 64 69 74 79 ',
+ \ '5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 ',
+ \ ], "\n"),
+ \ execute('args'))
+
+ " No trailing newline with one item per row.
+ let long_arg = repeat('X', 81)
+ exe 'args ' .. long_arg
+ call assert_equal("\n[".long_arg.']', execute('args'))
+ let &columns = save_columns
+
" Setting argument list should fail when the current buffer has unsaved
" changes
%argd
diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim
index e0d4df48a9..eb18284b4a 100644
--- a/src/nvim/testdir/test_cmdline.vim
+++ b/src/nvim/testdir/test_cmdline.vim
@@ -232,6 +232,15 @@ func Test_getcompletion()
let l = getcompletion('not', 'mapclear')
call assert_equal([], l)
+ let l = getcompletion('.', 'shellcmd')
+ call assert_equal(['./', '../'], l[0:1])
+ call assert_equal(-1, match(l[2:], '^\.\.\?/$'))
+ let root = has('win32') ? 'C:\\' : '/'
+ let l = getcompletion(root, 'shellcmd')
+ let expected = map(filter(glob(root . '*', 0, 1),
+ \ 'isdirectory(v:val) || executable(v:val)'), 'isdirectory(v:val) ? v:val . ''/'' : v:val')
+ call assert_equal(expected, l)
+
if has('cscope')
let l = getcompletion('', 'cscope')
let cmds = ['add', 'find', 'help', 'kill', 'reset', 'show']
@@ -273,8 +282,7 @@ func Test_getcompletion()
call assert_equal([], l)
" For others test if the name is recognized.
- let names = ['buffer', 'environment', 'file_in_path',
- \ 'mapping', 'shellcmd', 'tag', 'tag_listfiles', 'user']
+ let names = ['buffer', 'environment', 'file_in_path', 'mapping', 'tag', 'tag_listfiles', 'user']
if has('cmdline_hist')
call add(names, 'history')
endif
diff --git a/src/nvim/testdir/test_getvar.vim b/src/nvim/testdir/test_getvar.vim
index d6b6b69aa8..3b61d68ebc 100644
--- a/src/nvim/testdir/test_getvar.vim
+++ b/src/nvim/testdir/test_getvar.vim
@@ -1,4 +1,5 @@
-" Tests for getwinvar(), gettabvar() and gettabwinvar().
+" Tests for getwinvar(), gettabvar(), gettabwinvar() and get().
+
func Test_var()
" Use strings to test for memory leaks. First, check that in an empty
" window, gettabvar() returns the correct value
@@ -102,3 +103,44 @@ func Test_gettabvar_in_tabline()
close
redrawstatus!
endfunc
+
+" Test get() function using default value.
+
+" get({dict}, {key} [, {default}])
+func Test_get_dict()
+ let d = {'foo': 42}
+ call assert_equal(42, get(d, 'foo', 99))
+ call assert_equal(999, get(d, 'bar', 999))
+endfunc
+
+" get({list}, {idx} [, {default}])
+func Test_get_list()
+ let l = [1,2,3]
+ call assert_equal(1, get(l, 0, 999))
+ call assert_equal(3, get(l, -1, 999))
+ call assert_equal(999, get(l, 3, 999))
+endfunc
+
+" get({blob}, {idx} [, {default}]) - in test_blob.vim
+
+" get({lambda}, {what} [, {default}])
+func Test_get_lambda()
+ let l:L = {-> 42}
+ call assert_match('^<lambda>', get(l:L, 'name'))
+ call assert_equal(l:L, get(l:L, 'func'))
+ call assert_equal({'lambda has': 'no dict'}, get(l:L, 'dict', {'lambda has': 'no dict'}))
+ call assert_equal(0, get(l:L, 'dict'))
+ call assert_equal([], get(l:L, 'args'))
+endfunc
+
+" get({func}, {what} [, {default}])
+func Test_get_func()
+ let l:F = function('tr')
+ call assert_equal('tr', get(l:F, 'name'))
+ call assert_equal(l:F, get(l:F, 'func'))
+ call assert_equal({'func has': 'no dict'}, get(l:F, 'dict', {'func has': 'no dict'}))
+ call assert_equal(0, get(l:F, 'dict'))
+ call assert_equal([], get(l:F, 'args'))
+endfunc
+
+" get({partial}, {what} [, {default}]) - in test_partial.vim
diff --git a/src/nvim/testdir/test_maparg.vim b/src/nvim/testdir/test_maparg.vim
index 0fb878b04a..ee16a22398 100644
--- a/src/nvim/testdir/test_maparg.vim
+++ b/src/nvim/testdir/test_maparg.vim
@@ -27,6 +27,10 @@ function Test_maparg()
call assert_equal({'silent': 0, 'noremap': 0, 'lhs': 'foo', 'mode': ' ',
\ 'nowait': 1, 'expr': 0, 'sid': sid, 'rhs': 'bar', 'buffer': 1},
\ maparg('foo', '', 0, 1))
+ tmap baz foo
+ call assert_equal({'silent': 0, 'noremap': 0, 'lhs': 'baz', 'mode': 't',
+ \ 'nowait': 0, 'expr': 0, 'sid': sid, 'rhs': 'foo', 'buffer': 0},
+ \ maparg('baz', 't', 0, 1))
map abc x<char-114>x
call assert_equal("xrx", maparg('abc'))
diff --git a/src/nvim/testdir/test_partial.vim b/src/nvim/testdir/test_partial.vim
index de5c26c2dd..590e18e024 100644
--- a/src/nvim/testdir/test_partial.vim
+++ b/src/nvim/testdir/test_partial.vim
@@ -285,6 +285,11 @@ func Test_get_partial_items()
call assert_equal('MyDictFunc', get(Func, 'name'))
call assert_equal([], get(Func, 'args'))
call assert_true(empty( get(Func, 'dict')))
+
+ let P = function('substitute', ['hello there', 'there'])
+ let dict = {'partial has': 'no dict'}
+ call assert_equal(dict, get(P, 'dict', dict))
+ call assert_equal(0, get(l:P, 'dict'))
endfunc
func Test_compare_partials()
diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim
index 557f60751c..98e9de9ffb 100644
--- a/src/nvim/testdir/test_popup.vim
+++ b/src/nvim/testdir/test_popup.vim
@@ -841,4 +841,38 @@ func Test_popup_complete_info_02()
bwipe!
endfunc
+func Test_CompleteChanged()
+ new
+ call setline(1, ['foo', 'bar', 'foobar', ''])
+ set complete=. completeopt=noinsert,noselect,menuone
+ function! OnPumChange()
+ let g:event = copy(v:event)
+ let g:item = get(v:event, 'completed_item', {})
+ let g:word = get(g:item, 'word', v:null)
+ endfunction
+ augroup AAAAA_Group
+ au!
+ autocmd CompleteChanged * :call OnPumChange()
+ augroup END
+ call cursor(4, 1)
+
+ call feedkeys("Sf\<C-N>", 'tx')
+ call assert_equal({'completed_item': {}, 'width': 15,
+ \ 'height': 2, 'size': 2,
+ \ 'col': 0, 'row': 4, 'scrollbar': v:false}, g:event)
+ call feedkeys("a\<C-N>\<C-N>\<C-E>", 'tx')
+ call assert_equal('foo', g:word)
+ call feedkeys("a\<C-N>\<C-N>\<C-N>\<C-E>", 'tx')
+ call assert_equal('foobar', g:word)
+ call feedkeys("a\<C-N>\<C-N>\<C-N>\<C-N>\<C-E>", 'tx')
+ call assert_equal(v:null, g:word)
+ call feedkeys("a\<C-N>\<C-N>\<C-N>\<C-N>\<C-P>", 'tx')
+ call assert_equal('foobar', g:word)
+
+ autocmd! AAAAA_Group
+ set complete& completeopt&
+ delfunc! OnPumchange
+ bw!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim
index f3e40e1210..f39e53d6dd 100644
--- a/src/nvim/testdir/test_vimscript.vim
+++ b/src/nvim/testdir/test_vimscript.vim
@@ -21,7 +21,7 @@ com! -nargs=1 Xout call Xout(<args>)
"
" Create a script that consists of the body of the function a:funcname.
" Replace any ":return" by a ":finish", any argument variable by a global
-" variable, and and every ":call" by a ":source" for the next following argument
+" variable, and every ":call" by a ":source" for the next following argument
" in the variable argument list. This function is useful if similar tests are
" to be made for a ":return" from a function call or a ":finish" in a script
" file.
@@ -1310,6 +1310,43 @@ func Test_compound_assignment_operators()
let x .= 'n'
call assert_equal('2n', x)
+ " Test special cases: division or modulus with 0.
+ let x = 1
+ let x /= 0
+ if has('num64')
+ call assert_equal(0x7FFFFFFFFFFFFFFF, x)
+ else
+ call assert_equal(0x7fffffff, x)
+ endif
+
+ let x = -1
+ let x /= 0
+ if has('num64')
+ call assert_equal(-0x7FFFFFFFFFFFFFFF, x)
+ else
+ call assert_equal(-0x7fffffff, x)
+ endif
+
+ let x = 0
+ let x /= 0
+ if has('num64')
+ call assert_equal(-0x7FFFFFFFFFFFFFFF - 1, x)
+ else
+ call assert_equal(-0x7FFFFFFF - 1, x)
+ endif
+
+ let x = 1
+ let x %= 0
+ call assert_equal(0, x)
+
+ let x = -1
+ let x %= 0
+ call assert_equal(0, x)
+
+ let x = 0
+ let x %= 0
+ call assert_equal(0, x)
+
" Test for string
let x = 'str'
let x .= 'ing'
diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c
index d0b21ae591..2cb3cf7ee7 100644
--- a/src/nvim/ui_compositor.c
+++ b/src/nvim/ui_compositor.c
@@ -359,7 +359,7 @@ static void compose_line(Integer row, Integer startcol, Integer endcol,
attrbuf[i+1], &thru);
}
if (thru) {
- memcpy(linebuf[i], bg_line[i], (size_t)width * sizeof(linebuf[i]));
+ memcpy(linebuf + i, bg_line + i, (size_t)width * sizeof(linebuf[i]));
}
}
}
diff --git a/src/nvim/version.c b/src/nvim/version.c
index baa0df84f4..33b2a05cbb 100644
--- a/src/nvim/version.c
+++ b/src/nvim/version.c
@@ -2038,6 +2038,9 @@ static void version_msg(char *s)
static void list_features(void)
{
list_in_columns((char_u **)features, -1, -1);
+ if (msg_col > 0) {
+ msg_putchar('\n');
+ }
MSG_PUTS("See \":help feature-compile\"\n\n");
}
@@ -2065,7 +2068,7 @@ void list_in_columns(char_u **items, int size, int current)
// Not enough screen columns - show one per line
for (i = 0; i < item_count; i++) {
version_msg_wrap(items[i], i == current);
- if (msg_col > 0) {
+ if (msg_col > 0 && i < item_count - 1) {
msg_putchar('\n');
}
}
@@ -2100,6 +2103,14 @@ void list_in_columns(char_u **items, int size, int current)
msg_putchar(' ');
}
}
+ } else {
+ // this row is out of items, thus at the end of the row
+ if (msg_col > 0) {
+ if (cur_row < nrow) {
+ msg_putchar('\n');
+ }
+ cur_row++;
+ }
}
}
}
diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c
index dcc64db8a0..b4a0f57e99 100644
--- a/src/nvim/viml/parser/expressions.c
+++ b/src/nvim/viml/parser/expressions.c
@@ -2330,7 +2330,7 @@ viml_pexpr_parse_invalid_comma:
break;
} else if (eastnode_type == kExprNodeSubscript) {
is_subscript = true;
- can_be_ternary = false;
+ // can_be_ternary = false;
assert(!is_ternary);
break;
} else if (eastnode_type == kExprNodeColon) {