diff options
Diffstat (limited to 'src/nvim/lua')
-rw-r--r-- | src/nvim/lua/converter.c | 261 | ||||
-rw-r--r-- | src/nvim/lua/executor.c | 219 | ||||
-rw-r--r-- | src/nvim/lua/executor.h | 24 | ||||
-rw-r--r-- | src/nvim/lua/secure.c | 2 | ||||
-rw-r--r-- | src/nvim/lua/spell.c | 6 | ||||
-rw-r--r-- | src/nvim/lua/stdlib.c | 33 | ||||
-rw-r--r-- | src/nvim/lua/treesitter.c | 40 | ||||
-rw-r--r-- | src/nvim/lua/xdiff.c | 173 |
8 files changed, 421 insertions, 337 deletions
diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 4598d48c4a..bba771f8a5 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -16,8 +16,8 @@ #include "nvim/eval/typval_defs.h" #include "nvim/eval/typval_encode.h" #include "nvim/eval/userfunc.h" -#include "nvim/func_attr.h" -#include "nvim/gettext.h" +#include "nvim/gettext_defs.h" +#include "nvim/highlight_group.h" #include "nvim/lua/converter.h" #include "nvim/lua/executor.h" #include "nvim/macros_defs.h" @@ -149,7 +149,7 @@ static LuaTableProps nlua_traverse_table(lua_State *const lstate) } } else { if (tsize == 0 - || (tsize == ret.maxidx + || (tsize <= ret.maxidx && other_keys_num == 0 && ret.string_keys_num == 0)) { ret.type = kObjectTypeArray; @@ -171,11 +171,12 @@ static LuaTableProps nlua_traverse_table(lua_State *const lstate) /// Helper structure for nlua_pop_typval typedef struct { - typval_T *tv; ///< Location where conversion result is saved. - bool container; ///< True if tv is a container. - bool special; ///< If true then tv is a _VAL part of special dictionary - ///< that represents mapping. - int idx; ///< Container index (used to detect self-referencing structures). + typval_T *tv; ///< Location where conversion result is saved. + size_t list_len; ///< Maximum length when tv is a list. + bool container; ///< True if tv is a container. + bool special; ///< If true then tv is a _VAL part of special dictionary + ///< that represents mapping. + int idx; ///< Container index (used to detect self-referencing structures). } TVPopStackItem; /// Convert lua object to Vimscript typval_T @@ -193,7 +194,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) const int initial_size = lua_gettop(lstate); kvec_withinit_t(TVPopStackItem, 2) stack = KV_INITIAL_VALUE; kvi_init(stack); - kvi_push(stack, ((TVPopStackItem) { ret_tv, false, false, 0 })); + kvi_push(stack, ((TVPopStackItem){ .tv = ret_tv })); while (ret && kv_size(stack)) { if (!lua_checkstack(lstate, lua_gettop(lstate) + 3)) { semsg(_("E1502: Lua failed to grow stack to %i"), lua_gettop(lstate) + 3); @@ -232,19 +233,14 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) }); kvi_push(stack, cur); tv_list_append_list(cur.tv->vval.v_list, kv_pair); - cur = (TVPopStackItem) { - .tv = TV_LIST_ITEM_TV(tv_list_last(kv_pair)), - .container = false, - .special = false, - .idx = 0, - }; + cur = (TVPopStackItem){ .tv = TV_LIST_ITEM_TV(tv_list_last(kv_pair)) }; } else { dictitem_T *const di = tv_dict_item_alloc_len(s, len); if (tv_dict_add(cur.tv->vval.v_dict, di) == FAIL) { abort(); } kvi_push(stack, cur); - cur = (TVPopStackItem) { &di->di_tv, false, false, 0 }; + cur = (TVPopStackItem){ .tv = &di->di_tv }; } } else { lua_pop(lstate, 1); @@ -252,23 +248,18 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) } } else { assert(cur.tv->v_type == VAR_LIST); - lua_rawgeti(lstate, -1, tv_list_len(cur.tv->vval.v_list) + 1); - if (lua_isnil(lstate, -1)) { - lua_pop(lstate, 2); + if ((size_t)tv_list_len(cur.tv->vval.v_list) == cur.list_len) { + lua_pop(lstate, 1); continue; } + lua_rawgeti(lstate, -1, tv_list_len(cur.tv->vval.v_list) + 1); // Not populated yet, need to create list item to push. tv_list_append_owned_tv(cur.tv->vval.v_list, (typval_T) { .v_type = VAR_UNKNOWN, }); kvi_push(stack, cur); // TODO(ZyX-I): Use indexes, here list item *will* be reallocated. - cur = (TVPopStackItem) { - .tv = TV_LIST_ITEM_TV(tv_list_last(cur.tv->vval.v_list)), - .container = false, - .special = false, - .idx = 0, - }; + cur = (TVPopStackItem){ .tv = TV_LIST_ITEM_TV(tv_list_last(cur.tv->vval.v_list)) }; } } assert(!cur.container); @@ -332,6 +323,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) cur.tv->vval.v_list = tv_list_alloc((ptrdiff_t)table_props.maxidx); cur.tv->vval.v_list->lua_table_ref = table_ref; tv_list_ref(cur.tv->vval.v_list); + cur.list_len = table_props.maxidx; if (table_props.maxidx != 0) { cur.container = true; cur.idx = lua_gettop(lstate); @@ -355,6 +347,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) cur.tv = &val_di->di_tv; cur.tv->vval.v_list->lua_table_ref = table_ref; assert(cur.tv->v_type == VAR_LIST); + cur.list_len = table_props.string_keys_num; } else { cur.tv->v_type = VAR_DICT; cur.tv->vval.v_dict = tv_dict_alloc(); @@ -372,9 +365,8 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) cur.tv->vval.v_float = (float_T)table_props.val; break; case kObjectTypeNil: - emsg(_("E5100: Cannot convert given lua table: table " - "should either have a sequence of positive integer keys " - "or contain only string keys")); + emsg(_("E5100: Cannot convert given lua table: table should " + "contain either only integer keys or only string keys")); ret = false; break; default: @@ -727,7 +719,7 @@ void nlua_push_Dictionary(lua_State *lstate, const Dictionary dict, bool special } for (size_t i = 0; i < dict.size; i++) { nlua_push_String(lstate, dict.items[i].key, special); - nlua_push_Object(lstate, dict.items[i].value, special); + nlua_push_Object(lstate, &dict.items[i].value, special); lua_rawset(lstate, -3); } } @@ -740,7 +732,7 @@ void nlua_push_Array(lua_State *lstate, const Array array, bool special) { lua_createtable(lstate, (int)array.size, 0); for (size_t i = 0; i < array.size; i++) { - nlua_push_Object(lstate, array.items[i], special); + nlua_push_Object(lstate, &array.items[i], special); lua_rawseti(lstate, -2, (int)i + 1); } } @@ -761,10 +753,10 @@ GENERATE_INDEX_FUNCTION(Tabpage) /// Convert given Object to lua value /// /// Leaves converted value on top of the stack. -void nlua_push_Object(lua_State *lstate, const Object obj, bool special) +void nlua_push_Object(lua_State *lstate, Object *obj, bool special) FUNC_ATTR_NONNULL_ALL { - switch (obj.type) { + switch (obj->type) { case kObjectTypeNil: if (special) { lua_pushnil(lstate); @@ -773,12 +765,14 @@ void nlua_push_Object(lua_State *lstate, const Object obj, bool special) } break; case kObjectTypeLuaRef: { - nlua_pushref(lstate, obj.data.luaref); + nlua_pushref(lstate, obj->data.luaref); + api_free_luaref(obj->data.luaref); + obj->data.luaref = LUA_NOREF; break; } #define ADD_TYPE(type, data_key) \ case kObjectType##type: { \ - nlua_push_##type(lstate, obj.data.data_key, special); \ + nlua_push_##type(lstate, obj->data.data_key, special); \ break; \ } ADD_TYPE(Boolean, boolean) @@ -790,7 +784,7 @@ void nlua_push_Object(lua_State *lstate, const Object obj, bool special) #undef ADD_TYPE #define ADD_REMOTE_TYPE(type) \ case kObjectType##type: { \ - nlua_push_##type(lstate, (type)obj.data.integer, special); \ + nlua_push_##type(lstate, (type)obj->data.integer, special); \ break; \ } ADD_REMOTE_TYPE(Buffer) @@ -803,8 +797,8 @@ void nlua_push_Object(lua_State *lstate, const Object obj, bool special) /// Convert lua value to string /// /// Always pops one value from the stack. -String nlua_pop_String(lua_State *lstate, Error *err) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +String nlua_pop_String(lua_State *lstate, Arena *arena, Error *err) + FUNC_ATTR_NONNULL_ARG(1, 3) FUNC_ATTR_WARN_UNUSED_RESULT { if (lua_type(lstate, -1) != LUA_TSTRING) { lua_pop(lstate, 1); @@ -815,7 +809,10 @@ String nlua_pop_String(lua_State *lstate, Error *err) ret.data = (char *)lua_tolstring(lstate, -1, &(ret.size)); assert(ret.data != NULL); - ret.data = xmemdupz(ret.data, ret.size); + // TODO(bfredl): it would be "nice" to just use the memory of the lua string + // directly, although ensuring the lifetime of such strings is a bit tricky + // (an API call could invoke nested lua, which triggers GC, and kaboom?) + ret.data = arena_memdupz(arena, ret.data, ret.size); lua_pop(lstate, 1); return ret; @@ -824,8 +821,8 @@ String nlua_pop_String(lua_State *lstate, Error *err) /// Convert lua value to integer /// /// Always pops one value from the stack. -Integer nlua_pop_Integer(lua_State *lstate, Error *err) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +Integer nlua_pop_Integer(lua_State *lstate, Arena *arena, Error *err) + FUNC_ATTR_NONNULL_ARG(1, 3) FUNC_ATTR_WARN_UNUSED_RESULT { if (lua_type(lstate, -1) != LUA_TNUMBER) { lua_pop(lstate, 1); @@ -848,8 +845,8 @@ Integer nlua_pop_Integer(lua_State *lstate, Error *err) /// thus `err` is never set as any lua value can be co-erced into a lua bool /// /// Always pops one value from the stack. -Boolean nlua_pop_Boolean(lua_State *lstate, Error *err) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +Boolean nlua_pop_Boolean(lua_State *lstate, Arena *arena, Error *err) + FUNC_ATTR_NONNULL_ARG(1, 3) FUNC_ATTR_WARN_UNUSED_RESULT { const Boolean ret = lua_toboolean(lstate, -1); lua_pop(lstate, 1); @@ -923,7 +920,7 @@ static inline LuaTableProps nlua_check_type(lua_State *const lstate, Error *cons /// Convert lua table to float /// /// Always pops one value from the stack. -Float nlua_pop_Float(lua_State *lstate, Error *err) +Float nlua_pop_Float(lua_State *lstate, Arena *arena, Error *err) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { if (lua_type(lstate, -1) == LUA_TNUMBER) { @@ -947,29 +944,29 @@ Float nlua_pop_Float(lua_State *lstate, Error *err) /// @param[in] table_props nlua_traverse_table() output. /// @param[out] err Location where error will be saved. static Array nlua_pop_Array_unchecked(lua_State *const lstate, const LuaTableProps table_props, - Error *const err) + Arena *arena, Error *const err) { - Array ret = { .size = table_props.maxidx, .items = NULL }; + Array ret = arena_array(arena, table_props.maxidx); - if (ret.size == 0) { + if (table_props.maxidx == 0) { lua_pop(lstate, 1); return ret; } - ret.items = xcalloc(ret.size, sizeof(*ret.items)); - for (size_t i = 1; i <= ret.size; i++) { + for (size_t i = 1; i <= table_props.maxidx; i++) { Object val; lua_rawgeti(lstate, -1, (int)i); - val = nlua_pop_Object(lstate, false, err); + val = nlua_pop_Object(lstate, false, arena, err); if (ERROR_SET(err)) { - ret.size = i - 1; lua_pop(lstate, 1); - api_free_array(ret); + if (!arena) { + api_free_array(ret); + } return (Array) { .size = 0, .items = NULL }; } - ret.items[i - 1] = val; + ADD_C(ret, val); } lua_pop(lstate, 1); @@ -979,15 +976,14 @@ static Array nlua_pop_Array_unchecked(lua_State *const lstate, const LuaTablePro /// Convert lua table to array /// /// Always pops one value from the stack. -Array nlua_pop_Array(lua_State *lstate, Error *err) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +Array nlua_pop_Array(lua_State *lstate, Arena *arena, Error *err) + FUNC_ATTR_NONNULL_ARG(1, 3) FUNC_ATTR_WARN_UNUSED_RESULT { - const LuaTableProps table_props = nlua_check_type(lstate, err, - kObjectTypeArray); + const LuaTableProps table_props = nlua_check_type(lstate, err, kObjectTypeArray); if (table_props.type != kObjectTypeArray) { return (Array) { .size = 0, .items = NULL }; } - return nlua_pop_Array_unchecked(lstate, table_props, err); + return nlua_pop_Array_unchecked(lstate, table_props, arena, err); } /// Convert lua table to dictionary @@ -999,30 +995,30 @@ Array nlua_pop_Array(lua_State *lstate, Error *err) /// @param[in] table_props nlua_traverse_table() output. /// @param[out] err Location where error will be saved. static Dictionary nlua_pop_Dictionary_unchecked(lua_State *lstate, const LuaTableProps table_props, - bool ref, Error *err) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT + bool ref, Arena *arena, Error *err) + FUNC_ATTR_NONNULL_ARG(1, 5) FUNC_ATTR_WARN_UNUSED_RESULT { - Dictionary ret = { .size = table_props.string_keys_num, .items = NULL }; + Dictionary ret = arena_dict(arena, table_props.string_keys_num); - if (ret.size == 0) { + if (table_props.string_keys_num == 0) { lua_pop(lstate, 1); return ret; } - ret.items = xcalloc(ret.size, sizeof(*ret.items)); lua_pushnil(lstate); - for (size_t i = 0; lua_next(lstate, -2) && i < ret.size;) { + for (size_t i = 0; lua_next(lstate, -2) && i < table_props.string_keys_num;) { // stack: dict, key, value if (lua_type(lstate, -2) == LUA_TSTRING) { lua_pushvalue(lstate, -2); // stack: dict, key, value, key - ret.items[i].key = nlua_pop_String(lstate, err); + String key = nlua_pop_String(lstate, arena, err); // stack: dict, key, value if (!ERROR_SET(err)) { - ret.items[i].value = nlua_pop_Object(lstate, ref, err); + Object value = nlua_pop_Object(lstate, ref, arena, err); + kv_push_c(ret, ((KeyValuePair) { .key = key, .value = value })); // stack: dict, key } else { lua_pop(lstate, 1); @@ -1030,8 +1026,9 @@ static Dictionary nlua_pop_Dictionary_unchecked(lua_State *lstate, const LuaTabl } if (ERROR_SET(err)) { - ret.size = i; - api_free_dictionary(ret); + if (!arena) { + api_free_dictionary(ret); + } lua_pop(lstate, 2); // stack: return (Dictionary) { .size = 0, .items = NULL }; @@ -1050,8 +1047,8 @@ static Dictionary nlua_pop_Dictionary_unchecked(lua_State *lstate, const LuaTabl /// Convert lua table to dictionary /// /// Always pops one value from the stack. -Dictionary nlua_pop_Dictionary(lua_State *lstate, bool ref, Error *err) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +Dictionary nlua_pop_Dictionary(lua_State *lstate, bool ref, Arena *arena, Error *err) + FUNC_ATTR_NONNULL_ARG(1, 4) FUNC_ATTR_WARN_UNUSED_RESULT { const LuaTableProps table_props = nlua_check_type(lstate, err, kObjectTypeDictionary); @@ -1060,7 +1057,7 @@ Dictionary nlua_pop_Dictionary(lua_State *lstate, bool ref, Error *err) return (Dictionary) { .size = 0, .items = NULL }; } - return nlua_pop_Dictionary_unchecked(lstate, table_props, ref, err); + return nlua_pop_Dictionary_unchecked(lstate, table_props, ref, arena, err); } /// Helper structure for nlua_pop_Object @@ -1072,13 +1069,14 @@ typedef struct { /// Convert lua table to object /// /// Always pops one value from the stack. -Object nlua_pop_Object(lua_State *const lstate, bool ref, Error *const err) +Object nlua_pop_Object(lua_State *const lstate, bool ref, Arena *arena, Error *const err) + FUNC_ATTR_NONNULL_ARG(1, 4) FUNC_ATTR_WARN_UNUSED_RESULT { Object ret = NIL; const int initial_size = lua_gettop(lstate); kvec_withinit_t(ObjPopStackItem, 2) stack = KV_INITIAL_VALUE; kvi_init(stack); - kvi_push(stack, ((ObjPopStackItem) { &ret, false })); + kvi_push(stack, ((ObjPopStackItem){ .obj = &ret })); while (!ERROR_SET(err) && kv_size(stack)) { ObjPopStackItem cur = kv_pop(stack); if (cur.container) { @@ -1088,8 +1086,7 @@ Object nlua_pop_Object(lua_State *const lstate, bool ref, Error *const err) } if (cur.obj->type == kObjectTypeDictionary) { // stack: …, dict, key - if (cur.obj->data.dictionary.size - == cur.obj->data.dictionary.capacity) { + if (cur.obj->data.dictionary.size == cur.obj->data.dictionary.capacity) { lua_pop(lstate, 2); continue; } @@ -1108,15 +1105,9 @@ Object nlua_pop_Object(lua_State *const lstate, bool ref, Error *const err) size_t len; const char *s = lua_tolstring(lstate, -2, &len); const size_t idx = cur.obj->data.dictionary.size++; - cur.obj->data.dictionary.items[idx].key = (String) { - .data = xmemdupz(s, len), - .size = len, - }; + cur.obj->data.dictionary.items[idx].key = CBUF_TO_ARENA_STR(arena, s, len); kvi_push(stack, cur); - cur = (ObjPopStackItem) { - .obj = &cur.obj->data.dictionary.items[idx].value, - .container = false, - }; + cur = (ObjPopStackItem){ .obj = &cur.obj->data.dictionary.items[idx].value }; } else { // stack: …, dict lua_pop(lstate, 1); @@ -1130,15 +1121,8 @@ Object nlua_pop_Object(lua_State *const lstate, bool ref, Error *const err) } const size_t idx = cur.obj->data.array.size++; lua_rawgeti(lstate, -1, (int)idx + 1); - if (lua_isnil(lstate, -1)) { - lua_pop(lstate, 2); - continue; - } kvi_push(stack, cur); - cur = (ObjPopStackItem) { - .obj = &cur.obj->data.array.items[idx], - .container = false, - }; + cur = (ObjPopStackItem){ .obj = &cur.obj->data.array.items[idx] }; } } assert(!cur.container); @@ -1152,7 +1136,7 @@ Object nlua_pop_Object(lua_State *const lstate, bool ref, Error *const err) case LUA_TSTRING: { size_t len; const char *s = lua_tolstring(lstate, -1, &len); - *cur.obj = STRING_OBJ(((String) { .data = xmemdupz(s, len), .size = len })); + *cur.obj = STRING_OBJ(CBUF_TO_ARENA_STR(arena, s, len)); break; } case LUA_TNUMBER: { @@ -1170,23 +1154,17 @@ Object nlua_pop_Object(lua_State *const lstate, bool ref, Error *const err) switch (table_props.type) { case kObjectTypeArray: - *cur.obj = ARRAY_OBJ(((Array) { .items = NULL, .size = 0, .capacity = 0 })); + *cur.obj = ARRAY_OBJ(((Array)ARRAY_DICT_INIT)); if (table_props.maxidx != 0) { - cur.obj->data.array.items = - xcalloc(table_props.maxidx, - sizeof(cur.obj->data.array.items[0])); - cur.obj->data.array.capacity = table_props.maxidx; + cur.obj->data.array = arena_array(arena, table_props.maxidx); cur.container = true; kvi_push(stack, cur); } break; case kObjectTypeDictionary: - *cur.obj = DICTIONARY_OBJ(((Dictionary) { .items = NULL, .size = 0, .capacity = 0 })); + *cur.obj = DICTIONARY_OBJ(((Dictionary)ARRAY_DICT_INIT)); if (table_props.string_keys_num != 0) { - cur.obj->data.dictionary.items = - xcalloc(table_props.string_keys_num, - sizeof(cur.obj->data.dictionary.items[0])); - cur.obj->data.dictionary.capacity = table_props.string_keys_num; + cur.obj->data.dictionary = arena_dict(arena, table_props.string_keys_num); cur.container = true; kvi_push(stack, cur); lua_pushnil(lstate); @@ -1238,7 +1216,9 @@ type_error: } kvi_destroy(stack); if (ERROR_SET(err)) { - api_free_object(ret); + if (!arena) { + api_free_object(ret); + } ret = NIL; lua_pop(lstate, lua_gettop(lstate) - initial_size + 1); } @@ -1246,20 +1226,20 @@ type_error: return ret; } -LuaRef nlua_pop_LuaRef(lua_State *const lstate, Error *err) +LuaRef nlua_pop_LuaRef(lua_State *const lstate, Arena *arena, Error *err) { LuaRef rv = nlua_ref_global(lstate, -1); lua_pop(lstate, 1); return rv; } -handle_T nlua_pop_handle(lua_State *lstate, Error *err) +handle_T nlua_pop_handle(lua_State *lstate, Arena *arena, Error *err) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { handle_T ret; if (lua_type(lstate, -1) != LUA_TNUMBER) { api_set_error(err, kErrorTypeValidation, "Expected Lua number"); - ret = (handle_T) - 1; + ret = (handle_T)(-1); } else { ret = (handle_T)lua_tonumber(lstate, -1); } @@ -1315,7 +1295,8 @@ void nlua_init_types(lua_State *const lstate) } // lua specific variant of api_dict_to_keydict -void nlua_pop_keydict(lua_State *L, void *retval, FieldHashfn hashy, char **err_opt, Error *err) +void nlua_pop_keydict(lua_State *L, void *retval, FieldHashfn hashy, char **err_opt, Arena *arena, + Error *err) { if (!lua_istable(L, -1)) { api_set_error(err, kErrorTypeValidation, "Expected Lua table"); @@ -1342,24 +1323,31 @@ void nlua_pop_keydict(lua_State *L, void *retval, FieldHashfn hashy, char **err_ char *mem = ((char *)retval + field->ptr_off); if (field->type == kObjectTypeNil) { - *(Object *)mem = nlua_pop_Object(L, true, err); + *(Object *)mem = nlua_pop_Object(L, true, arena, err); } else if (field->type == kObjectTypeInteger) { - *(Integer *)mem = nlua_pop_Integer(L, err); + if (field->is_hlgroup && lua_type(L, -1) == LUA_TSTRING) { + size_t name_len; + const char *name = lua_tolstring(L, -1, &name_len); + lua_pop(L, 1); + *(Integer *)mem = name_len > 0 ? syn_check_group(name, name_len) : 0; + } else { + *(Integer *)mem = nlua_pop_Integer(L, arena, err); + } } else if (field->type == kObjectTypeBoolean) { *(Boolean *)mem = nlua_pop_Boolean_strict(L, err); } else if (field->type == kObjectTypeString) { - *(String *)mem = nlua_pop_String(L, err); + *(String *)mem = nlua_pop_String(L, arena, err); } else if (field->type == kObjectTypeFloat) { - *(Float *)mem = nlua_pop_Float(L, err); + *(Float *)mem = nlua_pop_Float(L, arena, err); } else if (field->type == kObjectTypeBuffer || field->type == kObjectTypeWindow || field->type == kObjectTypeTabpage) { - *(handle_T *)mem = nlua_pop_handle(L, err); + *(handle_T *)mem = nlua_pop_handle(L, arena, err); } else if (field->type == kObjectTypeArray) { - *(Array *)mem = nlua_pop_Array(L, err); + *(Array *)mem = nlua_pop_Array(L, arena, err); } else if (field->type == kObjectTypeDictionary) { - *(Dictionary *)mem = nlua_pop_Dictionary(L, false, err); + *(Dictionary *)mem = nlua_pop_Dictionary(L, false, arena, err); } else if (field->type == kObjectTypeLuaRef) { - *(LuaRef *)mem = nlua_pop_LuaRef(L, err); + *(LuaRef *)mem = nlua_pop_LuaRef(L, arena, err); } else { abort(); } @@ -1372,3 +1360,48 @@ void nlua_pop_keydict(lua_State *L, void *retval, FieldHashfn hashy, char **err_ lua_pop(L, 1); // [] } + +void nlua_push_keydict(lua_State *L, void *value, KeySetLink *table) +{ + lua_createtable(L, 0, 0); + for (size_t i = 0; table[i].str; i++) { + KeySetLink *field = &table[i]; + bool is_set = true; + if (field->opt_index >= 0) { + OptKeySet *ks = (OptKeySet *)value; + is_set = ks->is_set_ & (1ULL << field->opt_index); + } + + if (!is_set) { + continue; + } + + char *mem = ((char *)value + field->ptr_off); + + lua_pushstring(L, field->str); + if (field->type == kObjectTypeNil) { + nlua_push_Object(L, (Object *)mem, false); + } else if (field->type == kObjectTypeInteger) { + lua_pushinteger(L, *(Integer *)mem); + } else if (field->type == kObjectTypeBuffer || field->type == kObjectTypeWindow + || field->type == kObjectTypeTabpage) { + lua_pushinteger(L, *(handle_T *)mem); + } else if (field->type == kObjectTypeFloat) { + lua_pushnumber(L, *(Float *)mem); + } else if (field->type == kObjectTypeBoolean) { + lua_pushboolean(L, *(Boolean *)mem); + } else if (field->type == kObjectTypeString) { + nlua_push_String(L, *(String *)mem, false); + } else if (field->type == kObjectTypeArray) { + nlua_push_Array(L, *(Array *)mem, false); + } else if (field->type == kObjectTypeDictionary) { + nlua_push_Dictionary(L, *(Dictionary *)mem, false); + } else if (field->type == kObjectTypeLuaRef) { + nlua_pushref(L, *(LuaRef *)mem); + } else { + abort(); + } + + lua_rawset(L, -3); + } +} diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 06d16efb05..1a9bd026b5 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -15,7 +15,9 @@ #include "nvim/api/extmark.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" +#include "nvim/api/ui.h" #include "nvim/ascii_defs.h" +#include "nvim/buffer_defs.h" #include "nvim/change.h" #include "nvim/cmdexpand_defs.h" #include "nvim/cursor.h" @@ -33,8 +35,9 @@ #include "nvim/ex_cmds_defs.h" #include "nvim/ex_getln.h" #include "nvim/garray.h" +#include "nvim/garray_defs.h" #include "nvim/getchar.h" -#include "nvim/gettext.h" +#include "nvim/gettext_defs.h" #include "nvim/globals.h" #include "nvim/keycodes.h" #include "nvim/lua/converter.h" @@ -45,22 +48,30 @@ #include "nvim/main.h" #include "nvim/memline.h" #include "nvim/memory.h" +#include "nvim/memory_defs.h" #include "nvim/message.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/option_vars.h" #include "nvim/os/fileio.h" +#include "nvim/os/fileio_defs.h" #include "nvim/os/os.h" #include "nvim/path.h" #include "nvim/pos_defs.h" #include "nvim/profile.h" #include "nvim/runtime.h" +#include "nvim/runtime_defs.h" #include "nvim/strings.h" #include "nvim/ui.h" +#include "nvim/ui_defs.h" #include "nvim/undo.h" #include "nvim/usercmd.h" #include "nvim/vim_defs.h" #include "nvim/window.h" +#ifndef MSWIN +# include <pthread.h> +#endif + static int in_fast_callback = 0; static bool in_script = false; @@ -115,7 +126,7 @@ lua_State *get_global_lstate(void) /// Convert lua error into a Vim error message /// /// @param lstate Lua interpreter state. -/// @param[in] msg Message base, must contain one `%*s`. +/// @param[in] msg Message base, must contain one `%.*s`. void nlua_error(lua_State *const lstate, const char *const msg) FUNC_ATTR_NONNULL_ALL { @@ -208,7 +219,7 @@ static int nlua_luv_cfpcall(lua_State *lstate, int nargs, int nresult, int flags const char *error = lua_tostring(lstate, -1); multiqueue_put(main_loop.events, nlua_luv_error_event, - 2, xstrdup(error), (intptr_t)kCallback); + xstrdup(error), (void *)(intptr_t)kCallback); lua_pop(lstate, 1); // error message retval = -status; } else { // LUA_OK @@ -254,8 +265,7 @@ static int nlua_luv_thread_common_cfpcall(lua_State *lstate, int nargs, int nres if (status == LUA_ERRMEM && !(flags & LUVF_CALLBACK_NOEXIT)) { // Terminate this thread, as the main thread may be able to continue // execution. - os_errmsg(e_outofmem); - os_errmsg("\n"); + fprintf(stderr, "%s\n", e_outofmem); lua_close(lstate); #ifdef MSWIN ExitThread(0); @@ -266,11 +276,11 @@ static int nlua_luv_thread_common_cfpcall(lua_State *lstate, int nargs, int nres const char *error = lua_tostring(lstate, -1); loop_schedule_deferred(&main_loop, - event_create(nlua_luv_error_event, 2, + event_create(nlua_luv_error_event, xstrdup(error), - is_callback - ? (intptr_t)kThreadCallback - : (intptr_t)kThread)); + (void *)(intptr_t)(is_callback + ? kThreadCallback + : kThread))); lua_pop(lstate, 1); // error message retval = -status; } else { // LUA_OK @@ -302,7 +312,10 @@ static int nlua_thr_api_nvim__get_runtime(lua_State *lstate) lua_pop(lstate, 1); Error err = ERROR_INIT; - const Array pat = nlua_pop_Array(lstate, &err); + // TODO(bfredl): we could use an arena here for both "pat" and "ret", but then + // we need a path to not use the freelist but a private block local to the thread. + // We do not want mutex contentionery for the main arena freelist. + const Array pat = nlua_pop_Array(lstate, NULL, &err); if (ERROR_SET(&err)) { luaL_where(lstate, 1); lua_pushstring(lstate, err.msg); @@ -379,8 +392,7 @@ static int nlua_schedule(lua_State *const lstate) LuaRef cb = nlua_ref_global(lstate, 1); - multiqueue_put(main_loop.events, nlua_schedule_event, - 1, (void *)(ptrdiff_t)cb); + multiqueue_put(main_loop.events, nlua_schedule_event, (void *)(ptrdiff_t)cb); return 0; } @@ -643,8 +655,7 @@ static bool nlua_init_packages(lua_State *lstate, bool is_standalone) lua_getglobal(lstate, "require"); lua_pushstring(lstate, "vim._init_packages"); if (nlua_pcall(lstate, 1, 0)) { - os_errmsg(lua_tostring(lstate, -1)); - os_errmsg("\n"); + fprintf(stderr, "%s\n", lua_tostring(lstate, -1)); return false; } @@ -818,12 +829,12 @@ void nlua_init(char **argv, int argc, int lua_arg0) lua_State *lstate = luaL_newstate(); if (lstate == NULL) { - os_errmsg(_("E970: Failed to initialize lua interpreter\n")); + fprintf(stderr, _("E970: Failed to initialize lua interpreter\n")); os_exit(1); } luaL_openlibs(lstate); if (!nlua_state_init(lstate)) { - os_errmsg(_("E970: Failed to initialize builtin lua modules\n")); + fprintf(stderr, _("E970: Failed to initialize builtin lua modules\n")); #ifdef EXITFREE nlua_common_free_all_mem(lstate); #endif @@ -1022,15 +1033,14 @@ static int nlua_print(lua_State *const lstate) if (is_thread) { loop_schedule_deferred(&main_loop, - event_create(nlua_print_event, 2, + event_create(nlua_print_event, msg_ga.ga_data, - (intptr_t)msg_ga.ga_len)); + (void *)(intptr_t)msg_ga.ga_len)); } else if (in_fast_callback) { multiqueue_put(main_loop.events, nlua_print_event, - 2, msg_ga.ga_data, (intptr_t)msg_ga.ga_len); + msg_ga.ga_data, (void *)(intptr_t)msg_ga.ga_len); } else { - nlua_print_event((void *[]){ msg_ga.ga_data, - (void *)(intptr_t)msg_ga.ga_len }); + nlua_print_event((void *[]){ msg_ga.ga_data, (void *)(intptr_t)msg_ga.ga_len }); } return 0; @@ -1236,13 +1246,13 @@ static int nlua_rpc(lua_State *lstate, bool request) const char *name = luaL_checklstring(lstate, 2, &name_len); int nargs = lua_gettop(lstate) - 2; Error err = ERROR_INIT; - Array args = ARRAY_DICT_INIT; + Arena arena = ARENA_EMPTY; + Array args = arena_array(&arena, (size_t)nargs); for (int i = 0; i < nargs; i++) { lua_pushvalue(lstate, i + 3); - ADD(args, nlua_pop_Object(lstate, false, &err)); + ADD(args, nlua_pop_Object(lstate, false, &arena, &err)); if (ERROR_SET(&err)) { - api_free_array(args); goto check_err; } } @@ -1251,7 +1261,7 @@ static int nlua_rpc(lua_State *lstate, bool request) ArenaMem res_mem = NULL; Object result = rpc_send_call(chan_id, name, args, &res_mem, &err); if (!ERROR_SET(&err)) { - nlua_push_Object(lstate, result, false); + nlua_push_Object(lstate, &result, false); arena_mem_free(res_mem); } } else { @@ -1259,10 +1269,11 @@ static int nlua_rpc(lua_State *lstate, bool request) api_set_error(&err, kErrorTypeValidation, "Invalid channel: %" PRIu64, chan_id); } - api_free_array(args); // TODO(bfredl): no } check_err: + arena_mem_free(arena_finish(&arena)); + if (ERROR_SET(&err)) { lua_pushstring(lstate, err.msg); api_clear_error(&err); @@ -1535,10 +1546,12 @@ int typval_exec_lua_callable(LuaRef lua_cb, int argcount, typval_T *argvars, typ /// /// @param[in] str String to execute. /// @param[in] args array of ... args +/// @param[in] mode Whether and how the the return value should be converted to Object +/// @param[in] arena can be NULL, then nested allocations are used /// @param[out] err Location where error will be saved. /// /// @return Return value of the execution. -Object nlua_exec(const String str, const Array args, Error *err) +Object nlua_exec(const String str, const Array args, LuaRetMode mode, Arena *arena, Error *err) { lua_State *const lstate = global_lstate; @@ -1551,7 +1564,7 @@ Object nlua_exec(const String str, const Array args, Error *err) } for (size_t i = 0; i < args.size; i++) { - nlua_push_Object(lstate, args.items[i], false); + nlua_push_Object(lstate, &args.items[i], false); } if (nlua_pcall(lstate, (int)args.size, 1)) { @@ -1562,7 +1575,7 @@ Object nlua_exec(const String str, const Array args, Error *err) return NIL; } - return nlua_pop_Object(lstate, false, err); + return nlua_call_pop_retval(lstate, mode, arena, err); } bool nlua_ref_is_function(LuaRef ref) @@ -1583,12 +1596,12 @@ bool nlua_ref_is_function(LuaRef ref) /// @param ref the reference to call (not consumed) /// @param name if non-NULL, sent to callback as first arg /// if NULL, only args are used -/// @param retval if true, convert return value to Object -/// if false, only check if return value is truthy +/// @param mode Whether and how the the return value should be converted to Object +/// @param arena can be NULL, then nested allocations are used /// @param err Error details, if any (if NULL, errors are echoed) -/// @return Return value of function, if retval was set. Otherwise -/// BOOLEAN_OBJ(true) or NIL. -Object nlua_call_ref(LuaRef ref, const char *name, Array args, bool retval, Error *err) +/// @return Return value of function, as per mode +Object nlua_call_ref(LuaRef ref, const char *name, Array args, LuaRetMode mode, Arena *arena, + Error *err) { lua_State *const lstate = global_lstate; nlua_pushref(lstate, ref); @@ -1598,7 +1611,7 @@ Object nlua_call_ref(LuaRef ref, const char *name, Array args, bool retval, Erro nargs++; } for (size_t i = 0; i < args.size; i++) { - nlua_push_Object(lstate, args.items[i], false); + nlua_push_Object(lstate, &args.items[i], false); } if (nlua_pcall(lstate, nargs, 1)) { @@ -1614,18 +1627,34 @@ Object nlua_call_ref(LuaRef ref, const char *name, Array args, bool retval, Erro return NIL; } - if (retval) { - Error dummy = ERROR_INIT; - if (err == NULL) { - err = &dummy; - } - return nlua_pop_Object(lstate, false, err); - } else { - bool value = lua_toboolean(lstate, -1); + return nlua_call_pop_retval(lstate, mode, arena, err); +} + +static Object nlua_call_pop_retval(lua_State *lstate, LuaRetMode mode, Arena *arena, Error *err) +{ + if (lua_isnil(lstate, -1)) { lua_pop(lstate, 1); + return NIL; + } + Error dummy = ERROR_INIT; - return value ? BOOLEAN_OBJ(true) : NIL; + switch (mode) { + case kRetNilBool: { + bool bool_value = lua_toboolean(lstate, -1); + lua_pop(lstate, 1); + + return BOOLEAN_OBJ(bool_value); + } + case kRetLuaref: { + LuaRef ref = nlua_ref_global(lstate, -1); + lua_pop(lstate, 1); + + return LUAREF_OBJ(ref); } + case kRetObject: + return nlua_pop_Object(lstate, false, arena, err ? err : &dummy); + } + UNREACHABLE; } /// check if the current execution context is safe for calling deferred API @@ -1635,27 +1664,38 @@ bool nlua_is_deferred_safe(void) return in_fast_callback == 0; } -/// Run lua string +/// Executes Lua code. /// -/// Used for :lua. +/// Implements `:lua` and `:lua ={expr}`. /// -/// @param eap Vimscript command being run. +/// @param eap Vimscript `:lua {code}`, `:{range}lua`, or `:lua ={expr}` command. void ex_lua(exarg_T *const eap) FUNC_ATTR_NONNULL_ALL { + // ":{range}lua", only if no {code} + if (*eap->arg == NUL) { + if (eap->addr_count > 0) { + cmd_source_buffer(eap, true); + } else { + emsg(_(e_argreq)); + } + return; + } + size_t len; char *code = script_get(eap, &len); if (eap->skip || code == NULL) { xfree(code); return; } - // When =expr is used transform it to vim.print(expr) + + // ":lua {code}", ":={expr}" or ":lua ={expr}" + // + // When "=expr" is used transform it to "vim.print(expr)". if (eap->cmdidx == CMD_equal || code[0] == '=') { size_t off = (eap->cmdidx == CMD_equal) ? 0 : 1; len += sizeof("vim.print()") - 1 - off; - // code_buf needs to be 1 char larger then len for null byte in the end. - // lua nlua_typval_exec doesn't expect null terminated string so len - // needs to end before null byte. + // `nlua_typval_exec` doesn't expect NUL-terminated string so `len` must end before NUL byte. char *code_buf = xmallocz(len); vim_snprintf(code_buf, len + 1, "vim.print(%s)", code + off); xfree(code); @@ -1667,11 +1707,11 @@ void ex_lua(exarg_T *const eap) xfree(code); } -/// Run lua string for each line in range +/// Executes Lua code for-each line in a buffer range. /// -/// Used for :luado. +/// Implements `:luado`. /// -/// @param eap Vimscript command being run. +/// @param eap Vimscript `:luado {code}` command. void ex_luado(exarg_T *const eap) FUNC_ATTR_NONNULL_ALL { @@ -1715,10 +1755,15 @@ void ex_luado(exarg_T *const eap) nlua_error(lstate, _("E5110: Error executing lua: %.*s")); return; } + + buf_T *const was_curbuf = curbuf; + for (linenr_T l = eap->line1; l <= eap->line2; l++) { + // Check the line number, the command may have deleted lines. if (l > curbuf->b_ml.ml_line_count) { break; } + lua_pushvalue(lstate, -1); const char *const old_line = ml_get_buf(curbuf, l); // Get length of old_line here as calling Lua code may free it. @@ -1729,6 +1774,13 @@ void ex_luado(exarg_T *const eap) nlua_error(lstate, _("E5111: Error calling lua: %.*s")); break; } + + // Catch the command switching to another buffer. + // Check the line number, the command may have deleted lines. + if (curbuf != was_curbuf || l > curbuf->b_ml.ml_line_count) { + break; + } + if (lua_isstring(lstate, -1)) { size_t new_line_len; const char *const new_line = lua_tolstring(lstate, -1, &new_line_len); @@ -1743,16 +1795,17 @@ void ex_luado(exarg_T *const eap) } lua_pop(lstate, 1); } + lua_pop(lstate, 1); check_cursor(); redraw_curbuf_later(UPD_NOT_VALID); } -/// Run lua file +/// Executes Lua code from a file location. /// -/// Used for :luafile. +/// Implements `:luafile`. /// -/// @param eap Vimscript command being run. +/// @param eap Vimscript `:luafile {file}` command. void ex_luafile(exarg_T *const eap) FUNC_ATTR_NONNULL_ALL { @@ -1776,7 +1829,11 @@ bool nlua_exec_file(const char *path) lua_getglobal(lstate, "loadfile"); lua_pushstring(lstate, path); } else { - FileDescriptor *stdin_dup = file_open_stdin(); + FileDescriptor stdin_dup; + int error = file_open_stdin(&stdin_dup); + if (error) { + return false; + } StringBuilder sb = KV_INITIAL_VALUE; kv_resize(sb, 64); @@ -1785,7 +1842,7 @@ bool nlua_exec_file(const char *path) if (got_int) { // User canceled. return false; } - ptrdiff_t read_size = file_read(stdin_dup, IObuff, 64); + ptrdiff_t read_size = file_read(&stdin_dup, IObuff, 64); if (read_size < 0) { // Error. return false; } @@ -1797,7 +1854,7 @@ bool nlua_exec_file(const char *path) } } kv_push(sb, NUL); - file_free(stdin_dup, false); + file_close(&stdin_dup, false); lua_getglobal(lstate, "loadstring"); lua_pushstring(lstate, sb.items); @@ -1900,13 +1957,14 @@ int nlua_expand_pat(expand_T *xp, char *pat, int *num_results, char ***results) *num_results = 0; *results = NULL; - int prefix_len = (int)nlua_pop_Integer(lstate, &err); + Arena arena = ARENA_EMPTY; + int prefix_len = (int)nlua_pop_Integer(lstate, &arena, &err); if (ERROR_SET(&err)) { ret = FAIL; goto cleanup; } - Array completions = nlua_pop_Array(lstate, &err); + Array completions = nlua_pop_Array(lstate, &arena, &err); if (ERROR_SET(&err)) { ret = FAIL; goto cleanup_array; @@ -1930,7 +1988,7 @@ int nlua_expand_pat(expand_T *xp, char *pat, int *num_results, char ***results) *num_results = result_array.ga_len; cleanup_array: - api_free_array(completions); + arena_mem_free(arena_finish(&arena)); cleanup: @@ -2178,7 +2236,7 @@ int nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap, bool preview) // every possible modifier (with room to spare). If the list of possible // modifiers grows this may need to be updated. char buf[200] = { 0 }; - (void)uc_mods(buf, &cmdmod, false); + uc_mods(buf, &cmdmod, false); lua_pushstring(lstate, buf); lua_setfield(lstate, -2, "mods"); @@ -2274,11 +2332,9 @@ int nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap, bool preview) /// String representation of a Lua function reference /// /// @return Allocated string -char *nlua_funcref_str(LuaRef ref) +char *nlua_funcref_str(LuaRef ref, Arena *arena) { lua_State *const lstate = global_lstate; - StringBuilder str = KV_INITIAL_VALUE; - kv_resize(str, 16); if (!lua_checkstack(lstate, 1)) { goto plain; @@ -2292,14 +2348,13 @@ char *nlua_funcref_str(LuaRef ref) lua_Debug ar; if (lua_getinfo(lstate, ">S", &ar) && *ar.source == '@' && ar.linedefined >= 0) { char *src = home_replace_save(NULL, ar.source + 1); - kv_printf(str, "<Lua %d: %s:%d>", ref, src, ar.linedefined); + String str = arena_printf(arena, "<Lua %d: %s:%d>", ref, src, ar.linedefined); xfree(src); - return str.items; + return str.data; } -plain: - kv_printf(str, "<Lua %d>", ref); - return str.items; +plain: {} + return arena_printf(arena, "<Lua %d>", ref).data; } /// Execute the vim._defaults module to set up default mappings and autocommands @@ -2311,7 +2366,23 @@ void nlua_init_defaults(void) lua_getglobal(L, "require"); lua_pushstring(L, "vim._defaults"); if (nlua_pcall(L, 1, 0)) { - os_errmsg(lua_tostring(L, -1)); - os_errmsg("\n"); + fprintf(stderr, "%s\n", lua_tostring(L, -1)); } } + +/// check lua function exist +bool nlua_func_exists(const char *lua_funcname) +{ + MAXSIZE_TEMP_ARRAY(args, 1); + size_t length = strlen(lua_funcname) + 8; + char *str = xmalloc(length); + vim_snprintf(str, length, "return %s", lua_funcname); + ADD_C(args, CSTR_AS_OBJ(str)); + Error err = ERROR_INIT; + Object result = NLUA_EXEC_STATIC("return type(loadstring(...)()) == 'function'", args, + kRetNilBool, NULL, &err); + xfree(str); + + api_clear_error(&err); + return LUARET_TRUTHY(result); +} diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h index b38faddbb3..ebcd62122f 100644 --- a/src/nvim/lua/executor.h +++ b/src/nvim/lua/executor.h @@ -4,18 +4,13 @@ #include <lua.h> #include <stdbool.h> -#include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" -#include "nvim/assert_defs.h" -#include "nvim/cmdexpand_defs.h" -#include "nvim/eval/typval_defs.h" -#include "nvim/ex_cmds_defs.h" +#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep +#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep #include "nvim/func_attr.h" -#include "nvim/lua/converter.h" #include "nvim/macros_defs.h" -#include "nvim/map_defs.h" #include "nvim/types_defs.h" -#include "nvim/usercmd.h" +#include "nvim/usercmd.h" // IWYU pragma: keep // Generated by msgpack-gen.lua void nlua_add_api_functions(lua_State *lstate) REAL_FATTR_NONNULL_ALL; @@ -29,7 +24,8 @@ typedef struct { #endif } nlua_ref_state_t; -#define NLUA_EXEC_STATIC(cstr, arg, err) nlua_exec(STATIC_CSTR_AS_STRING(cstr), arg, err) +#define NLUA_EXEC_STATIC(cstr, arg, mode, arena, err) \ + nlua_exec(STATIC_CSTR_AS_STRING(cstr), arg, mode, arena, err) #define NLUA_CLEAR_REF(x) \ do { \ @@ -40,6 +36,16 @@ typedef struct { } \ } while (0) +typedef enum { + kRetObject, ///< any object, but doesn't preserve nested luarefs + kRetNilBool, ///< NIL preserved as such, other values return their booleanness + ///< Should also be used when return value is ignored, as it is allocation-free + kRetLuaref, ///< return value becomes a single Luaref, regardless of type (except NIL) +} LuaRetMode; + +/// To use with kRetNilBool for quick thuthyness check +#define LUARET_TRUTHY(res) ((res).type == kObjectTypeBoolean && (res).data.boolean == true) + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "lua/executor.h.generated.h" #endif diff --git a/src/nvim/lua/secure.c b/src/nvim/lua/secure.c index 65c13f8872..f62e0ace04 100644 --- a/src/nvim/lua/secure.c +++ b/src/nvim/lua/secure.c @@ -4,7 +4,7 @@ #include "nvim/charset.h" #include "nvim/ex_cmds_defs.h" -#include "nvim/gettext.h" +#include "nvim/gettext_defs.h" #include "nvim/globals.h" #include "nvim/lua/executor.h" #include "nvim/lua/secure.h" diff --git a/src/nvim/lua/spell.c b/src/nvim/lua/spell.c index c261c5105e..ba83239dc5 100644 --- a/src/nvim/lua/spell.c +++ b/src/nvim/lua/spell.c @@ -7,7 +7,7 @@ #include "nvim/ascii_defs.h" #include "nvim/buffer_defs.h" -#include "nvim/gettext.h" +#include "nvim/gettext_defs.h" #include "nvim/globals.h" #include "nvim/highlight_defs.h" #include "nvim/lua/spell.h" @@ -15,10 +15,11 @@ #include "nvim/spell.h" #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "lua/spell.c.generated.h" // IWYU pragma: export +# include "lua/spell.c.generated.h" #endif int nlua_spell_check(lua_State *lstate) + FUNC_ATTR_NONNULL_ALL { if (lua_gettop(lstate) < 1) { return luaL_error(lstate, "Expected 1 argument"); @@ -99,6 +100,7 @@ static const luaL_Reg spell_functions[] = { }; int luaopen_spell(lua_State *L) + FUNC_ATTR_NONNULL_ALL { lua_newtable(L); luaL_register(L, NULL, spell_functions); diff --git a/src/nvim/lua/stdlib.c b/src/nvim/lua/stdlib.c index 33770b2e62..8f58fd1a1a 100644 --- a/src/nvim/lua/stdlib.c +++ b/src/nvim/lua/stdlib.c @@ -6,7 +6,7 @@ #include <stddef.h> #include <stdint.h> #include <string.h> -#include <sys/types.h> +#include <uv.h> #ifdef NVIM_VENDOR_BIT # include "bit.h" @@ -19,10 +19,10 @@ #include "nvim/ascii_defs.h" #include "nvim/buffer_defs.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/vars.h" #include "nvim/ex_eval.h" #include "nvim/fold.h" -#include "nvim/func_attr.h" #include "nvim/globals.h" #include "nvim/lua/base64.h" #include "nvim/lua/converter.h" @@ -31,10 +31,12 @@ #include "nvim/lua/xdiff.h" #include "nvim/map_defs.h" #include "nvim/mbyte.h" +#include "nvim/mbyte_defs.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/pos_defs.h" #include "nvim/regexp.h" +#include "nvim/regexp_defs.h" #include "nvim/runtime.h" #include "nvim/strings.h" #include "nvim/types_defs.h" @@ -83,7 +85,8 @@ static int regex_match_line(lua_State *lstate) handle_T bufnr = (handle_T)luaL_checkinteger(lstate, 2); linenr_T rownr = (linenr_T)luaL_checkinteger(lstate, 3); - int start = 0, end = -1; + int start = 0; + int end = -1; if (narg >= 4) { start = (int)luaL_checkinteger(lstate, 4); } @@ -178,7 +181,8 @@ int nlua_str_utfindex(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL } } - size_t codepoints = 0, codeunits = 0; + size_t codepoints = 0; + size_t codeunits = 0; mb_utflen(s1, (size_t)idx, &codepoints, &codeunits); lua_pushinteger(lstate, (lua_Integer)codepoints); @@ -222,11 +226,12 @@ static int nlua_str_utf_start(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL size_t s1_len; const char *s1 = luaL_checklstring(lstate, 1, &s1_len); ptrdiff_t offset = luaL_checkinteger(lstate, 2); - if (offset < 0 || offset > (intptr_t)s1_len) { + if (offset <= 0 || offset > (intptr_t)s1_len) { return luaL_error(lstate, "index out of range"); } - int head_offset = -utf_cp_head_off(s1, s1 + offset - 1); - lua_pushinteger(lstate, head_offset); + size_t const off = (size_t)(offset - 1); + int head_off = -utf_cp_bounds_len(s1, s1 + off, (int)(s1_len - off)).begin_off; + lua_pushinteger(lstate, head_off); return 1; } @@ -242,11 +247,12 @@ static int nlua_str_utf_end(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL size_t s1_len; const char *s1 = luaL_checklstring(lstate, 1, &s1_len); ptrdiff_t offset = luaL_checkinteger(lstate, 2); - if (offset < 0 || offset > (intptr_t)s1_len) { + if (offset <= 0 || offset > (intptr_t)s1_len) { return luaL_error(lstate, "index out of range"); } - int tail_offset = utf_cp_tail_off(s1, s1 + offset - 1); - lua_pushinteger(lstate, tail_offset); + size_t const off = (size_t)(offset - 1); + int tail_off = utf_cp_bounds_len(s1, s1 + off, (int)(s1_len - off)).end_off - 1; + lua_pushinteger(lstate, tail_off); return 1; } @@ -537,11 +543,14 @@ static int nlua_iconv(lua_State *lstate) return 1; } -// Like 'zx' but don't call newFoldLevel() +// Update foldlevels (e.g., by evaluating 'foldexpr') for all lines in the current window without +// invoking other side effects. Unlike `zx`, it does not close manually opened folds and does not +// open folds under the cursor. static int nlua_foldupdate(lua_State *lstate) { curwin->w_foldinvalid = true; // recompute folds - foldOpenCursor(); + foldUpdate(curwin, 1, (linenr_T)MAXLNUM); + curwin->w_foldinvalid = false; return 0; } diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 008b3f2e95..25a753b179 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -1,5 +1,5 @@ -// lua bindings for tree-sitter. -// NB: this file mostly contains a generic lua interface for tree-sitter +// lua bindings for treesitter. +// NB: this file mostly contains a generic lua interface for treesitter // trees and nodes, and could be broken out as a reusable lua package #include <assert.h> @@ -56,6 +56,7 @@ typedef struct { # include "lua/treesitter.c.generated.h" #endif +// TSParser static struct luaL_Reg parser_meta[] = { { "__gc", parser_gc }, { "__tostring", parser_tostring }, @@ -70,6 +71,7 @@ static struct luaL_Reg parser_meta[] = { { NULL, NULL } }; +// TSTree static struct luaL_Reg tree_meta[] = { { "__gc", tree_gc }, { "__tostring", tree_tostring }, @@ -80,6 +82,7 @@ static struct luaL_Reg tree_meta[] = { { NULL, NULL } }; +// TSNode static struct luaL_Reg node_meta[] = { { "__tostring", node_tostring }, { "__eq", node_eq }, @@ -119,6 +122,7 @@ static struct luaL_Reg node_meta[] = { { NULL, NULL } }; +// TSQuery static struct luaL_Reg query_meta[] = { { "__gc", query_gc }, { "__tostring", query_tostring }, @@ -1360,9 +1364,16 @@ static int node_equal(lua_State *L) /// assumes the match table being on top of the stack static void set_match(lua_State *L, TSQueryMatch *match, int nodeidx) { - for (int i = 0; i < match->capture_count; i++) { - push_node(L, match->captures[i].node, nodeidx); - lua_rawseti(L, -2, (int)match->captures[i].index + 1); + // [match] + for (size_t i = 0; i < match->capture_count; i++) { + lua_rawgeti(L, -1, (int)match->captures[i].index + 1); // [match, captures] + if (lua_isnil(L, -1)) { // [match, nil] + lua_pop(L, 1); // [match] + lua_createtable(L, 1, 0); // [match, captures] + } + push_node(L, match->captures[i].node, nodeidx); // [match, captures, node] + lua_rawseti(L, -2, (int)lua_objlen(L, -2) + 1); // [match, captures] + lua_rawseti(L, -2, (int)match->captures[i].index + 1); // [match] } } @@ -1375,7 +1386,7 @@ static int query_next_match(lua_State *L) TSQueryMatch match; if (ts_query_cursor_next_match(cursor, &match)) { lua_pushinteger(L, match.pattern_index + 1); // [index] - lua_createtable(L, (int)ts_query_capture_count(query), 2); // [index, match] + lua_createtable(L, (int)ts_query_capture_count(query), 0); // [index, match] set_match(L, &match, lua_upvalueindex(2)); return 2; } @@ -1417,7 +1428,8 @@ static int query_next_capture(lua_State *L) if (n_pred > 0 && (ud->max_match_id < (int)match.id)) { ud->max_match_id = (int)match.id; - lua_pushvalue(L, lua_upvalueindex(4)); // [index, node, match] + // Create a new cleared match table + lua_createtable(L, (int)ts_query_capture_count(query), 2); // [index, node, match] set_match(L, &match, lua_upvalueindex(2)); lua_pushinteger(L, match.pattern_index + 1); lua_setfield(L, -2, "pattern"); @@ -1427,6 +1439,10 @@ static int query_next_capture(lua_State *L) lua_pushboolean(L, false); lua_setfield(L, -2, "active"); } + + // Set current_match to the new match + lua_replace(L, lua_upvalueindex(4)); // [index, node] + lua_pushvalue(L, lua_upvalueindex(4)); // [index, node, match] return 3; } return 2; @@ -1449,10 +1465,7 @@ static int node_rawquery(lua_State *L) cursor = ts_query_cursor_new(); } -#ifdef NVIM_TS_HAS_SET_MAX_START_DEPTH - // reset the start depth ts_query_cursor_set_max_start_depth(cursor, UINT32_MAX); -#endif ts_query_cursor_set_match_limit(cursor, 256); ts_query_cursor_exec(cursor, query, node); @@ -1475,11 +1488,8 @@ static int node_rawquery(lua_State *L) if (lua_type(L, -2) == LUA_TSTRING) { char *k = (char *)lua_tostring(L, -2); if (strequal("max_start_depth", k)) { - // TODO(lewis6991): remove ifdef when min TS version is 0.20.9 -#ifdef NVIM_TS_HAS_SET_MAX_START_DEPTH uint32_t max_start_depth = (uint32_t)lua_tointeger(L, -1); ts_query_cursor_set_max_start_depth(cursor, max_start_depth); -#endif } } lua_pop(L, 1); // pop the value; lua_next will pop the key. @@ -1655,8 +1665,10 @@ static int query_inspect(lua_State *L) return 0; } - uint32_t n_pat = ts_query_pattern_count(query); + // TSQueryInfo lua_createtable(L, 0, 2); // [retval] + + uint32_t n_pat = ts_query_pattern_count(query); lua_createtable(L, (int)n_pat, 1); // [retval, patterns] for (size_t i = 0; i < n_pat; i++) { uint32_t len; diff --git a/src/nvim/lua/xdiff.c b/src/nvim/lua/xdiff.c index 16c3aa5e11..035c171a14 100644 --- a/src/nvim/lua/xdiff.c +++ b/src/nvim/lua/xdiff.c @@ -5,7 +5,9 @@ #include <string.h> #include "luaconf.h" +#include "nvim/api/keysets_defs.h" #include "nvim/api/private/defs.h" +#include "nvim/api/private/dispatch.h" #include "nvim/api/private/helpers.h" #include "nvim/linematch.h" #include "nvim/lua/converter.h" @@ -74,7 +76,8 @@ static void get_linematch_results(lua_State *lstate, mmfile_t *ma, mmfile_t *mb, int *decisions = NULL; size_t decisions_length = linematch_nbuffers(diff_begin, diff_length, 2, &decisions, iwhite); - int lnuma = start_a, lnumb = start_b; + int lnuma = start_a; + int lnumb = start_b; int hunkstarta = lnuma; int hunkstartb = lnumb; @@ -186,136 +189,84 @@ static mmfile_t get_string_arg(lua_State *lstate, int idx) return mf; } -// Helper function for validating option types -static bool check_xdiff_opt(ObjectType actType, ObjectType expType, const char *name, Error *err) -{ - if (actType != expType) { - const char *type_str = - expType == kObjectTypeString - ? "string" : (expType == kObjectTypeInteger - ? "integer" : (expType == kObjectTypeBoolean - ? "boolean" : (expType == kObjectTypeLuaRef - ? "function" : "NA"))); - - api_set_error(err, kErrorTypeValidation, "%s is not a %s", name, - type_str); - return true; - } - - return false; -} - static NluaXdiffMode process_xdl_diff_opts(lua_State *lstate, xdemitconf_t *cfg, xpparam_t *params, int64_t *linematch, Error *err) { - const DictionaryOf(LuaRef) opts = nlua_pop_Dictionary(lstate, true, err); + Dict(xdl_diff) opts = KEYDICT_INIT; + char *err_param = NULL; + KeySetLink *KeyDict_xdl_diff_get_field(const char *str, size_t len); + nlua_pop_keydict(lstate, &opts, KeyDict_xdl_diff_get_field, &err_param, NULL, err); NluaXdiffMode mode = kNluaXdiffModeUnified; - bool had_on_hunk = false; bool had_result_type_indices = false; - for (size_t i = 0; i < opts.size; i++) { - String k = opts.items[i].key; - Object *v = &opts.items[i].value; - if (strequal("on_hunk", k.data)) { - if (check_xdiff_opt(v->type, kObjectTypeLuaRef, "on_hunk", err)) { - goto exit_1; - } - had_on_hunk = true; - nlua_pushref(lstate, v->data.luaref); - } else if (strequal("result_type", k.data)) { - if (check_xdiff_opt(v->type, kObjectTypeString, "result_type", err)) { - goto exit_1; - } - if (strequal("unified", v->data.string.data)) { - // the default - } else if (strequal("indices", v->data.string.data)) { - had_result_type_indices = true; - } else { - api_set_error(err, kErrorTypeValidation, "not a valid result_type"); - goto exit_1; - } - } else if (strequal("algorithm", k.data)) { - if (check_xdiff_opt(v->type, kObjectTypeString, "algorithm", err)) { - goto exit_1; - } - if (strequal("myers", v->data.string.data)) { - // default - } else if (strequal("minimal", v->data.string.data)) { - params->flags |= XDF_NEED_MINIMAL; - } else if (strequal("patience", v->data.string.data)) { - params->flags |= XDF_PATIENCE_DIFF; - } else if (strequal("histogram", v->data.string.data)) { - params->flags |= XDF_HISTOGRAM_DIFF; - } else { - api_set_error(err, kErrorTypeValidation, "not a valid algorithm"); - goto exit_1; - } - } else if (strequal("ctxlen", k.data)) { - if (check_xdiff_opt(v->type, kObjectTypeInteger, "ctxlen", err)) { - goto exit_1; - } - cfg->ctxlen = (long)v->data.integer; - } else if (strequal("interhunkctxlen", k.data)) { - if (check_xdiff_opt(v->type, kObjectTypeInteger, "interhunkctxlen", - err)) { - goto exit_1; - } - cfg->interhunkctxlen = (long)v->data.integer; - } else if (strequal("linematch", k.data)) { - if (v->type == kObjectTypeBoolean) { - *linematch = v->data.boolean ? INT64_MAX : 0; - } else if (v->type == kObjectTypeInteger) { - *linematch = v->data.integer; - } else { - api_set_error(err, kErrorTypeValidation, "linematch must be a boolean or integer"); - goto exit_1; - } + + if (HAS_KEY(&opts, xdl_diff, result_type)) { + if (strequal("unified", opts.result_type.data)) { + // the default + } else if (strequal("indices", opts.result_type.data)) { + had_result_type_indices = true; } else { - struct { - const char *name; - unsigned long value; - } flags[] = { - { "ignore_whitespace", XDF_IGNORE_WHITESPACE }, - { "ignore_whitespace_change", XDF_IGNORE_WHITESPACE_CHANGE }, - { "ignore_whitespace_change_at_eol", XDF_IGNORE_WHITESPACE_AT_EOL }, - { "ignore_cr_at_eol", XDF_IGNORE_CR_AT_EOL }, - { "ignore_blank_lines", XDF_IGNORE_BLANK_LINES }, - { "indent_heuristic", XDF_INDENT_HEURISTIC }, - { NULL, 0 }, - }; - bool key_used = false; - for (size_t j = 0; flags[j].name; j++) { - if (strequal(flags[j].name, k.data)) { - if (check_xdiff_opt(v->type, kObjectTypeBoolean, flags[j].name, - err)) { - goto exit_1; - } - if (v->data.boolean) { - params->flags |= flags[j].value; - } - key_used = true; - break; - } - } + api_set_error(err, kErrorTypeValidation, "not a valid result_type"); + goto exit_1; + } + } - if (key_used) { - continue; - } + if (HAS_KEY(&opts, xdl_diff, algorithm)) { + if (strequal("myers", opts.algorithm.data)) { + // default + } else if (strequal("minimal", opts.algorithm.data)) { + params->flags |= XDF_NEED_MINIMAL; + } else if (strequal("patience", opts.algorithm.data)) { + params->flags |= XDF_PATIENCE_DIFF; + } else if (strequal("histogram", opts.algorithm.data)) { + params->flags |= XDF_HISTOGRAM_DIFF; + } else { + api_set_error(err, kErrorTypeValidation, "not a valid algorithm"); + goto exit_1; + } + } - api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); + if (HAS_KEY(&opts, xdl_diff, ctxlen)) { + cfg->ctxlen = (long)opts.ctxlen; + } + + if (HAS_KEY(&opts, xdl_diff, interhunkctxlen)) { + cfg->interhunkctxlen = (long)opts.interhunkctxlen; + } + + if (HAS_KEY(&opts, xdl_diff, linematch)) { + if (opts.linematch.type == kObjectTypeBoolean) { + *linematch = opts.linematch.data.boolean ? INT64_MAX : 0; + } else if (opts.linematch.type == kObjectTypeInteger) { + *linematch = opts.linematch.data.integer; + } else { + api_set_error(err, kErrorTypeValidation, "linematch must be a boolean or integer"); goto exit_1; } } - if (had_on_hunk) { + params->flags |= opts.ignore_whitespace ? XDF_IGNORE_WHITESPACE : 0; + params->flags |= opts.ignore_whitespace_change ? XDF_IGNORE_WHITESPACE_CHANGE : 0; + params->flags |= opts.ignore_whitespace_change_at_eol ? XDF_IGNORE_WHITESPACE_AT_EOL : 0; + params->flags |= opts.ignore_cr_at_eol ? XDF_IGNORE_CR_AT_EOL : 0; + params->flags |= opts.ignore_blank_lines ? XDF_IGNORE_BLANK_LINES : 0; + params->flags |= opts.indent_heuristic ? XDF_INDENT_HEURISTIC : 0; + + if (HAS_KEY(&opts, xdl_diff, on_hunk)) { mode = kNluaXdiffModeOnHunkCB; + nlua_pushref(lstate, opts.on_hunk); + if (lua_type(lstate, -1) != LUA_TFUNCTION) { + api_set_error(err, kErrorTypeValidation, "on_hunk is not a function"); + } } else if (had_result_type_indices) { mode = kNluaXdiffModeLocations; } exit_1: - api_free_dictionary(opts); + api_free_string(opts.result_type); + api_free_string(opts.algorithm); + api_free_luaref(opts.on_hunk); return mode; } |