diff options
Diffstat (limited to 'runtime/lua/vim/shared.lua')
-rw-r--r-- | runtime/lua/vim/shared.lua | 330 |
1 files changed, 204 insertions, 126 deletions
diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 9542d93789..bd553598c7 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -6,46 +6,40 @@ -- or the test suite. (Eventually the test suite will be run in a worker process, -- so this wouldn't be a separate case to consider) +---@nodoc ---@diagnostic disable-next-line: lowercase-global vim = vim or {} -local function _id(v) - return v -end +---@generic T +---@param orig T +---@param cache? table<any,any> +---@return T +local function deepcopy(orig, cache) + if orig == vim.NIL then + return vim.NIL + elseif type(orig) == 'userdata' or type(orig) == 'thread' then + error('Cannot deepcopy object of type ' .. type(orig)) + elseif type(orig) ~= 'table' then + return orig + end -local deepcopy + --- @cast orig table<any,any> -local deepcopy_funcs = { - table = function(orig, cache) - if cache[orig] then - return cache[orig] - end - local copy = {} + if cache and cache[orig] then + return cache[orig] + end + local copy = {} --- @type table<any,any> + + if cache then cache[orig] = copy - local mt = getmetatable(orig) - for k, v in pairs(orig) do - copy[deepcopy(k, cache)] = deepcopy(v, cache) - end - return setmetatable(copy, mt) - end, - number = _id, - string = _id, - ['nil'] = _id, - boolean = _id, - ['function'] = _id, -} - -deepcopy = function(orig, _cache) - local f = deepcopy_funcs[type(orig)] - if f then - return f(orig, _cache or {}) - else - if type(orig) == 'userdata' and orig == vim.NIL then - return vim.NIL - end - error('Cannot deepcopy object of type ' .. type(orig)) end + + for k, v in pairs(orig) do + copy[deepcopy(k, cache)] = deepcopy(v, cache) + end + + return setmetatable(copy, getmetatable(orig)) end --- Returns a deep copy of the given object. Non-table objects are copied as @@ -54,13 +48,31 @@ end --- same functions as those in the input table. Userdata and threads are not --- copied and will throw an error. --- +--- Note: `noref=true` is much more performant on tables with unique table +--- fields, while `noref=false` is more performant on tables that reuse table +--- fields. +--- ---@generic T: table ---@param orig T Table to copy +---@param noref? boolean +--- When `false` (default) a contained table is only copied once and all +--- references point to this single copy. When `true` every occurrence of a +--- table results in a new copy. This also means that a cyclic reference can +--- cause `deepcopy()` to fail. ---@return T Table of copied keys and (nested) values. -function vim.deepcopy(orig) - return deepcopy(orig) +function vim.deepcopy(orig, noref) + return deepcopy(orig, not noref and {} or nil) end +--- @class vim.gsplit.Opts +--- @inlinedoc +--- +--- Use `sep` literally (as in string.find). +--- @field plain? boolean +--- +--- Discard empty segments at start and end of the sequence. +--- @field trimempty? boolean + --- Gets an |iterator| that splits a string at each instance of a separator, in "lazy" fashion --- (as opposed to |vim.split()| which is "eager"). --- @@ -89,12 +101,10 @@ end --- --- @param s string String to split --- @param sep string Separator or pattern ---- @param opts (table|nil) Keyword arguments |kwargs|: ---- - plain: (boolean) Use `sep` literally (as in string.find). ---- - trimempty: (boolean) Discard empty segments at start and end of the sequence. ----@return fun():string|nil (function) Iterator over the split components +--- @param opts? vim.gsplit.Opts Keyword arguments |kwargs|: +--- @return fun():string? : Iterator over the split components function vim.gsplit(s, sep, opts) - local plain + local plain --- @type boolean? local trimempty = false if type(opts) == 'boolean' then plain = opts -- For backwards compatibility. @@ -111,6 +121,11 @@ function vim.gsplit(s, sep, opts) local segs = {} local empty_start = true -- Only empty segments seen so far. + --- @param i integer? + --- @param j integer + --- @param ... unknown + --- @return string + --- @return ... local function _pass(i, j, ...) if i then assert(j + 1 > start, 'Infinite loop detected') @@ -180,8 +195,8 @@ end --- ---@param s string String to split ---@param sep string Separator or pattern ----@param opts (table|nil) Keyword arguments |kwargs| accepted by |vim.gsplit()| ----@return string[] List of split components +---@param opts? vim.gsplit.Opts Keyword arguments |kwargs|: +---@return string[] : List of split components function vim.split(s, sep, opts) local t = {} for c in vim.gsplit(s, sep, opts) do @@ -195,14 +210,15 @@ end --- ---@see From https://github.com/premake/premake-core/blob/master/src/base/table.lua --- ----@generic T: table +---@generic T ---@param t table<T, any> (table) Table ----@return T[] (list) List of keys +---@return T[] : List of keys function vim.tbl_keys(t) - assert(type(t) == 'table', string.format('Expected table, got %s', type(t))) + vim.validate({ t = { t, 't' } }) + --- @cast t table<any,any> local keys = {} - for k, _ in pairs(t) do + for k in pairs(t) do table.insert(keys, k) end return keys @@ -213,12 +229,14 @@ end --- ---@generic T ---@param t table<any, T> (table) Table ----@return T[] (list) List of values +---@return T[] : List of values function vim.tbl_values(t) - assert(type(t) == 'table', string.format('Expected table, got %s', type(t))) + vim.validate({ t = { t, 't' } }) local values = {} - for _, v in pairs(t) do + for _, v in + pairs(t --[[@as table<any,any>]]) + do table.insert(values, v) end return values @@ -227,13 +245,14 @@ end --- Apply a function to all values of a table. --- ---@generic T ----@param func fun(value: T): any (function) Function ----@param t table<any, T> (table) Table ----@return table Table of transformed values +---@param func fun(value: T): any Function +---@param t table<any, T> Table +---@return table : Table of transformed values function vim.tbl_map(func, t) vim.validate({ func = { func, 'c' }, t = { t, 't' } }) + --- @cast t table<any,any> - local rettab = {} + local rettab = {} --- @type table<any,any> for k, v in pairs(t) do rettab[k] = func(v) end @@ -245,19 +264,26 @@ end ---@generic T ---@param func fun(value: T): boolean (function) Function ---@param t table<any, T> (table) Table ----@return T[] (table) Table of filtered values +---@return T[] : Table of filtered values function vim.tbl_filter(func, t) vim.validate({ func = { func, 'c' }, t = { t, 't' } }) + --- @cast t table<any,any> - local rettab = {} + local rettab = {} --- @type table<any,any> for _, entry in pairs(t) do if func(entry) then - table.insert(rettab, entry) + rettab[#rettab + 1] = entry end end return rettab end +--- @class vim.tbl_contains.Opts +--- @inlinedoc +--- +--- `value` is a function reference to be checked (default false) +--- @field predicate? boolean + --- Checks if a table contains a given value, specified either directly or via --- a predicate that is checked for each value. --- @@ -274,13 +300,13 @@ end --- ---@param t table Table to check ---@param value any Value to compare or predicate function reference ----@param opts (table|nil) Keyword arguments |kwargs|: ---- - predicate: (boolean) `value` is a function reference to be checked (default false) +---@param opts? vim.tbl_contains.Opts Keyword arguments |kwargs|: ---@return boolean `true` if `t` contains `value` function vim.tbl_contains(t, value, opts) vim.validate({ t = { t, 't' }, opts = { opts, 't', true } }) + --- @cast t table<any,any> - local pred + local pred --- @type fun(v: any): boolean? if opts and opts.predicate then vim.validate({ value = { value, 'c' } }) pred = value @@ -307,6 +333,7 @@ end ---@return boolean `true` if `t` contains `value` function vim.list_contains(t, value) vim.validate({ t = { t, 't' } }) + --- @cast t table<any,any> for _, v in ipairs(t) do if v == value then @@ -323,7 +350,7 @@ end ---@param t table Table to check ---@return boolean `true` if `t` is empty function vim.tbl_isempty(t) - assert(type(t) == 'table', string.format('Expected table, got %s', type(t))) + vim.validate({ t = { t, 't' } }) return next(t) == nil end @@ -345,7 +372,7 @@ local function tbl_extend(behavior, deep_extend, ...) ) end - local ret = {} + local ret = {} --- @type table<any,any> if vim._empty_dict_mt ~= nil and getmetatable(select(1, ...)) == vim._empty_dict_mt then ret = vim.empty_dict() end @@ -353,6 +380,7 @@ local function tbl_extend(behavior, deep_extend, ...) for i = 1, select('#', ...) do local tbl = select(i, ...) vim.validate({ ['after the second argument'] = { tbl, 't' } }) + --- @cast tbl table<any,any> if tbl then for k, v in pairs(tbl) do if deep_extend and can_merge(v) and can_merge(ret[k]) then @@ -379,7 +407,7 @@ end --- - "keep": use value from the leftmost map --- - "force": use value from the rightmost map ---@param ... table Two or more tables ----@return table Merged table +---@return table : Merged table function vim.tbl_extend(behavior, ...) return tbl_extend(behavior, false, ...) end @@ -415,12 +443,14 @@ function vim.deep_equal(a, b) return false end if type(a) == 'table' then + --- @cast a table<any,any> + --- @cast b table<any,any> for k, v in pairs(a) do if not vim.deep_equal(v, b[k]) then return false end end - for k, _ in pairs(b) do + for k in pairs(b) do if a[k] == nil then return false end @@ -432,12 +462,17 @@ end --- Add the reverse lookup values to an existing table. --- For example: ---- ``tbl_add_reverse_lookup { A = 1 } == { [1] = 'A', A = 1 }`` +--- `tbl_add_reverse_lookup { A = 1 } == { [1] = 'A', A = 1 }` --- --- Note that this *modifies* the input. +---@deprecated ---@param o table Table to add the reverse to ---@return table o function vim.tbl_add_reverse_lookup(o) + vim.deprecate('vim.tbl_add_reverse_lookup', nil, '0.12') + + --- @cast o table<any,any> + --- @type any[] local keys = vim.tbl_keys(o) for _, k in ipairs(keys) do local v = o[k] @@ -467,15 +502,14 @@ end --- ---@param o table Table to index ---@param ... any Optional keys (0 or more, variadic) via which to index the table ---- ----@return any Nested value indexed by key (if it exists), else nil +---@return any : Nested value indexed by key (if it exists), else nil function vim.tbl_get(o, ...) local keys = { ... } if #keys == 0 then return nil end for i, k in ipairs(keys) do - o = o[k] + o = o[k] --- @type any if o == nil then return nil elseif type(o) ~= 'table' and next(keys, i) then @@ -494,8 +528,8 @@ end ---@generic T: table ---@param dst T List which will be modified and appended to ---@param src table List from which values will be inserted ----@param start (integer|nil) Start index on src. Defaults to 1 ----@param finish (integer|nil) Final index on src. Defaults to `#src` +---@param start integer? Start index on src. Defaults to 1 +---@param finish integer? Final index on src. Defaults to `#src` ---@return T dst function vim.list_extend(dst, src, start, finish) vim.validate({ @@ -519,6 +553,7 @@ end ---@return table Flattened copy of the given list-like table function vim.tbl_flatten(t) local result = {} + --- @param _t table<any,any> local function _tbl_flatten(_t) local n = #_t for i = 1, n do @@ -538,10 +573,13 @@ end --- ---@see Based on https://github.com/premake/premake-core/blob/master/src/base/table.lua --- ----@param t table Dict-like table ----@return function # |for-in| iterator over sorted keys and their values +---@generic T: table, K, V +---@param t T Dict-like table +---@return fun(table: table<K, V>, index?: K):K, V # |for-in| iterator over sorted keys and their values +---@return T function vim.spairs(t) - assert(type(t) == 'table', string.format('Expected table, got %s', type(t))) + vim.validate({ t = { t, 't' } }) + --- @cast t table<any,any> -- collect the keys local keys = {} @@ -557,7 +595,8 @@ function vim.spairs(t) if keys[i] then return keys[i], t[keys[i]] end - end + end, + t end --- Tests if `t` is an "array": a table indexed _only_ by integers (potentially non-contiguous). @@ -576,10 +615,12 @@ function vim.tbl_isarray(t) return false end + --- @cast t table<any,any> + local count = 0 for k, _ in pairs(t) do - --- Check if the number k is an integer + -- Check if the number k is an integer if type(k) == 'number' and k == math.floor(k) then count = count + 1 else @@ -614,23 +655,21 @@ function vim.tbl_islist(t) return false end - local num_elem = vim.tbl_count(t) - - if num_elem == 0 then - -- TODO(bfredl): in the future, we will always be inside nvim - -- then this check can be deleted. - if vim._empty_dict_mt == nil then - return nil - end + if next(t) == nil then return getmetatable(t) ~= vim._empty_dict_mt - else - for i = 1, num_elem do - if t[i] == nil then - return false - end + end + + local j = 1 + for _ in + pairs(t--[[@as table<any,any>]]) + do + if t[j] == nil then + return false end - return true + j = j + 1 end + + return true end --- Counts the number of non-nil values in table `t`. @@ -642,9 +681,10 @@ end --- ---@see https://github.com/Tieske/Penlight/blob/master/lua/pl/tablex.lua ---@param t table Table ----@return integer Number of non-nil values in table +---@return integer : Number of non-nil values in table function vim.tbl_count(t) vim.validate({ t = { t, 't' } }) + --- @cast t table<any,any> local count = 0 for _ in pairs(t) do @@ -656,12 +696,12 @@ end --- Creates a copy of a table containing only elements from start to end (inclusive) --- ---@generic T ----@param list T[] (list) Table +---@param list T[] Table ---@param start integer|nil Start range of slice ---@param finish integer|nil End range of slice ----@return T[] (list) Copy of table sliced from start to finish (inclusive) +---@return T[] Copy of table sliced from start to finish (inclusive) function vim.list_slice(list, start, finish) - local new_list = {} + local new_list = {} --- @type `T`[] for i = start or 1, finish or #list do new_list[#new_list + 1] = list[i] end @@ -710,6 +750,16 @@ function vim.endswith(s, suffix) end do + --- @alias vim.validate.Type + --- | 't' | 'table' + --- | 's' | 'string' + --- | 'n' | 'number' + --- | 'f' | 'function' + --- | 'c' | 'callable' + --- | 'nil' + --- | 'thread' + --- | 'userdata + local type_names = { ['table'] = 'table', t = 'table', @@ -728,10 +778,18 @@ do ['userdata'] = 'userdata', } + --- @nodoc + --- @class vim.validate.Spec {[1]: any, [2]: string|string[], [3]: boolean } + --- @field [1] any Argument value + --- @field [2] string|string[]|fun(v:any):boolean, string? Type name, or callable + --- @field [3]? boolean + local function _is_type(val, t) return type(val) == t or (t == 'callable' and vim.is_callable(val)) end + --- @param opt table<vim.validate.Type,vim.validate.Spec> + --- @return boolean, string? local function is_valid(opt) if type(opt) ~= 'table' then return false, string.format('opt: expected table, got %s', type(opt)) @@ -790,7 +848,7 @@ do end end - return true, nil + return true end --- Validates a parameter specification (types and values). @@ -798,41 +856,40 @@ do --- Usage example: --- --- ```lua - --- function user.new(name, age, hobbies) - --- vim.validate{ - --- name={name, 'string'}, - --- age={age, 'number'}, - --- hobbies={hobbies, 'table'}, - --- } - --- ... - --- end + --- function user.new(name, age, hobbies) + --- vim.validate{ + --- name={name, 'string'}, + --- age={age, 'number'}, + --- hobbies={hobbies, 'table'}, + --- } + --- ... + --- end --- ``` --- --- Examples with explicit argument values (can be run directly): --- --- ```lua - --- vim.validate{arg1={{'foo'}, 'table'}, arg2={'foo', 'string'}} - --- --> NOP (success) + --- vim.validate{arg1={{'foo'}, 'table'}, arg2={'foo', 'string'}} + --- --> NOP (success) --- - --- vim.validate{arg1={1, 'table'}} - --- --> error('arg1: expected table, got number') + --- vim.validate{arg1={1, 'table'}} + --- --> error('arg1: expected table, got number') --- - --- vim.validate{arg1={3, function(a) return (a % 2) == 0 end, 'even number'}} - --- --> error('arg1: expected even number, got 3') + --- vim.validate{arg1={3, function(a) return (a % 2) == 0 end, 'even number'}} + --- --> error('arg1: expected even number, got 3') --- ``` --- --- If multiple types are valid they can be given as a list. --- --- ```lua - --- vim.validate{arg1={{'foo'}, {'table', 'string'}}, arg2={'foo', {'table', 'string'}}} - --- -- NOP (success) - --- - --- vim.validate{arg1={1, {'string', 'table'}}} - --- -- error('arg1: expected string|table, got number') + --- vim.validate{arg1={{'foo'}, {'table', 'string'}}, arg2={'foo', {'table', 'string'}}} + --- -- NOP (success) --- + --- vim.validate{arg1={1, {'string', 'table'}}} + --- -- error('arg1: expected string|table, got number') --- ``` --- - ---@param opt table Names of parameters to validate. Each key is a parameter + ---@param opt table<vim.validate.Type,vim.validate.Spec> (table) Names of parameters to validate. Each key is a parameter --- name; each value is a tuple in one of these forms: --- 1. (arg_value, type_name, optional) --- - arg_value: argument value @@ -878,7 +935,7 @@ end --- a.b.c = 1 --- ``` --- ----@param createfn function?(key:any):any Provides the value for a missing `key`. +---@param createfn? fun(key:any):any Provides the value for a missing `key`. ---@return table # Empty table with `__index` metamethod. function vim.defaulttable(createfn) createfn = createfn or function(_) @@ -898,6 +955,7 @@ do ---@field private _idx_read integer ---@field private _idx_write integer ---@field private _size integer + ---@overload fun(self): table? local Ringbuf = {} --- Clear all items @@ -946,19 +1004,19 @@ do --- Once the buffer is full, adding a new entry overrides the oldest entry. --- --- ```lua - --- local ringbuf = vim.ringbuf(4) - --- ringbuf:push("a") - --- ringbuf:push("b") - --- ringbuf:push("c") - --- ringbuf:push("d") - --- ringbuf:push("e") -- overrides "a" - --- print(ringbuf:pop()) -- returns "b" - --- print(ringbuf:pop()) -- returns "c" + --- local ringbuf = vim.ringbuf(4) + --- ringbuf:push("a") + --- ringbuf:push("b") + --- ringbuf:push("c") + --- ringbuf:push("d") + --- ringbuf:push("e") -- overrides "a" + --- print(ringbuf:pop()) -- returns "b" + --- print(ringbuf:pop()) -- returns "c" --- - --- -- Can be used as iterator. Pops remaining items: - --- for val in ringbuf do - --- print(val) - --- end + --- -- Can be used as iterator. Pops remaining items: + --- for val in ringbuf do + --- print(val) + --- end --- ``` --- --- Returns a Ringbuf instance with the following methods: @@ -969,7 +1027,7 @@ do --- - |Ringbuf:clear()| --- ---@param size integer - ---@return vim.Ringbuf ringbuf (table) + ---@return vim.Ringbuf ringbuf function vim.ringbuf(size) local ringbuf = { _items = {}, @@ -986,4 +1044,24 @@ do end end +--- @private +--- @generic T +--- @param root string +--- @param mod T +--- @return T +function vim._defer_require(root, mod) + return setmetatable({}, { + ---@param t table<string, any> + ---@param k string + __index = function(t, k) + if not mod[k] then + return + end + local name = string.format('%s.%s', root, k) + t[k] = require(name) + return t[k] + end, + }) +end + return vim |