diff options
Diffstat (limited to 'runtime/lua/vim/shared.lua')
-rw-r--r-- | runtime/lua/vim/shared.lua | 354 |
1 files changed, 195 insertions, 159 deletions
diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 4d06cdd77d..4f2373b182 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -109,7 +109,9 @@ function vim.gsplit(s, sep, opts) if type(opts) == 'boolean' then plain = opts -- For backwards compatibility. else - vim.validate({ s = { s, 's' }, sep = { sep, 's' }, opts = { opts, 't', true } }) + vim.validate('s', s, 'string') + vim.validate('sep', sep, 'string') + vim.validate('opts', opts, 'table', true) opts = opts or {} plain, trimempty = opts.plain, opts.trimempty end @@ -249,7 +251,8 @@ end ---@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' } }) + vim.validate('func', func, 'callable') + vim.validate('t', t, 'table') --- @cast t table<any,any> local rettab = {} --- @type table<any,any> @@ -266,7 +269,8 @@ end ---@param t table<any, T> (table) Table ---@return T[] : Table of filtered values function vim.tbl_filter(func, t) - vim.validate({ func = { func, 'c' }, t = { t, 't' } }) + vim.validate('func', func, 'callable') + vim.validate('t', t, 'table') --- @cast t table<any,any> local rettab = {} --- @type table<any,any> @@ -303,12 +307,13 @@ end ---@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 } }) + vim.validate('t', t, 'table') + vim.validate('opts', opts, 'table', true) --- @cast t table<any,any> local pred --- @type fun(v: any): boolean? if opts and opts.predicate then - vim.validate({ value = { value, 'c' } }) + vim.validate('value', value, 'callable') pred = value else pred = function(v) @@ -550,12 +555,10 @@ end ---@param finish integer? Final index on src. Defaults to `#src` ---@return T dst function vim.list_extend(dst, src, start, finish) - vim.validate({ - dst = { dst, 't' }, - src = { src, 't' }, - start = { start, 'n', true }, - finish = { finish, 'n', true }, - }) + vim.validate('dst', dst, 'table') + vim.validate('src', src, 'table') + vim.validate('start', start, 'number', true) + vim.validate('finish', finish, 'number', true) for i = start or 1, finish or #src do table.insert(dst, src[i]) end @@ -778,231 +781,227 @@ 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', - ['string'] = 'string', - s = 'string', - ['number'] = 'number', - n = 'number', - ['boolean'] = 'boolean', + --- @alias vim.validate.Validator + --- | type + --- | 'callable' + --- | (type|'callable')[] + --- | fun(v:any):boolean, string? + + local type_aliases = { b = 'boolean', - ['function'] = 'function', - f = 'function', - ['callable'] = 'callable', c = 'callable', - ['nil'] = 'nil', - ['thread'] = 'thread', - ['userdata'] = 'userdata', + f = 'function', + n = 'number', + s = 'string', + t = 'table', } --- @nodoc - --- @class vim.validate.Spec [any, string|string[], boolean] + --- @class vim.validate.Spec --- @field [1] any Argument value - --- @field [2] string|string[]|fun(v:any):boolean, string? Type name, or callable - --- @field [3]? boolean + --- @field [2] vim.validate.Validator Argument validator + --- @field [3]? boolean|string Optional flag or error message - local function _is_type(val, t) + local function is_type(val, t) return type(val) == t or (t == 'callable' and vim.is_callable(val)) end --- @param param_name string - --- @param spec vim.validate.Spec + --- @param val any + --- @param validator vim.validate.Validator + --- @param message? string + --- @param allow_alias? boolean Allow short type names: 'n', 's', 't', 'b', 'f', 'c' --- @return string? - local function is_param_valid(param_name, spec) - if type(spec) ~= 'table' then - return string.format('opt[%s]: expected table, got %s', param_name, type(spec)) - end + local function is_valid(param_name, val, validator, message, allow_alias) + if type(validator) == 'string' then + local expected = allow_alias and type_aliases[validator] or validator - local val = spec[1] -- Argument value - local types = spec[2] -- Type name, or callable - local optional = (true == spec[3]) - - if type(types) == 'string' then - types = { types } - end + if not expected then + return string.format('invalid type name: %s', validator) + end - if vim.is_callable(types) then + if not is_type(val, expected) then + return string.format('%s: expected %s, got %s', param_name, expected, type(val)) + end + elseif vim.is_callable(validator) then -- Check user-provided validation function - local valid, optional_message = types(val) + local valid, opt_msg = validator(val) if not valid then - local error_message = - string.format('%s: expected %s, got %s', param_name, (spec[3] or '?'), tostring(val)) - if optional_message ~= nil then - error_message = string.format('%s. Info: %s', error_message, optional_message) + local err_msg = + string.format('%s: expected %s, got %s', param_name, message or '?', tostring(val)) + + if opt_msg then + err_msg = string.format('%s. Info: %s', err_msg, opt_msg) end - return error_message + return err_msg end - elseif type(types) == 'table' then - local success = false - for i, t in ipairs(types) do - local t_name = type_names[t] - if not t_name then + elseif type(validator) == 'table' then + for _, t in ipairs(validator) do + local expected = allow_alias and type_aliases[t] or t + if not expected then return string.format('invalid type name: %s', t) end - types[i] = t_name - if (optional and val == nil) or _is_type(val, t_name) then - success = true - break + if is_type(val, expected) then + return -- success end end - if not success then - return string.format( - '%s: expected %s, got %s', - param_name, - table.concat(types, '|'), - type(val) - ) + + -- Normalize validator types for error message + if allow_alias then + for i, t in ipairs(validator) do + validator[i] = type_aliases[t] or t + end end + + return string.format( + '%s: expected %s, got %s', + param_name, + table.concat(validator, '|'), + type(val) + ) else - return string.format('invalid type name: %s', tostring(types)) + return string.format('invalid validator: %s', tostring(validator)) end 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)) - end - + --- @param opt table<type|'callable',vim.validate.Spec> + --- @return string? + local function validate_spec(opt) local report --- @type table<string,string>? for param_name, spec in pairs(opt) do - local msg = is_param_valid(param_name, spec) - if msg then + local err_msg --- @type string? + if type(spec) ~= 'table' then + err_msg = string.format('opt[%s]: expected table, got %s', param_name, type(spec)) + else + local value, validator = spec[1], spec[2] + local msg = type(spec[3]) == 'string' and spec[3] or nil --[[@as string?]] + local optional = spec[3] == true + if not (optional and value == nil) then + err_msg = is_valid(param_name, value, validator, msg, true) + end + end + + if err_msg then report = report or {} - report[param_name] = msg + report[param_name] = err_msg end end if report then for _, msg in vim.spairs(report) do -- luacheck: ignore - return false, msg + return msg end end - - return true end --- Validate function arguments. --- --- This function has two valid forms: --- - --- 1. vim.validate(name: str, value: any, type: string, optional?: bool) - --- 2. vim.validate(spec: table) + --- 1. `vim.validate(name, value, validator[, optional][, message])` --- - --- Form 1 validates that argument {name} with value {value} has the type - --- {type}. {type} must be a value returned by |lua-type()|. If {optional} is - --- true, then {value} may be null. This form is significantly faster and - --- should be preferred for simple cases. + --- Validates that argument {name} with value {value} satisfies + --- {validator}. If {optional} is given and is `true`, then {value} may be + --- `nil`. If {message} is given, then it is used as the expected type in the + --- error message. --- - --- Example: + --- Example: --- - --- ```lua - --- function vim.startswith(s, prefix) - --- vim.validate('s', s, 'string') - --- vim.validate('prefix', prefix, 'string') - --- ... - --- end - --- ``` + --- ```lua + --- function vim.startswith(s, prefix) + --- vim.validate('s', s, 'string') + --- vim.validate('prefix', prefix, 'string') + --- ... + --- end + --- ``` --- - --- Form 2 validates a parameter specification (types and values). Specs are - --- evaluated in alphanumeric order, until the first failure. + --- 2. `vim.validate(spec)` (deprecated) + --- where `spec` is of type + --- `table<string,[value:any, validator: vim.validate.Validator, optional_or_msg? : boolean|string]>)` --- - --- Usage example: + --- Validates a argument specification. + --- Specs are evaluated in alphanumeric order, until the first failure. --- - --- ```lua - --- function user.new(name, age, hobbies) - --- vim.validate{ - --- name={name, 'string'}, - --- age={age, 'number'}, - --- hobbies={hobbies, 'table'}, - --- } - --- ... - --- end - --- ``` + --- Example: + --- + --- ```lua + --- 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'}} + --- vim.validate('arg1', {'foo'}, 'table') + --- --> NOP (success) + --- vim.validate('arg2', 'foo', 'string') --- --> NOP (success) --- - --- vim.validate{arg1={1, 'table'}} + --- vim.validate('arg1', 1, 'table') --- --> error('arg1: expected table, got number') --- - --- vim.validate{arg1={3, function(a) return (a % 2) == 0 end, 'even number'}} + --- 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'}}} + --- vim.validate('arg1', {'foo'}, {'table', 'string'}) + --- vim.validate('arg2', 'foo', {'table', 'string'}) --- -- NOP (success) --- - --- vim.validate{arg1={1, {'string', 'table'}}} + --- vim.validate('arg1', 1, {'string', 'table'}) --- -- error('arg1: expected string|table, got number') --- ``` --- - ---@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 - --- - type_name: string|table type name, one of: ("table", "t", "string", - --- "s", "number", "n", "boolean", "b", "function", "f", "nil", - --- "thread", "userdata") or list of them. - --- - optional: (optional) boolean, if true, `nil` is valid - --- 2. (arg_value, fn, msg) - --- - arg_value: argument value - --- - fn: any function accepting one argument, returns true if and - --- only if the argument is valid. Can optionally return an additional - --- informative error message as the second returned value. - --- - msg: (optional) error string if validation fails - --- @overload fun(name: string, val: any, expected: string, optional?: boolean) - function vim.validate(opt, ...) - local ok = false - local err_msg ---@type string? - local narg = select('#', ...) - if narg == 0 then - ok, err_msg = is_valid(opt) - elseif narg >= 2 then - -- Overloaded signature for fast/simple cases - local name = opt --[[@as string]] - local v, expected, optional = ... ---@type string, string, boolean? - local actual = type(v) - - ok = (actual == expected) or (v == nil and optional == true) + --- @note `validator` set to a value returned by |lua-type()| provides the + --- best performance. + --- + --- @param name string Argument name + --- @param value string Argument value + --- @param validator vim.validate.Validator + --- - (`string|string[]`): Any value that can be returned from |lua-type()| in addition to + --- `'callable'`: `'boolean'`, `'callable'`, `'function'`, `'nil'`, `'number'`, `'string'`, `'table'`, + --- `'thread'`, `'userdata'`. + --- - (`fun(val:any): boolean, string?`) A function that returns a boolean and an optional + --- string message. + --- @param optional? boolean Argument is optional (may be omitted) + --- @param message? string message when validation fails + --- @overload fun(name: string, val: any, validator: vim.validate.Validator, message: string) + --- @overload fun(spec: table<string,[any, vim.validate.Validator, boolean|string]>) + function vim.validate(name, value, validator, optional, message) + local err_msg --- @type string? + if validator then -- Form 1 + -- Check validator as a string first to optimize the common case. + local ok = (type(value) == validator) or (value == nil and optional == true) if not ok then - err_msg = ('%s: expected %s, got %s%s'):format( - name, - expected, - actual, - v and (' (%s)'):format(v) or '' - ) + local msg = type(optional) == 'string' and optional or message --[[@as string?]] + -- Check more complicated validators + err_msg = is_valid(name, value, validator, msg, false) end + elseif type(name) == 'table' then -- Form 2 + vim.deprecate('vim.validate', 'vim.validate(name, value, validator, optional_or_msg)', '1.0') + err_msg = validate_spec(name) else error('invalid arguments') end - if not ok then + if err_msg then error(err_msg, 2) end end end + --- Returns true if object `f` can be called as a function. --- ---@param f any Any object @@ -1143,7 +1142,7 @@ end --- @param mod T --- @return T function vim._defer_require(root, mod) - return setmetatable({}, { + return setmetatable({ _submodules = mod }, { ---@param t table<string, any> ---@param k string __index = function(t, k) @@ -1157,6 +1156,34 @@ function vim._defer_require(root, mod) }) end +--- @private +--- Creates a module alias/shim that lazy-loads a target module. +--- +--- Unlike `vim.defaulttable()` this also: +--- - implements __call +--- - calls vim.deprecate() +--- +--- @param old_name string Name of the deprecated module, which will be shimmed. +--- @param new_name string Name of the new module, which will be loaded by require(). +function vim._defer_deprecated_module(old_name, new_name) + return setmetatable({}, { + ---@param _ table<string, any> + ---@param k string + __index = function(_, k) + vim.deprecate(old_name, new_name, '2.0.0', nil, false) + --- @diagnostic disable-next-line:no-unknown + local target = require(new_name) + return target[k] + end, + __call = function(self) + vim.deprecate(old_name, new_name, '2.0.0', nil, false) + --- @diagnostic disable-next-line:no-unknown + local target = require(new_name) + return target(self) + end, + }) +end + --- @nodoc --- @class vim.context.mods --- @field bo? table<string, any> @@ -1193,11 +1220,14 @@ local state_restore_order = { 'bo', 'wo', 'go', 'env' } --- @param context vim.context.mods --- @return vim.context.state local get_context_state = function(context) + --- @type vim.context.state local res = { bo = {}, env = {}, go = {}, wo = {} } -- Use specific order from possibly most to least intrusive for _, scope in ipairs(scope_order) do - for name, _ in pairs(context[scope] or {}) do + for name, _ in + pairs(context[scope] or {} --[[@as table<string,any>]]) + do local sc = scope == 'o' and scope_map[vim.api.nvim_get_option_info2(name, {}).scope] or scope -- Do not override already set state and fall back to `vim.NIL` for @@ -1291,7 +1321,10 @@ function vim._with(context, f) -- Apply some parts of the context in specific order -- NOTE: triggers `OptionSet` event for _, scope in ipairs(scope_order) do - for name, context_value in pairs(context[scope] or {}) do + for name, context_value in + pairs(context[scope] or {} --[[@as table<string,any>]]) + do + --- @diagnostic disable-next-line:no-unknown vim[scope][name] = context_value end end @@ -1302,7 +1335,10 @@ function vim._with(context, f) -- Restore relevant cached values in specific order, global scope last -- NOTE: triggers `OptionSet` event for _, scope in ipairs(state_restore_order) do - for name, cached_value in pairs(state[scope]) do + for name, cached_value in + pairs(state[scope] --[[@as table<string,any>]]) + do + --- @diagnostic disable-next-line:no-unknown vim[scope][name] = cached_value end end |