aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/eval.txt9
-rw-r--r--runtime/doc/usr_41.txt1
-rw-r--r--src/nvim/api/buffer.c6
-rw-r--r--src/nvim/api/private/helpers.c27
-rw-r--r--src/nvim/api/vim.c20
-rw-r--r--src/nvim/ascii.h2
-rw-r--r--src/nvim/autocmd.c12
-rw-r--r--src/nvim/buffer_updates.c13
-rw-r--r--src/nvim/decoration.c50
-rw-r--r--src/nvim/eval.c31
-rw-r--r--src/nvim/eval.lua3
-rw-r--r--src/nvim/eval/funcs.c31
-rw-r--r--src/nvim/eval/typval.c7
-rw-r--r--src/nvim/highlight.c4
-rw-r--r--src/nvim/lua/converter.c2
-rw-r--r--src/nvim/lua/converter.h1
-rw-r--r--src/nvim/lua/executor.c188
-rw-r--r--src/nvim/lua/executor.h2
-rw-r--r--src/nvim/memory.c6
-rw-r--r--src/nvim/screen.c4
-rw-r--r--src/nvim/search.c3
-rw-r--r--src/nvim/terminal.c3
-rw-r--r--src/nvim/testdir/test_compiler.vim4
-rw-r--r--src/nvim/testdir/test_textformat.vim8
-rw-r--r--src/nvim/tui/tui.c22
-rw-r--r--test/functional/plugin/lsp_spec.lua23
-rw-r--r--test/functional/terminal/buffer_spec.lua7
-rw-r--r--test/functional/ui/cmdline_spec.lua12
28 files changed, 311 insertions, 190 deletions
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index 2911224de5..78c7a8a69f 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -2047,6 +2047,7 @@ assert_inrange({lower}, {upper}, {actual} [, {msg}])
Number assert {actual} is inside the range
assert_match({pat}, {text} [, {msg}])
Number assert {pat} matches {text}
+assert_nobeep({cmd}) Number assert {cmd} does not cause a beep
assert_notequal({exp}, {act} [, {msg}])
Number assert {exp} is not equal {act}
assert_notmatch({pat}, {text} [, {msg}])
@@ -2638,7 +2639,8 @@ argv([{nr} [, {winid}])
assert_beeps({cmd}) *assert_beeps()*
Run {cmd} and add an error message to |v:errors| if it does
NOT produce a beep or visual bell.
- Also see |assert_fails()| and |assert-return|.
+ Also see |assert_fails()|, |assert_nobeep()| and
+ |assert-return|.
*assert_equal()*
assert_equal({expected}, {actual}, [, {msg}])
@@ -2721,6 +2723,11 @@ assert_match({pattern}, {actual} [, {msg}])
< Will result in a string to be added to |v:errors|:
test.vim line 12: Pattern '^f.*o$' does not match 'foobar' ~
+assert_nobeep({cmd}) *assert_nobeep()*
+ Run {cmd} and add an error message to |v:errors| if it
+ produces a beep or visual bell.
+ Also see |assert_beeps()|.
+
*assert_notequal()*
assert_notequal({expected}, {actual} [, {msg}])
The opposite of `assert_equal()`: add an error message to
diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt
index 97aacc1403..ccf47044e7 100644
--- a/runtime/doc/usr_41.txt
+++ b/runtime/doc/usr_41.txt
@@ -958,6 +958,7 @@ Testing: *test-functions*
assert_true() assert that an expression is true
assert_exception() assert that a command throws an exception
assert_beeps() assert that a command beeps
+ assert_nobeep() assert that a command does not cause a beep
assert_fails() assert that a command fails
Timers: *timer-functions*
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 787b6addc9..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?
@@ -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/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/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/screen.c b/src/nvim/screen.c
index 5bf5a471c1..0042d8a2a4 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -7508,6 +7508,10 @@ void screen_resize(int width, int height)
Rows = height;
Columns = width;
check_shellsize();
+ int max_p_ch = Rows - min_rows() + 1;
+ if (!ui_has(kUIMessages) && p_ch > max_p_ch) {
+ p_ch = max_p_ch ? max_p_ch : 1;
+ }
height = Rows;
width = Columns;
p_lines = 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_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_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) {
diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua
index 6d3af115fa..66b33cc9e1 100644
--- a/test/functional/plugin/lsp_spec.lua
+++ b/test/functional/plugin/lsp_spec.lua
@@ -29,6 +29,16 @@ teardown(function()
os.remove(fake_lsp_logfile)
end)
+local function clear_notrace()
+ -- problem: here be dragons
+ -- solution: don't look for dragons to closely
+ clear {env={
+ NVIM_LUA_NOTRACK="1";
+ VIMRUNTIME=os.getenv"VIMRUNTIME";
+ }}
+end
+
+
local function fake_lsp_server_setup(test_name, timeout_ms, options)
exec_lua([=[
lsp = require('vim.lsp')
@@ -36,6 +46,7 @@ local function fake_lsp_server_setup(test_name, timeout_ms, options)
TEST_RPC_CLIENT_ID = lsp.start_client {
cmd_env = {
NVIM_LOG_FILE = logfile;
+ NVIM_LUA_NOTRACK = "1";
};
cmd = {
vim.v.progpath, '-Es', '-u', 'NONE', '--headless',
@@ -65,7 +76,7 @@ end
local function test_rpc_server(config)
if config.test_name then
- clear()
+ clear_notrace()
fake_lsp_server_setup(config.test_name, config.timeout_ms or 1e3, config.options)
end
local client = setmetatable({}, {
@@ -120,7 +131,7 @@ end
describe('LSP', function()
describe('server_name specified', function()
before_each(function()
- clear()
+ clear_notrace()
-- Run an instance of nvim on the file which contains our "scripts".
-- Pass TEST_NAME to pick the script.
local test_name = "basic_init"
@@ -250,6 +261,10 @@ describe('LSP', function()
end)
it('should succeed with manual shutdown', function()
+ if 'openbsd' == helpers.uname() then
+ pending('hangs the build on openbsd #14028, re-enable with freeze timeout #14204')
+ return
+ end
local expected_callbacks = {
{NIL, "shutdown", {}, 1, NIL};
{NIL, "test", {}, 1};
@@ -314,7 +329,7 @@ describe('LSP', function()
}
end)
it('workspace/configuration returns NIL per section if client was started without config.settings', function()
- clear()
+ clear_notrace()
fake_lsp_server_setup('workspace/configuration no settings')
eq({ NIL, NIL, }, exec_lua [[
local params = {
@@ -941,7 +956,7 @@ end)
describe('LSP', function()
before_each(function()
- clear()
+ clear_notrace()
end)
local function make_edit(y_0, x_0, y_1, x_1, text)
diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua
index 209537831f..c61bf108cb 100644
--- a/test/functional/terminal/buffer_spec.lua
+++ b/test/functional/terminal/buffer_spec.lua
@@ -258,6 +258,13 @@ describe(':terminal buffer', function()
it('handles wqall', function()
eq('Vim(wqall):E948: Job still running', exc_exec('wqall'))
end)
+
+ it('does not segfault when pasting empty buffer #13955', function()
+ feed_command('terminal')
+ feed('<c-\\><c-n>')
+ feed_command('put a') -- buffer a is empty
+ helpers.assert_alive()
+ end)
end)
describe('No heap-buffer-overflow when using', function()
diff --git a/test/functional/ui/cmdline_spec.lua b/test/functional/ui/cmdline_spec.lua
index 21c01b3458..29a76c7a07 100644
--- a/test/functional/ui/cmdline_spec.lua
+++ b/test/functional/ui/cmdline_spec.lua
@@ -3,6 +3,7 @@ local Screen = require('test.functional.ui.screen')
local clear, feed = helpers.clear, helpers.feed
local source = helpers.source
local command = helpers.command
+local assert_alive = helpers.assert_alive
local function new_screen(opt)
local screen = Screen.new(25, 5)
@@ -842,3 +843,14 @@ describe('cmdline redraw', function()
]], unchanged=true}
end)
end)
+
+describe("cmdline height", function()
+ it("does not crash resized screen #14263", function()
+ clear()
+ local screen = Screen.new(25, 10)
+ screen:attach()
+ command('set cmdheight=9999')
+ screen:try_resize(25, 5)
+ assert_alive()
+ end)
+end)