aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim/shared.lua
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/lua/vim/shared.lua')
-rw-r--r--runtime/lua/vim/shared.lua227
1 files changed, 220 insertions, 7 deletions
diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua
index 7727fdbab0..ff89acc524 100644
--- a/runtime/lua/vim/shared.lua
+++ b/runtime/lua/vim/shared.lua
@@ -47,9 +47,7 @@ end)()
--@param plain If `true` use `sep` literally (passed to String.find)
--@returns Iterator over the split components
function vim.gsplit(s, sep, plain)
- assert(type(s) == "string", string.format("Expected string, got %s", type(s)))
- assert(type(sep) == "string", string.format("Expected string, got %s", type(sep)))
- assert(type(plain) == "boolean" or type(plain) == "nil", string.format("Expected boolean or nil, got %s", type(plain)))
+ vim.validate{s={s,'s'},sep={sep,'s'},plain={plain,'b',true}}
local start = 1
local done = false
@@ -100,13 +98,45 @@ function vim.split(s,sep,plain)
return t
end
+--- Return a list of all keys used in a table.
+--- However, the order of the return table of keys is not guaranteed.
+---
+--@see From https://github.com/premake/premake-core/blob/master/src/base/table.lua
+---
+--@param t Table
+--@returns list of keys
+function vim.tbl_keys(t)
+ assert(type(t) == 'table', string.format("Expected table, got %s", type(t)))
+
+ local keys = {}
+ for k, _ in pairs(t) do
+ table.insert(keys, k)
+ end
+ return keys
+end
+
+--- Return a list of all values used in a table.
+--- However, the order of the return table of values is not guaranteed.
+---
+--@param t Table
+--@returns list of values
+function vim.tbl_values(t)
+ assert(type(t) == 'table', string.format("Expected table, got %s", type(t)))
+
+ local values = {}
+ for _, v in pairs(t) do
+ table.insert(values, v)
+ end
+ return values
+end
+
--- Checks if a list-like (vector) table contains `value`.
---
--@param t Table to check
--@param value Value to compare
--@returns true if `t` contains `value`
function vim.tbl_contains(t, value)
- assert(type(t) == 'table', string.format("Expected table, got %s", type(t)))
+ vim.validate{t={t,'t'}}
for _,v in ipairs(t) do
if v == value then
@@ -116,6 +146,16 @@ function vim.tbl_contains(t, value)
return false
end
+-- Returns true if the table is empty, and contains no indexed or keyed values.
+--
+--@see From https://github.com/premake/premake-core/blob/master/src/base/table.lua
+--
+--@param t Table to check
+function vim.tbl_isempty(t)
+ assert(type(t) == 'table', string.format("Expected table, got %s", type(t)))
+ return next(t) == nil
+end
+
--- Merges two or more map-like tables.
---
--@see |extend()|
@@ -147,13 +187,69 @@ function vim.tbl_extend(behavior, ...)
return ret
end
+--- Deep compare values for equality
+function vim.deep_equal(a, b)
+ if a == b then return true end
+ if type(a) ~= type(b) then return false end
+ if type(a) == 'table' then
+ -- TODO improve this algorithm's performance.
+ for k, v in pairs(a) do
+ if not vim.deep_equal(v, b[k]) then
+ return false
+ end
+ end
+ for k, v in pairs(b) do
+ if not vim.deep_equal(v, a[k]) then
+ return false
+ end
+ end
+ return true
+ end
+ return false
+end
+
+--- Add the reverse lookup values to an existing table.
+--- For example:
+--- `tbl_add_reverse_lookup { A = 1 } == { [1] = 'A', A = 1 }`
+--
+--Do note that it *modifies* the input.
+--@param o table The table to add the reverse to.
+function vim.tbl_add_reverse_lookup(o)
+ local keys = vim.tbl_keys(o)
+ for _, k in ipairs(keys) do
+ local v = o[k]
+ if o[v] then
+ error(string.format("The reverse lookup found an existing value for %q while processing key %q", tostring(v), tostring(k)))
+ end
+ o[v] = k
+ end
+ return o
+end
+
+--- Extends a list-like table with the values of another list-like table.
+---
+--NOTE: This *mutates* dst!
+--@see |extend()|
+---
+--@param dst The list which will be modified and appended to.
+--@param src The list from which values will be inserted.
+function vim.list_extend(dst, src)
+ assert(type(dst) == 'table', "dst must be a table")
+ assert(type(src) == 'table', "src must be a table")
+ for _, v in ipairs(src) do
+ table.insert(dst, v)
+ end
+ return dst
+end
+
--- Creates a copy of a list-like table such that any nested tables are
--- "unrolled" and appended to the result.
---
+--@see From https://github.com/premake/premake-core/blob/master/src/base/table.lua
+---
--@param t List-like table
--@returns Flattened copy of the given list-like table.
function vim.tbl_flatten(t)
- -- From https://github.com/premake/premake-core/blob/master/src/base/table.lua
local result = {}
local function _tbl_flatten(_t)
local n = #_t
@@ -170,13 +266,39 @@ function vim.tbl_flatten(t)
return result
end
+-- Determine whether a Lua table can be treated as an array.
+---
+--@params Table
+--@returns true: A non-empty array, false: A non-empty table, nil: An empty table
+function vim.tbl_islist(t)
+ if type(t) ~= 'table' then
+ return false
+ end
+
+ local count = 0
+
+ for k, _ in pairs(t) do
+ if type(k) == "number" then
+ count = count + 1
+ else
+ return false
+ end
+ end
+
+ if count > 0 then
+ return true
+ else
+ return nil
+ end
+end
+
--- Trim whitespace (Lua pattern "%s") from both sides of a string.
---
--@see https://www.lua.org/pil/20.2.html
--@param s String to trim
--@returns String with whitespace removed from its beginning and end
function vim.trim(s)
- assert(type(s) == 'string', string.format("Expected string, got %s", type(s)))
+ vim.validate{s={s,'s'}}
return s:match('^%s*(.*%S)') or ''
end
@@ -186,8 +308,99 @@ end
--@param s String to escape
--@returns %-escaped pattern string
function vim.pesc(s)
- assert(type(s) == 'string', string.format("Expected string, got %s", type(s)))
+ vim.validate{s={s,'s'}}
return s:gsub('[%(%)%.%%%+%-%*%?%[%]%^%$]', '%%%1')
end
+--- Validates a parameter specification (types and values).
+---
+--- Usage example:
+--- <pre>
+--- function user.new(name, age, hobbies)
+--- vim.validate{
+--- name={name, 'string'},
+--- age={age, 'number'},
+--- hobbies={hobbies, 'table'},
+--- }
+--- ...
+--- end
+--- </pre>
+---
+--- Examples with explicit argument values (can be run directly):
+--- <pre>
+--- vim.validate{arg1={{'foo'}, 'table'}, arg2={'foo', 'string'}}
+--- => NOP (success)
+---
+--- 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')
+--- </pre>
+---
+--@param opt Map of parameter names to validations. 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 type name, one of: ("table", "t", "string",
+--- "s", "number", "n", "boolean", "b", "function", "f", "nil",
+--- "thread", "userdata")
+--- - 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
+--- - msg: (optional) error string if validation fails
+function vim.validate(opt) end -- luacheck: no unused
+vim.validate = (function()
+ local type_names = {
+ t='table', s='string', n='number', b='boolean', f='function', c='callable',
+ ['table']='table', ['string']='string', ['number']='number',
+ ['boolean']='boolean', ['function']='function', ['callable']='callable',
+ ['nil']='nil', ['thread']='thread', ['userdata']='userdata',
+ }
+ local function _type_name(t)
+ local tname = type_names[t]
+ if tname == nil then
+ error(string.format('invalid type name: %s', tostring(t)))
+ end
+ return tname
+ end
+ local function _is_type(val, t)
+ return t == 'callable' and vim.is_callable(val) or type(val) == t
+ end
+
+ return function(opt)
+ assert(type(opt) == 'table', string.format('opt: expected table, got %s', type(opt)))
+ for param_name, spec in pairs(opt) do
+ assert(type(spec) == 'table', string.format('%s: expected table, got %s', param_name, type(spec)))
+
+ local val = spec[1] -- Argument value.
+ local t = spec[2] -- Type name, or callable.
+ local optional = (true == spec[3])
+
+ if not vim.is_callable(t) then -- Check type name.
+ if (not optional or val ~= nil) and not _is_type(val, _type_name(t)) then
+ error(string.format("%s: expected %s, got %s", param_name, _type_name(t), type(val)))
+ end
+ elseif not t(val) then -- Check user-provided validation function.
+ error(string.format("%s: expected %s, got %s", param_name, (spec[3] or '?'), val))
+ end
+ end
+ return true
+ end
+end)()
+
+--- Returns true if object `f` can be called as a function.
+---
+--@param f Any object
+--@return true if `f` is callable, else false
+function vim.is_callable(f)
+ if type(f) == 'function' then return true end
+ local m = getmetatable(f)
+ if m == nil then return false end
+ return type(m.__call) == 'function'
+end
+
return vim
+-- vim:sw=2 ts=2 et