diff options
Diffstat (limited to 'src/nvim')
40 files changed, 1394 insertions, 345 deletions
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, ®, &is_unnamed); + reg_iter = op_global_reg_iter(reg_iter, &name, ®, &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, ¬anum); + 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(®match, 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, ®, &is_unnamed); + reg_iter = op_global_reg_iter(reg_iter, &name, ®, &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) { |