diff options
Diffstat (limited to 'src')
35 files changed, 486 insertions, 203 deletions
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 66c4454f7b..6142db049d 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -222,11 +222,7 @@ Boolean nvim_buf_attach(uint64_t channel_id, return buf_updates_register(buf, channel_id, cb, send_buffer); error: - // TODO(bfredl): ASAN build should check that the ref table is empty? - api_free_luaref(cb.on_lines); - api_free_luaref(cb.on_bytes); - api_free_luaref(cb.on_changedtick); - api_free_luaref(cb.on_detach); + buffer_update_callbacks_free(cb); return false; } diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index d2b787a6f5..382244d6b3 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -1708,33 +1708,6 @@ const char *describe_ns(NS ns_id) return "(UNKNOWN PLUGIN)"; } -DecorProvider *get_provider(NS ns_id, bool force) -{ - ssize_t i; - for (i = 0; i < (ssize_t)kv_size(decor_providers); i++) { - DecorProvider *item = &kv_A(decor_providers, i); - if (item->ns_id == ns_id) { - return item; - } else if (item->ns_id > ns_id) { - break; - } - } - - if (!force) { - return NULL; - } - - for (ssize_t j = (ssize_t)kv_size(decor_providers)-1; j >= i; j++) { - // allocates if needed: - (void)kv_a(decor_providers, (size_t)j+1); - kv_A(decor_providers, (size_t)j+1) = kv_A(decor_providers, j); - } - DecorProvider *item = &kv_a(decor_providers, (size_t)i); - *item = DECORATION_PROVIDER_INIT(ns_id); - - return item; -} - static bool parse_float_anchor(String anchor, FloatAnchor *out) { if (anchor.size == 0) { diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 9dde62f0ee..3ae944de4d 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -2708,6 +2708,7 @@ Dictionary nvim__stats(void) Dictionary rv = ARRAY_DICT_INIT; PUT(rv, "fsync", INTEGER_OBJ(g_stats.fsync)); PUT(rv, "redraw", INTEGER_OBJ(g_stats.redraw)); + PUT(rv, "lua_refcount", INTEGER_OBJ(nlua_refcount)); return rv; } @@ -2880,19 +2881,6 @@ void nvim__screenshot(String path) ui_call_screenshot(path); } -static void clear_provider(DecorProvider *p) -{ - if (p == NULL) { - return; - } - NLUA_CLEAR_REF(p->redraw_start); - NLUA_CLEAR_REF(p->redraw_buf); - NLUA_CLEAR_REF(p->redraw_win); - NLUA_CLEAR_REF(p->redraw_line); - NLUA_CLEAR_REF(p->redraw_end); - p->active = false; -} - /// Set or change decoration provider for a namespace /// /// This is a very general purpose interface for having lua callbacks @@ -2937,8 +2925,8 @@ void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts, Error *err) FUNC_API_SINCE(7) FUNC_API_LUA_ONLY { - DecorProvider *p = get_provider((NS)ns_id, true); - clear_provider(p); + DecorProvider *p = get_decor_provider((NS)ns_id, true); + decor_provider_clear(p); // regardless of what happens, it seems good idea to redraw redraw_all_later(NOT_VALID); // TODO(bfredl): too soon? @@ -2960,7 +2948,7 @@ void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts, String k = opts.items[i].key; Object *v = &opts.items[i].value; size_t j; - for (j = 0; cbs[j].name; j++) { + for (j = 0; cbs[j].name && cbs[j].dest; j++) { if (strequal(cbs[j].name, k.data)) { if (v->type != kObjectTypeLuaRef) { api_set_error(err, kErrorTypeValidation, @@ -2981,5 +2969,5 @@ void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts, p->active = true; return; error: - clear_provider(p); + decor_provider_clear(p); } diff --git a/src/nvim/ascii.h b/src/nvim/ascii.h index f41068ea70..7e4dee3d34 100644 --- a/src/nvim/ascii.h +++ b/src/nvim/ascii.h @@ -31,7 +31,9 @@ #define CSI 0x9b // Control Sequence Introducer #define CSI_STR "\233" #define DCS 0x90 // Device Control String +#define DCS_STR "\033P" #define STERM 0x9c // String Terminator +#define STERM_STR "\033\\" #define POUND 0xA3 diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index f71075ae74..145f6f5601 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -1621,13 +1621,21 @@ static bool apply_autocmds_group(event_T event, ap->last = false; } ap->last = true; - check_lnums(true); // make sure cursor and topline are valid + + if (nesting == 1) { + // make sure cursor and topline are valid + check_lnums(true); + } // Execute the autocmd. The `getnextac` callback handles iteration. do_cmdline(NULL, getnextac, (void *)&patcmd, DOCMD_NOWAIT | DOCMD_VERBOSE | DOCMD_REPEAT); - reset_lnums(); // restore cursor and topline, unless they were changed + if (nesting == 1) { + // restore cursor and topline, unless they were changed + reset_lnums(); + } + if (eap != NULL) { (void)set_cmdarg(NULL, save_cmdarg); diff --git a/src/nvim/buffer_updates.c b/src/nvim/buffer_updates.c index 97562eace6..5c573530d1 100644 --- a/src/nvim/buffer_updates.c +++ b/src/nvim/buffer_updates.c @@ -176,7 +176,7 @@ void buf_updates_unload(buf_T *buf, bool can_reload) if (keep) { kv_A(buf->update_callbacks, j++) = kv_A(buf->update_callbacks, i); } else { - free_update_callbacks(cb); + buffer_update_callbacks_free(cb); } } kv_size(buf->update_callbacks) = j; @@ -290,7 +290,7 @@ void buf_updates_send_changes(buf_T *buf, textlock--; if (res.type == kObjectTypeBoolean && res.data.boolean == true) { - free_update_callbacks(cb); + buffer_update_callbacks_free(cb); keep = false; } api_free_object(res); @@ -342,7 +342,7 @@ void buf_updates_send_splice( textlock--; if (res.type == kObjectTypeBoolean && res.data.boolean == true) { - free_update_callbacks(cb); + buffer_update_callbacks_free(cb); keep = false; } } @@ -378,7 +378,7 @@ void buf_updates_changedtick(buf_T *buf) textlock--; if (res.type == kObjectTypeBoolean && res.data.boolean == true) { - free_update_callbacks(cb); + buffer_update_callbacks_free(cb); keep = false; } api_free_object(res); @@ -406,8 +406,11 @@ void buf_updates_changedtick_single(buf_T *buf, uint64_t channel_id) rpc_send_event(channel_id, "nvim_buf_changedtick_event", args); } -static void free_update_callbacks(BufUpdateCallbacks cb) +void buffer_update_callbacks_free(BufUpdateCallbacks cb) { api_free_luaref(cb.on_lines); + api_free_luaref(cb.on_bytes); api_free_luaref(cb.on_changedtick); + api_free_luaref(cb.on_reload); + api_free_luaref(cb.on_detach); } diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index e16598e7d2..52a48ae6fb 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -2,6 +2,7 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include "nvim/vim.h" +#include "nvim/lua/executor.h" #include "nvim/extmark.h" #include "nvim/decoration.h" #include "nvim/screen.h" @@ -365,3 +366,52 @@ void decor_add_ephemeral(int start_row, int start_col, int end_row, int end_col, decor_add(&decor_state, start_row, start_col, end_row, end_col, decor, true, priority); } + + +DecorProvider *get_decor_provider(NS ns_id, bool force) +{ + ssize_t i; + for (i = 0; i < (ssize_t)kv_size(decor_providers); i++) { + DecorProvider *item = &kv_A(decor_providers, i); + if (item->ns_id == ns_id) { + return item; + } else if (item->ns_id > ns_id) { + break; + } + } + + if (!force) { + return NULL; + } + + for (ssize_t j = (ssize_t)kv_size(decor_providers)-1; j >= i; j++) { + // allocates if needed: + (void)kv_a(decor_providers, (size_t)j+1); + kv_A(decor_providers, (size_t)j+1) = kv_A(decor_providers, j); + } + DecorProvider *item = &kv_a(decor_providers, (size_t)i); + *item = DECORATION_PROVIDER_INIT(ns_id); + + return item; +} + +void decor_provider_clear(DecorProvider *p) +{ + if (p == NULL) { + return; + } + NLUA_CLEAR_REF(p->redraw_start); + NLUA_CLEAR_REF(p->redraw_buf); + NLUA_CLEAR_REF(p->redraw_win); + NLUA_CLEAR_REF(p->redraw_line); + NLUA_CLEAR_REF(p->redraw_end); + p->active = false; +} + +void decor_free_all_mem(void) +{ + for (size_t i = 0; i < kv_size(decor_providers); i++) { + decor_provider_clear(&kv_A(decor_providers, i)); + } + kv_destroy(decor_providers); +} diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 9c3941b0fd..550fe8ab65 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -5981,6 +5981,35 @@ static void assert_append_cmd_or_arg(garray_T *gap, typval_T *argvars, } } +int assert_beeps(typval_T *argvars, bool no_beep) + FUNC_ATTR_NONNULL_ALL +{ + const char *const cmd = tv_get_string_chk(&argvars[0]); + int ret = 0; + + called_vim_beep = false; + suppress_errthrow = true; + emsg_silent = false; + do_cmdline_cmd(cmd); + if (no_beep ? called_vim_beep : !called_vim_beep) { + garray_T ga; + prepare_assert_error(&ga); + if (no_beep) { + ga_concat(&ga, (const char_u *)"command did beep: "); + } else { + ga_concat(&ga, (const char_u *)"command did not beep: "); + } + ga_concat(&ga, (const char_u *)cmd); + assert_error(&ga); + ga_clear(&ga); + ret = 1; + } + + suppress_errthrow = false; + emsg_on_display = false; + return ret; +} + int assert_fails(typval_T *argvars) FUNC_ATTR_NONNULL_ALL { @@ -6234,6 +6263,7 @@ void common_function(typval_T *argvars, typval_T *rettv, // function(dict.MyFunc, [arg]) arg_pt = argvars[0].vval.v_partial; s = partial_name(arg_pt); + // TODO(bfredl): do the entire nlua_is_table_from_lua dance } else { // function('MyFunc', [arg], dict) s = (char_u *)tv_get_string(&argvars[0]); @@ -7333,7 +7363,6 @@ bool callback_from_typval(Callback *const callback, typval_T *const arg) char_u *name = nlua_register_table_as_callable(arg); if (name != NULL) { - func_ref(name); callback->data.funcref = vim_strsave(name); callback->type = kCallbackFuncref; } else { diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 72168060cc..aed0824ad6 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -26,7 +26,7 @@ return { arglistid={args={0, 2}}, argv={args={0, 2}}, asin={args=1, func="float_op_wrapper", data="&asin"}, -- WJMc - assert_beeps={args={1, 2}}, + assert_beeps={args={1}}, assert_equal={args={2, 3}}, assert_equalfile={args={2, 3}}, assert_exception={args={1, 2}}, @@ -34,6 +34,7 @@ return { assert_false={args={1, 2}}, assert_inrange={args={3, 4}}, assert_match={args={2, 3}}, + assert_nobeep={args={1}}, assert_notequal={args={2, 3}}, assert_notmatch={args={2, 3}}, assert_report={args=1}, diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index deeda28571..51e5a27348 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -391,28 +391,16 @@ static void f_argv(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } +// "assert_beeps(cmd [, error])" function static void f_assert_beeps(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - const char *const cmd = tv_get_string_chk(&argvars[0]); - garray_T ga; - int ret = 0; - - called_vim_beep = false; - suppress_errthrow = true; - emsg_silent = false; - do_cmdline_cmd(cmd); - if (!called_vim_beep) { - prepare_assert_error(&ga); - ga_concat(&ga, (const char_u *)"command did not beep: "); - ga_concat(&ga, (const char_u *)cmd); - assert_error(&ga); - ga_clear(&ga); - ret = 1; - } + rettv->vval.v_number = assert_beeps(argvars, false); +} - suppress_errthrow = false; - emsg_on_display = false; - rettv->vval.v_number = ret; +// "assert_nobeep(cmd [, error])" function +static void f_assert_nobeep(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_beeps(argvars, true); } // "assert_equal(expected, actual[, msg])" function @@ -822,6 +810,7 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } + bool owned = false; char_u *func; partial_T *partial = NULL; dict_T *selfdict = NULL; @@ -832,6 +821,7 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr) func = partial_name(partial); } else if (nlua_is_table_from_lua(&argvars[0])) { func = nlua_register_table_as_callable(&argvars[0]); + owned = true; } else { func = (char_u *)tv_get_string(&argvars[0]); } @@ -849,6 +839,9 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr) } func_call(func, &argvars[1], partial, selfdict, rettv); + if (owned) { + func_unref(func); + } } /* diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index fe3d147040..71e4edc667 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -219,6 +219,7 @@ list_T *tv_list_alloc(const ptrdiff_t len) list->lv_used_next = gc_first_list; gc_first_list = list; list_log(list, NULL, (void *)(uintptr_t)len, "alloc"); + list->lua_table_ref = LUA_NOREF; return list; } @@ -302,7 +303,7 @@ void tv_list_free_list(list_T *const l) } list_log(l, NULL, NULL, "freelist"); - nlua_free_typval_list(l); + NLUA_CLEAR_REF(l->lua_table_ref); xfree(l); } @@ -1404,6 +1405,8 @@ dict_T *tv_dict_alloc(void) d->dv_copyID = 0; QUEUE_INIT(&d->watchers); + d->lua_table_ref = LUA_NOREF; + return d; } @@ -1454,7 +1457,7 @@ void tv_dict_free_dict(dict_T *const d) d->dv_used_next->dv_used_prev = d->dv_used_prev; } - nlua_free_typval_dict(d); + NLUA_CLEAR_REF(d->lua_table_ref); xfree(d); } diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index c4c18c4324..cc0ec71627 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -2535,7 +2535,7 @@ void ex_source(exarg_T *eap) static void cmd_source(char_u *fname, exarg_T *eap) { - if (*fname == NUL) { + if (eap != NULL && *fname == NUL) { cmd_source_buffer(eap); } else if (eap != NULL && eap->forceit) { // ":source!": read Normal mode commands @@ -2575,7 +2575,8 @@ static char_u *get_buffer_line(int c, void *cookie, int indent, bool do_concat) return (char_u *)xstrdup((const char *)curr_line); } -static void cmd_source_buffer(exarg_T *eap) +static void cmd_source_buffer(const exarg_T *eap) + FUNC_ATTR_NONNULL_ALL { GetBufferLineCookie cookie = { .curr_lnum = eap->line1, diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 9977be56ca..38385d19b2 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -775,9 +775,9 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) redrawcmd(); } - // redraw the statusline for statuslines that display the current mode - // using the mode() function. - if (!cmd_silent && msg_scrolled == 0) { + // Redraw the statusline in case it uses the current mode using the mode() + // function. + if (!cmd_silent && msg_scrolled == 0 && *p_stl != NUL) { curwin->w_redr_status = true; redraw_statuslines(); } @@ -4093,9 +4093,10 @@ ExpandOne ( } if (mode == WILD_CANCEL) { - ss = vim_strsave(orig_save); + ss = vim_strsave(orig_save ? orig_save : (char_u *)""); } else if (mode == WILD_APPLY) { - ss = vim_strsave(findex == -1 ? orig_save : xp->xp_files[findex]); + ss = vim_strsave(findex == -1 ? (orig_save ? orig_save : (char_u *)"") : + xp->xp_files[findex]); } /* free old names */ diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 714bbb5780..65bd809436 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -4280,7 +4280,7 @@ char *modname(const char *fname, const char *ext, bool prepend_dot) if (fname == NULL || *fname == NUL) { retval = xmalloc(MAXPATHL + extlen + 3); // +3 for PATHSEP, "_" (Win), NUL if (os_dirname((char_u *)retval, MAXPATHL) == FAIL - || (fnamelen = strlen(retval)) == 0) { + || strlen(retval) == 0) { xfree(retval); return NULL; } diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index f03382bea7..329c448cf0 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -151,7 +151,7 @@ int hl_get_syn_attr(int ns_id, int idx, HlAttrs at_en) void ns_hl_def(NS ns_id, int hl_id, HlAttrs attrs, int link_id) { - DecorProvider *p = get_provider(ns_id, true); + DecorProvider *p = get_decor_provider(ns_id, true); if ((attrs.rgb_ae_attr & HL_DEFAULT) && map_has(ColorKey, ColorItem)(ns_hl, ColorKey(ns_id, hl_id))) { return; @@ -175,7 +175,7 @@ int ns_get_hl(NS ns_id, int hl_id, bool link, bool nodefault) ns_id = ns_hl_active; } - DecorProvider *p = get_provider(ns_id, true); + DecorProvider *p = get_decor_provider(ns_id, true); ColorItem it = map_get(ColorKey, ColorItem)(ns_hl, ColorKey(ns_id, hl_id)); // TODO(bfredl): map_ref true even this? bool valid_cache = it.version >= p->hl_valid; diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 83b3729ad3..ce8c9b0d06 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -400,7 +400,6 @@ nlua_pop_typval_table_processing_end: case LUA_TFUNCTION: { LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState)); state->lua_callable.func_ref = nlua_ref(lstate, -1); - state->lua_callable.table_ref = LUA_NOREF; char_u *name = register_cfunc( &nlua_CFunction_func_call, @@ -412,6 +411,7 @@ nlua_pop_typval_table_processing_end: break; } case LUA_TUSERDATA: { + // TODO(bfredl): check mt.__call and convert to function? nlua_pushref(lstate, nlua_nil_ref); bool is_nil = lua_rawequal(lstate, -2, -1); lua_pop(lstate, 1); diff --git a/src/nvim/lua/converter.h b/src/nvim/lua/converter.h index 8601a32418..43a7e06019 100644 --- a/src/nvim/lua/converter.h +++ b/src/nvim/lua/converter.h @@ -11,7 +11,6 @@ typedef struct { LuaRef func_ref; - LuaRef table_ref; } LuaCallable; typedef struct { diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 03d178467b..9b8e9ff8cc 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -5,6 +5,7 @@ #include <lualib.h> #include <lauxlib.h> +#include "nvim/assert.h" #include "nvim/version.h" #include "nvim/misc1.h" #include "nvim/getchar.h" @@ -18,6 +19,7 @@ #include "nvim/vim.h" #include "nvim/ex_getln.h" #include "nvim/ex_cmds2.h" +#include "nvim/map.h" #include "nvim/message.h" #include "nvim/memline.h" #include "nvim/buffer_defs.h" @@ -32,9 +34,7 @@ #include "nvim/event/time.h" #include "nvim/event/loop.h" -#ifdef WIN32 #include "nvim/os/os.h" -#endif #include "nvim/lua/converter.h" #include "nvim/lua/executor.h" @@ -63,6 +63,11 @@ typedef struct { } \ } +#if __has_feature(address_sanitizer) + PMap(handle_T) *nlua_ref_markers = NULL; +# define NLUA_TRACK_REFS +#endif + /// Convert lua error into a Vim error message /// /// @param lstate Lua interpreter state. @@ -547,6 +552,13 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL static lua_State *nlua_init(void) FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT { +#ifdef NLUA_TRACK_REFS + const char *env = os_getenv("NVIM_LUA_NOTRACK"); + if (!env || !*env) { + nlua_ref_markers = pmap_new(handle_T)(); + } +#endif + lua_State *lstate = luaL_newstate(); if (lstate == NULL) { EMSG(_("E970: Failed to initialize lua interpreter")); @@ -554,9 +566,13 @@ static lua_State *nlua_init(void) } luaL_openlibs(lstate); nlua_state_init(lstate); + return lstate; } +// only to be used by nlua_enter and nlua_free_all_mem! +static lua_State *global_lstate = NULL; + /// Enter lua interpreter /// /// Calls nlua_init() if needed. Is responsible for pre-lua call initalization @@ -567,26 +583,39 @@ static lua_State *nlua_init(void) static lua_State *nlua_enter(void) FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT { - static lua_State *global_lstate = NULL; if (global_lstate == NULL) { global_lstate = nlua_init(); } lua_State *const lstate = global_lstate; - // Last used p_rtp value. Must not be dereferenced because value pointed to - // may already be freed. Used to check whether &runtimepath option value - // changed. - static const void *last_p_rtp = NULL; - if (last_p_rtp != (const void *)p_rtp) { - // stack: (empty) - lua_getglobal(lstate, "vim"); - // stack: vim - lua_pop(lstate, 1); - // stack: (empty) - last_p_rtp = (const void *)p_rtp; - } return lstate; } +void nlua_free_all_mem(void) +{ + if (!global_lstate) { + return; + } + lua_State *lstate = global_lstate; + + nlua_unref(lstate, nlua_nil_ref); + nlua_unref(lstate, nlua_empty_dict_ref); + +#ifdef NLUA_TRACK_REFS + if (nlua_refcount) { + fprintf(stderr, "%d lua references were leaked!", nlua_refcount); + } + + if (nlua_ref_markers) { + // in case there are leaked luarefs, leak the associated memory + // to get LeakSanitizer stacktraces on exit + pmap_free(handle_T)(nlua_ref_markers); + } +#endif + + nlua_refcount = 0; + lua_close(lstate); +} + static void nlua_print_event(void **argv) { char *str = argv[0]; @@ -866,17 +895,35 @@ static int nlua_getenv(lua_State *lstate) } #endif + /// add the value to the registry LuaRef nlua_ref(lua_State *lstate, int index) { lua_pushvalue(lstate, index); - return luaL_ref(lstate, LUA_REGISTRYINDEX); + LuaRef ref = luaL_ref(lstate, LUA_REGISTRYINDEX); + if (ref > 0) { + nlua_refcount++; +#ifdef NLUA_TRACK_REFS + if (nlua_ref_markers) { + // dummy allocation to make LeakSanitizer track our luarefs + pmap_put(handle_T)(nlua_ref_markers, ref, xmalloc(3)); + } +#endif + } + return ref; } /// remove the value from the registry void nlua_unref(lua_State *lstate, LuaRef ref) { if (ref > 0) { + nlua_refcount--; +#ifdef NLUA_TRACK_REFS + // NB: don't remove entry from map to track double-unref + if (nlua_ref_markers) { + xfree(pmap_get(handle_T)(nlua_ref_markers, ref)); + } +#endif luaL_unref(lstate, LUA_REGISTRYINDEX, ref); } } @@ -893,19 +940,11 @@ void nlua_pushref(lua_State *lstate, LuaRef ref) lua_rawgeti(lstate, LUA_REGISTRYINDEX, ref); } + /// Gets a new reference to an object stored at original_ref /// /// NOTE: It does not copy the value, it creates a new ref to the lua object. /// Leaves the stack unchanged. -LuaRef nlua_newref(lua_State *lstate, LuaRef original_ref) -{ - nlua_pushref(lstate, original_ref); - LuaRef new_ref = nlua_ref(lstate, -1); - lua_pop(lstate, 1); - - return new_ref; -} - LuaRef api_new_luaref(LuaRef original_ref) { if (original_ref == LUA_NOREF) { @@ -913,7 +952,10 @@ LuaRef api_new_luaref(LuaRef original_ref) } lua_State *const lstate = nlua_enter(); - return nlua_newref(lstate, original_ref); + nlua_pushref(lstate, original_ref); + LuaRef new_ref = nlua_ref(lstate, -1); + lua_pop(lstate, 1); + return new_ref; } @@ -1023,25 +1065,13 @@ int typval_exec_lua_callable( typval_T *rettv ) { - int offset = 0; LuaRef cb = lua_cb.func_ref; - if (cb == LUA_NOREF) { - // This shouldn't happen. - luaL_error(lstate, "Invalid function passed to VimL"); - return ERROR_OTHER; - } - nlua_pushref(lstate, cb); - if (lua_cb.table_ref != LUA_NOREF) { - offset += 1; - nlua_pushref(lstate, lua_cb.table_ref); - } - PUSH_ALL_TYPVALS(lstate, argvars, argcount, false); - if (lua_pcall(lstate, argcount + offset, 1, 0)) { + if (lua_pcall(lstate, argcount, 1, 0)) { nlua_print(lstate); return ERROR_OTHER; } @@ -1508,6 +1538,8 @@ static int regex_match_line(lua_State *lstate) return nret; } +// Required functions for lua c functions as VimL callbacks + int nlua_CFunction_func_call( int argcount, typval_T *argvars, @@ -1517,53 +1549,40 @@ int nlua_CFunction_func_call( lua_State *const lstate = nlua_enter(); LuaCFunctionState *funcstate = (LuaCFunctionState *)state; - return typval_exec_lua_callable( - lstate, - funcstate->lua_callable, - argcount, - argvars, - rettv); + return typval_exec_lua_callable(lstate, funcstate->lua_callable, + argcount, argvars, rettv); } -/// Required functions for lua c functions as VimL callbacks + void nlua_CFunction_func_free(void *state) { lua_State *const lstate = nlua_enter(); LuaCFunctionState *funcstate = (LuaCFunctionState *)state; nlua_unref(lstate, funcstate->lua_callable.func_ref); - nlua_unref(lstate, funcstate->lua_callable.table_ref); xfree(funcstate); } bool nlua_is_table_from_lua(typval_T *const arg) { - if (arg->v_type != VAR_DICT && arg->v_type != VAR_LIST) { - return false; - } - if (arg->v_type == VAR_DICT) { - return arg->vval.v_dict->lua_table_ref > 0 - && arg->vval.v_dict->lua_table_ref != LUA_NOREF; + return arg->vval.v_dict->lua_table_ref != LUA_NOREF; } else if (arg->v_type == VAR_LIST) { - return arg->vval.v_list->lua_table_ref > 0 - && arg->vval.v_list->lua_table_ref != LUA_NOREF; + return arg->vval.v_list->lua_table_ref != LUA_NOREF; + } else { + return false; } - - return false; } char_u *nlua_register_table_as_callable(typval_T *const arg) { - if (!nlua_is_table_from_lua(arg)) { - return NULL; - } - - LuaRef table_ref; + LuaRef table_ref = LUA_NOREF; if (arg->v_type == VAR_DICT) { table_ref = arg->vval.v_dict->lua_table_ref; } else if (arg->v_type == VAR_LIST) { table_ref = arg->vval.v_list->lua_table_ref; - } else { + } + + if (table_ref == LUA_NOREF) { return NULL; } @@ -1573,55 +1592,34 @@ char_u *nlua_register_table_as_callable(typval_T *const arg) int top = lua_gettop(lstate); #endif - nlua_pushref(lstate, table_ref); + nlua_pushref(lstate, table_ref); // [table] if (!lua_getmetatable(lstate, -1)) { + lua_pop(lstate, 1); + assert(top == lua_gettop(lstate)); return NULL; - } + } // [table, mt] - lua_getfield(lstate, -1, "__call"); + lua_getfield(lstate, -1, "__call"); // [table, mt, mt.__call] if (!lua_isfunction(lstate, -1)) { + lua_pop(lstate, 3); + assert(top == lua_gettop(lstate)); return NULL; } - - LuaRef new_table_ref = nlua_newref(lstate, table_ref); + lua_pop(lstate, 2); // [table] LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState)); state->lua_callable.func_ref = nlua_ref(lstate, -1); - state->lua_callable.table_ref = new_table_ref; - char_u *name = register_cfunc( - &nlua_CFunction_func_call, - &nlua_CFunction_func_free, - state); + char_u *name = register_cfunc(&nlua_CFunction_func_call, + &nlua_CFunction_func_free, state); - lua_pop(lstate, 3); + lua_pop(lstate, 1); // [] assert(top == lua_gettop(lstate)); return name; } -/// Helper function to free a list_T -void nlua_free_typval_list(list_T *const l) -{ - if (l->lua_table_ref != LUA_NOREF && l->lua_table_ref > 0) { - lua_State *const lstate = nlua_enter(); - nlua_unref(lstate, l->lua_table_ref); - l->lua_table_ref = LUA_NOREF; - } -} - - -/// Helper function to free a dict_T -void nlua_free_typval_dict(dict_T *const d) -{ - if (d->lua_table_ref != LUA_NOREF && d->lua_table_ref > 0) { - lua_State *const lstate = nlua_enter(); - nlua_unref(lstate, d->lua_table_ref); - d->lua_table_ref = LUA_NOREF; - } -} - void nlua_execute_log_keystroke(int c) { char_u buf[NUMBUFLEN]; diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h index 1d7a15d9aa..ea774ac2e3 100644 --- a/src/nvim/lua/executor.h +++ b/src/nvim/lua/executor.h @@ -16,6 +16,8 @@ void nlua_add_api_functions(lua_State *lstate) REAL_FATTR_NONNULL_ALL; EXTERN LuaRef nlua_nil_ref INIT(= LUA_NOREF); EXTERN LuaRef nlua_empty_dict_ref INIT(= LUA_NOREF); +EXTERN int nlua_refcount INIT(= 0); + #define set_api_error(s, err) \ do { \ Error *err_ = (err); \ diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 188b2c1ef7..38848b0266 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -222,8 +222,9 @@ int tslua_inspect_lang(lua_State *L) lua_setfield(L, -2, "symbols"); // [retval] size_t nfields = (size_t)ts_language_field_count(lang); - lua_createtable(L, nfields-1, 1); // [retval, fields] - for (size_t i = 0; i < nfields; i++) { + lua_createtable(L, nfields, 1); // [retval, fields] + // Field IDs go from 1 to nfields inclusive (extra index 0 maps to NULL) + for (size_t i = 1; i <= nfields; i++) { lua_pushstring(L, ts_language_field_name_for_id(lang, i)); lua_rawseti(L, -2, i); // [retval, fields] } diff --git a/src/nvim/memory.c b/src/nvim/memory.c index 9bc6b23ce3..7a8fc4da75 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -19,6 +19,8 @@ #include "nvim/ui.h" #include "nvim/sign.h" #include "nvim/api/vim.h" +#include "nvim/lua/executor.h" +#include "nvim/decoration.h" #ifdef UNIT_TESTING # define malloc(size) mem_malloc(size) @@ -695,6 +697,10 @@ void free_all_mem(void) list_free_log(); check_quickfix_busy(); + + decor_free_all_mem(); + + nlua_free_all_mem(); } #endif diff --git a/src/nvim/message.c b/src/nvim/message.c index dea6696f55..7c98d3c6b5 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -869,18 +869,18 @@ char_u *msg_trunc_attr(char_u *s, int force, int attr) */ char_u *msg_may_trunc(int force, char_u *s) { - int n; int room; room = (int)(Rows - cmdline_row - 1) * Columns + sc_col - 1; if ((force || (shortmess(SHM_TRUNC) && !exmode_active)) - && (n = (int)STRLEN(s) - room) > 0) { + && (int)STRLEN(s) - room > 0) { int size = vim_strsize(s); // There may be room anyway when there are multibyte chars. if (size <= room) { return s; } + int n; for (n = 0; size >= room; ) { size -= utf_ptr2cells(s + n); n += utfc_ptr2len(s + n); diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index fa9787a3ac..4c0339e5f4 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -748,7 +748,7 @@ int mouse_check_fold(void) } } - if (mouse_char == wp->w_p_fcs_chars.foldclosed) { + if (wp && mouse_char == wp->w_p_fcs_chars.foldclosed) { return MOUSE_FOLD_OPEN; } else if (mouse_char != ' ') { return MOUSE_FOLD_CLOSE; diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 2d351f4dba..2cd71f2360 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -2807,7 +2807,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) size_t y_size; size_t oldlen; int y_width = 0; - colnr_T vcol; + colnr_T vcol = 0; int delcount; int incr = 0; struct block_def bd; diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 384636f705..0042d8a2a4 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -3154,6 +3154,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, mb_utf8 = false; } } else { + assert(p_extra != NULL); c = *p_extra; mb_c = c; // If the UTF-8 character is more than one byte: @@ -7613,8 +7614,9 @@ void win_new_shellsize(void) static long old_Columns = 0; if (old_Rows != Rows) { - // if 'window' uses the whole screen, keep it using that */ - if (p_window == old_Rows - 1 || old_Rows == 0) { + // If 'window' uses the whole screen, keep it using that. + // Don't change it when set with "-w size" on the command line. + if (p_window == old_Rows - 1 || (old_Rows == 0 && p_window == 0)) { p_window = Rows - 1; } old_Rows = Rows; diff --git a/src/nvim/search.c b/src/nvim/search.c index 84b71d56a0..9d3d3061e4 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -2326,6 +2326,9 @@ showmatch( return; } } + if (*p == NUL) { + return; + } if ((lpos = findmatch(NULL, NUL)) == NULL) { // no match, so beep vim_beep(BO_MATCH); diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 913ef3baed..afad20f557 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -580,6 +580,9 @@ static bool is_filter_char(int c) void terminal_paste(long count, char_u **y_array, size_t y_size) { + if (y_size == 0) { + return; + } vterm_keyboard_start_paste(curbuf->terminal->vt); terminal_flush_output(curbuf->terminal); size_t buff_len = STRLEN(y_array[0]); diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index a47d20a265..71af3eead7 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -33,6 +33,7 @@ source test_move.vim source test_partial.vim source test_popup.vim source test_put.vim +source test_rename.vim source test_scroll_opt.vim source test_sort.vim source test_sha256.vim diff --git a/src/nvim/testdir/test_compiler.vim b/src/nvim/testdir/test_compiler.vim index d361205baa..c3de7d0050 100644 --- a/src/nvim/testdir/test_compiler.vim +++ b/src/nvim/testdir/test_compiler.vim @@ -60,10 +60,10 @@ func Test_compiler_completion() call assert_match('^"compiler ' .. clist .. '$', @:) call feedkeys(":compiler p\<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"compiler pbx perl php pylint pyunit', @:) + call assert_match('"compiler pbx perl\( p[a-z]\+\)\+ pylint pyunit', @:) call feedkeys(":compiler! p\<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"compiler! pbx perl php pylint pyunit', @:) + call assert_match('"compiler! pbx perl\( p[a-z]\+\)\+ pylint pyunit', @:) endfunc func Test_compiler_error() diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index 44b8479621..fa0bffd96c 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -371,6 +371,8 @@ let s:filename_checks = { \ 'promela': ['file.pml'], \ 'proto': ['file.proto'], \ 'protocols': ['/etc/protocols'], + \ 'ps1': ['file.ps1', 'file.psd1', 'file.psm1', 'file.pssc'], + \ 'ps1xml': ['file.ps1xml'], \ 'psf': ['file.psf'], \ 'puppet': ['file.pp'], \ 'pyrex': ['file.pyx', 'file.pxd'], @@ -521,7 +523,7 @@ let s:filename_checks = { \ 'xhtml': ['file.xhtml', 'file.xht'], \ 'xinetd': ['/etc/xinetd.conf'], \ 'xmath': ['file.msc', 'file.msf'], - \ 'xml': ['/etc/blkid.tab', '/etc/blkid.tab.old', 'file.xmi', 'file.csproj', 'file.csproj.user', 'file.ui', 'file.tpm', '/etc/xdg/menus/file.menu', 'fglrxrc', 'file.xlf', 'file.xliff', 'file.xul', 'file.wsdl', 'file.wpl', 'any/etc/blkid.tab', 'any/etc/blkid.tab.old', 'any/etc/xdg/menus/file.menu', 'file.atom', 'file.rss'], + \ 'xml': ['/etc/blkid.tab', '/etc/blkid.tab.old', 'file.xmi', 'file.csproj', 'file.csproj.user', 'file.ui', 'file.tpm', '/etc/xdg/menus/file.menu', 'fglrxrc', 'file.xlf', 'file.xliff', 'file.xul', 'file.wsdl', 'file.wpl', 'any/etc/blkid.tab', 'any/etc/blkid.tab.old', 'any/etc/xdg/menus/file.menu', 'file.atom', 'file.rss', 'file.cdxml', 'file.psc1'], \ 'xmodmap': ['anyXmodmap'], \ 'xf86conf': ['xorg.conf', 'xorg.conf-4'], \ 'xpm2': ['file.xpm2'], diff --git a/src/nvim/testdir/test_rename.vim b/src/nvim/testdir/test_rename.vim new file mode 100644 index 0000000000..e4228188bd --- /dev/null +++ b/src/nvim/testdir/test_rename.vim @@ -0,0 +1,119 @@ +" Test rename() + +func Test_rename_file_to_file() + call writefile(['foo'], 'Xrename1') + + call assert_equal(0, rename('Xrename1', 'Xrename2')) + + call assert_equal('', glob('Xrename1')) + call assert_equal(['foo'], readfile('Xrename2')) + + " When the destination file already exists, it should be overwritten. + call writefile(['foo'], 'Xrename1') + call writefile(['bar'], 'Xrename2') + + call assert_equal(0, rename('Xrename1', 'Xrename2')) + call assert_equal('', glob('Xrename1')) + call assert_equal(['foo'], readfile('Xrename2')) + + call delete('Xrename2') +endfunc + +func Test_rename_file_ignore_case() + " With 'fileignorecase', renaming file will go through a temp file + " when the source and destination file only differ by case. + set fileignorecase + call writefile(['foo'], 'Xrename') + + call assert_equal(0, rename('Xrename', 'XRENAME')) + + call assert_equal(['foo'], readfile('XRENAME')) + + set fileignorecase& + call delete('XRENAME') +endfunc + +func Test_rename_same_file() + call writefile(['foo'], 'Xrename') + + " When the source and destination are the same file, nothing + " should be done. The source file should not be deleted. + call assert_equal(0, rename('Xrename', 'Xrename')) + call assert_equal(['foo'], readfile('Xrename')) + + call assert_equal(0, rename('./Xrename', 'Xrename')) + call assert_equal(['foo'], readfile('Xrename')) + + call delete('Xrename') +endfunc + +func Test_rename_dir_to_dir() + call mkdir('Xrenamedir1') + call writefile(['foo'], 'Xrenamedir1/Xrenamefile') + + call assert_equal(0, rename('Xrenamedir1', 'Xrenamedir2')) + + call assert_equal('', glob('Xrenamedir1')) + call assert_equal(['foo'], readfile('Xrenamedir2/Xrenamefile')) + + call delete('Xrenamedir2/Xrenamefile') + call delete('Xrenamedir2', 'd') +endfunc + +func Test_rename_same_dir() + call mkdir('Xrenamedir') + call writefile(['foo'], 'Xrenamedir/Xrenamefile') + + call assert_equal(0, rename('Xrenamedir', 'Xrenamedir')) + + call assert_equal(['foo'], readfile('Xrenamedir/Xrenamefile')) + + call delete('Xrenamedir/Xrenamefile') + call delete('Xrenamedir', 'd') +endfunc + +func Test_rename_copy() + " Check that when original file can't be deleted, rename() + " still succeeds but copies the file. + call mkdir('Xrenamedir') + call writefile(['foo'], 'Xrenamedir/Xrenamefile') + call setfperm('Xrenamedir', 'r-xr-xr-x') + + call assert_equal(0, rename('Xrenamedir/Xrenamefile', 'Xrenamefile')) + + if !has('win32') + " On Windows, the source file is removed despite + " its directory being made not writable. + call assert_equal(['foo'], readfile('Xrenamedir/Xrenamefile')) + endif + call assert_equal(['foo'], readfile('Xrenamefile')) + + call setfperm('Xrenamedir', 'rwxrwxrwx') + call delete('Xrenamedir/Xrenamefile') + call delete('Xrenamedir', 'd') + call delete('Xrenamefile') +endfunc + +func Test_rename_fails() + throw 'skipped: TODO: ' + call writefile(['foo'], 'Xrenamefile') + + " Can't rename into a non-existing directory. + call assert_notequal(0, rename('Xrenamefile', 'Xdoesnotexist/Xrenamefile')) + + " Can't rename a non-existing file. + call assert_notequal(0, rename('Xdoesnotexist', 'Xrenamefile2')) + call assert_equal('', glob('Xrenamefile2')) + + " When rename() fails, the destination file should not be deleted. + call assert_notequal(0, rename('Xdoesnotexist', 'Xrenamefile')) + call assert_equal(['foo'], readfile('Xrenamefile')) + + " Can't rename to en empty file name. + call assert_notequal(0, rename('Xrenamefile', '')) + + call assert_fails('call rename("Xrenamefile", [])', 'E730') + call assert_fails('call rename(0z, "Xrenamefile")', 'E976') + + call delete('Xrenamefile') +endfunc diff --git a/src/nvim/testdir/test_startup.vim b/src/nvim/testdir/test_startup.vim index eb9378194f..e0dc0e0075 100644 --- a/src/nvim/testdir/test_startup.vim +++ b/src/nvim/testdir/test_startup.vim @@ -814,6 +814,34 @@ func Test_v_argv() call assert_equal(['arg1', '--cmd', 'echo v:argv', '--cmd', 'q'']'], list[idx:]) endfunc +" Test for the '-t' option to jump to a tag +func Test_t_arg() + let before =<< trim [CODE] + set tags=Xtags + [CODE] + let after =<< trim [CODE] + let s = bufname('') .. ':L' .. line('.') .. 'C' .. col('.') + call writefile([s], "Xtestout") + qall + [CODE] + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "first\tXfile1\t/^ \\zsfirst$/", + \ "second\tXfile1\t/^ \\zssecond$/", + \ "third\tXfile1\t/^ \\zsthird$/"], + \ 'Xtags') + call writefile([' first', ' second', ' third'], 'Xfile1') + + for t_arg in ['-t second', '-tsecond'] + if RunVim(before, after, '-t second') + call assert_equal(['Xfile1:L2C5'], readfile('Xtestout'), t_arg) + call delete('Xtestout') + endif + endfor + + call delete('Xtags') + call delete('Xfile1') +endfunc + " Test the '-T' argument which sets the 'term' option. func Test_T_arg() throw 'skipped: Nvim does not support "-T" argument' @@ -890,6 +918,38 @@ func Test_not_a_term() endfunc +" Test for the "-w scriptout" argument +func Test_w_arg() + " Can't catch the output of gvim. + CheckNotGui + + call writefile(["iVim Editor\<Esc>:q!\<CR>"], 'Xscriptin', 'b') + if RunVim([], [], '-s Xscriptin -w Xscriptout') + call assert_equal(["iVim Editor\e:q!\r"], readfile('Xscriptout')) + call delete('Xscriptout') + endif + call delete('Xscriptin') + + " Test for failing to open the script output file. This test works only when + " the language is English. + if !has('win32') && (v:lang == "C" || v:lang =~ '^[Ee]n') + call mkdir("Xdir") + let m = system(GetVimCommand() .. " -w Xdir") + call assert_equal("Cannot open for script output: \"Xdir\"\n", m) + call delete("Xdir", 'rf') + endif + + " A number argument sets the 'window' option + call writefile(["iwindow \<C-R>=&window\<CR>\<Esc>:wq! Xresult\<CR>"], 'Xscriptin', 'b') + for w_arg in ['-w 17', '-w17'] + if RunVim([], [], '-s Xscriptin ' .. w_arg) + call assert_equal(["window 17"], readfile('Xresult'), w_arg) + call delete('Xresult') + endif + endfor + call delete('Xscriptin') +endfunc + " Test starting vim with various names: vim, ex, view, evim, etc. func Test_progname() CheckUnix diff --git a/src/nvim/testdir/test_statusline.vim b/src/nvim/testdir/test_statusline.vim index ce2ef4dcd8..48b7b4f2f1 100644 --- a/src/nvim/testdir/test_statusline.vim +++ b/src/nvim/testdir/test_statusline.vim @@ -440,6 +440,27 @@ func Test_statusline_removed_group() call delete('XTest_statusline') endfunc +func Test_statusline_using_mode() + CheckScreendump + + let lines =<< trim END + set laststatus=2 + let &statusline = '-%{mode()}-' + END + call writefile(lines, 'XTest_statusline') + + let buf = RunVimInTerminal('-S XTest_statusline', {'rows': 5, 'cols': 50}) + call VerifyScreenDump(buf, 'Test_statusline_mode_1', {}) + + call term_sendkeys(buf, ":") + call VerifyScreenDump(buf, 'Test_statusline_mode_2', {}) + + " clean up + call term_sendkeys(buf, "\<CR>") + call StopVimInTerminal(buf) + call delete('XTest_statusline') +endfunc + func Test_statusline_after_split_vsplit() only diff --git a/src/nvim/testdir/test_textformat.vim b/src/nvim/testdir/test_textformat.vim index 4af52b536c..29f0433954 100644 --- a/src/nvim/testdir/test_textformat.vim +++ b/src/nvim/testdir/test_textformat.vim @@ -891,6 +891,14 @@ func Test_mps() bwipe! endfunc +func Test_empty_matchpairs() + split + set matchpairs= showmatch + call assert_nobeep('call feedkeys("ax\tx\t\<Esc>", "xt")') + set matchpairs& noshowmatch + bwipe! +endfunc + " Test for ra on multi-byte characters func Test_ra_multibyte() new diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 62d7dc8b18..ed40a64c66 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -55,7 +55,11 @@ #define STARTS_WITH(str, prefix) (strlen(str) >= (sizeof(prefix) - 1) \ && 0 == memcmp((str), (prefix), sizeof(prefix) - 1)) #define TMUX_WRAP(is_tmux, seq) ((is_tmux) \ - ? "\x1bPtmux;\x1b" seq "\x1b\\" : seq) + ? DCS_STR "tmux;\x1b" seq STERM_STR : seq) +#define SCREEN_TMUX_WRAP(is_screen, is_tmux, seq) \ + ((is_screen) \ + ? DCS_STR seq STERM_STR : (is_tmux) \ + ? DCS_STR "tmux;\x1b" seq STERM_STR : seq) #define LINUXSET0C "\x1b[?0c" #define LINUXSET1C "\x1b[?1c" @@ -297,6 +301,12 @@ static void terminfo_start(UI *ui) data->invis, sizeof data->invis); // Set 't_Co' from the result of unibilium & fix_terminfo. t_colors = unibi_get_num(data->ut, unibi_max_colors); + // Ask the terminal to send us the background color. + // If get_bg is sent at the same time after enter_ca_mode, tmux will not send + // get_bg to the host terminal. To avoid this, send get_bg before + // enter_ca_mode. + data->input.waiting_for_bg_response = 5; + unibi_out_ext(ui, data->unibi_ext.get_bg); // Enter alternate screen, save title, and clear. // NOTE: Do this *before* changing terminal settings. #6433 unibi_out(ui, unibi_enter_ca_mode); @@ -304,9 +314,6 @@ static void terminfo_start(UI *ui) unibi_out_ext(ui, data->unibi_ext.save_title); unibi_out(ui, unibi_keypad_xmit); unibi_out(ui, unibi_clear_screen); - // Ask the terminal to send us the background color. - data->input.waiting_for_bg_response = 5; - unibi_out_ext(ui, data->unibi_ext.get_bg); // Enable bracketed paste unibi_out_ext(ui, data->unibi_ext.enable_bracketed_paste); @@ -328,6 +335,7 @@ static void terminfo_start(UI *ui) uv_pipe_init(&data->write_loop, &data->output_handle.pipe, 0); uv_pipe_open(&data->output_handle.pipe, data->out_fd); } + flush_buf(ui); } @@ -1772,8 +1780,10 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, #define XTERM_SETAB_16 \ "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e39%;m" - data->unibi_ext.get_bg = (int)unibi_add_ext_str(ut, "ext.get_bg", - "\x1b]11;?\x07"); + data->unibi_ext.get_bg = + (int)unibi_add_ext_str(ut, "ext.get_bg", + SCREEN_TMUX_WRAP((screen && !tmux), tmux, + "\x1b]11;?\x07")); // Terminals with 256-colour SGR support despite what terminfo says. if (unibi_get_num(ut, unibi_max_colors) < 256) { |