mirror of
https://github.com/neovim/neovim
synced 2025-07-16 01:01:49 +00:00
feat(lua): vim.tbl_contains supports general tables and predicates (#23040)
* feat(lua): vim.tbl_contains supports general tables and predicates Problem: `vim.tbl_contains` only works for list-like tables (integer keys without gaps) and primitive values (in particular, not for nested tables). Solution: Rename `vim.tbl_contains` to `vim.list_contains` and add new `vim.tbl_contains` that works for general tables and optionally allows `value` to be a predicate function that is checked for every key.
This commit is contained in:
@ -1698,6 +1698,19 @@ is_callable({f}) *vim.is_callable()*
|
|||||||
Return: ~
|
Return: ~
|
||||||
(boolean) `true` if `f` is callable, else `false`
|
(boolean) `true` if `f` is callable, else `false`
|
||||||
|
|
||||||
|
list_contains({t}, {value}) *vim.list_contains()*
|
||||||
|
Checks if a list-like table (integer keys without gaps) contains `value`.
|
||||||
|
|
||||||
|
Parameters: ~
|
||||||
|
• {t} (table) Table to check (must be list-like, not validated)
|
||||||
|
• {value} any Value to compare
|
||||||
|
|
||||||
|
Return: ~
|
||||||
|
(boolean) `true` if `t` contains `value`
|
||||||
|
|
||||||
|
See also: ~
|
||||||
|
• |vim.tbl_contains()| for checking values in general tables
|
||||||
|
|
||||||
list_extend({dst}, {src}, {start}, {finish}) *vim.list_extend()*
|
list_extend({dst}, {src}, {start}, {finish}) *vim.list_extend()*
|
||||||
Extends a list-like table with the values of another list-like table.
|
Extends a list-like table with the values of another list-like table.
|
||||||
|
|
||||||
@ -1797,16 +1810,31 @@ tbl_add_reverse_lookup({o}) *vim.tbl_add_reverse_lookup()*
|
|||||||
Return: ~
|
Return: ~
|
||||||
(table) o
|
(table) o
|
||||||
|
|
||||||
tbl_contains({t}, {value}) *vim.tbl_contains()*
|
tbl_contains({t}, {value}, {opts}) *vim.tbl_contains()*
|
||||||
Checks if a list-like (vector) table contains `value`.
|
Checks if a table contains a given value, specified either directly or via
|
||||||
|
a predicate that is checked for each value.
|
||||||
|
|
||||||
|
Example: >lua
|
||||||
|
|
||||||
|
vim.tbl_contains({ 'a', { 'b', 'c' } }, function(v)
|
||||||
|
return vim.deep_equal(v, { 'b', 'c' })
|
||||||
|
end, { predicate = true })
|
||||||
|
-- true
|
||||||
|
<
|
||||||
|
|
||||||
Parameters: ~
|
Parameters: ~
|
||||||
• {t} (table) Table to check
|
• {t} (table) Table to check
|
||||||
• {value} any Value to compare
|
• {value} any Value to compare or predicate function reference
|
||||||
|
• {opts} (table|nil) Keyword arguments |kwargs|:
|
||||||
|
• predicate: (boolean) `value` is a function reference to be
|
||||||
|
checked (default false)
|
||||||
|
|
||||||
Return: ~
|
Return: ~
|
||||||
(boolean) `true` if `t` contains `value`
|
(boolean) `true` if `t` contains `value`
|
||||||
|
|
||||||
|
See also: ~
|
||||||
|
• |vim.list_contains()| for checking values in list-like tables
|
||||||
|
|
||||||
tbl_count({t}) *vim.tbl_count()*
|
tbl_count({t}) *vim.tbl_count()*
|
||||||
Counts the number of non-nil values in table `t`.
|
Counts the number of non-nil values in table `t`.
|
||||||
|
|
||||||
|
@ -37,6 +37,10 @@ CHANGED FEATURES *news-changed*
|
|||||||
|
|
||||||
The following changes to existing APIs or features add new behavior.
|
The following changes to existing APIs or features add new behavior.
|
||||||
|
|
||||||
|
• |vim.tbl_contains()| now works for general tables and allows specifying a
|
||||||
|
predicate function that is checked for each value. (Use |vim.list_contains()|
|
||||||
|
for checking list-like tables (integer keys without gaps) for literal values.)
|
||||||
|
|
||||||
• |vim.region()| can use a string accepted by |getpos()| as position.
|
• |vim.region()| can use a string accepted by |getpos()| as position.
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
@ -44,8 +48,6 @@ REMOVED FEATURES *news-removed*
|
|||||||
|
|
||||||
The following deprecated functions or APIs were removed.
|
The following deprecated functions or APIs were removed.
|
||||||
|
|
||||||
• ...
|
|
||||||
|
|
||||||
• Vimball support is removed.
|
• Vimball support is removed.
|
||||||
- :Vimuntar command removed.
|
- :Vimuntar command removed.
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ end
|
|||||||
|
|
||||||
function M.properties.charset(bufnr, val)
|
function M.properties.charset(bufnr, val)
|
||||||
assert(
|
assert(
|
||||||
vim.tbl_contains({ 'utf-8', 'utf-8-bom', 'latin1', 'utf-16be', 'utf-16le' }, val),
|
vim.list_contains({ 'utf-8', 'utf-8-bom', 'latin1', 'utf-16be', 'utf-16le' }, val),
|
||||||
'charset must be one of "utf-8", "utf-8-bom", "latin1", "utf-16be", or "utf-16le"'
|
'charset must be one of "utf-8", "utf-8-bom", "latin1", "utf-16be", or "utf-16le"'
|
||||||
)
|
)
|
||||||
if val == 'utf-8' or val == 'utf-8-bom' then
|
if val == 'utf-8' or val == 'utf-8-bom' then
|
||||||
|
@ -325,7 +325,7 @@ local function check_tmux()
|
|||||||
-- check for RGB capabilities
|
-- check for RGB capabilities
|
||||||
local info = vim.fn.system({ 'tmux', 'display-message', '-p', '#{client_termfeatures}' })
|
local info = vim.fn.system({ 'tmux', 'display-message', '-p', '#{client_termfeatures}' })
|
||||||
info = vim.split(vim.trim(info), ',', { trimempty = true })
|
info = vim.split(vim.trim(info), ',', { trimempty = true })
|
||||||
if not vim.tbl_contains(info, 'RGB') then
|
if not vim.list_contains(info, 'RGB') then
|
||||||
local has_rgb = false
|
local has_rgb = false
|
||||||
if #info == 0 then
|
if #info == 0 then
|
||||||
-- client_termfeatures may not be supported; fallback to checking show-messages
|
-- client_termfeatures may not be supported; fallback to checking show-messages
|
||||||
|
@ -449,7 +449,7 @@ local function python()
|
|||||||
local path_bin = vim.fs.normalize(path .. '/' .. pyname)
|
local path_bin = vim.fs.normalize(path .. '/' .. pyname)
|
||||||
if
|
if
|
||||||
path_bin ~= vim.fs.normalize(python_exe)
|
path_bin ~= vim.fs.normalize(python_exe)
|
||||||
and vim.tbl_contains(python_multiple, path_bin)
|
and vim.list_contains(python_multiple, path_bin)
|
||||||
and executable(path_bin)
|
and executable(path_bin)
|
||||||
then
|
then
|
||||||
python_multiple[#python_multiple + 1] = path_bin
|
python_multiple[#python_multiple + 1] = path_bin
|
||||||
|
@ -449,7 +449,7 @@ function Loader.lsmod(path)
|
|||||||
if topname then
|
if topname then
|
||||||
Loader._indexed[path][topname] = { modpath = modpath, modname = topname }
|
Loader._indexed[path][topname] = { modpath = modpath, modname = topname }
|
||||||
Loader._topmods[topname] = Loader._topmods[topname] or {}
|
Loader._topmods[topname] = Loader._topmods[topname] or {}
|
||||||
if not vim.tbl_contains(Loader._topmods[topname], path) then
|
if not vim.list_contains(Loader._topmods[topname], path) then
|
||||||
table.insert(Loader._topmods[topname], path)
|
table.insert(Loader._topmods[topname], path)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -523,7 +523,7 @@ function M._inspect(opts)
|
|||||||
{ ms(Loader._stats[stat].time / Loader._stats[stat].total) .. '\n', 'Bold' },
|
{ ms(Loader._stats[stat].time / Loader._stats[stat].total) .. '\n', 'Bold' },
|
||||||
})
|
})
|
||||||
for k, v in pairs(Loader._stats[stat]) do
|
for k, v in pairs(Loader._stats[stat]) do
|
||||||
if not vim.tbl_contains({ 'time', 'total' }, k) then
|
if not vim.list_contains({ 'time', 'total' }, k) then
|
||||||
chunks[#chunks + 1] = { '* ' .. k .. ':' .. string.rep(' ', 9 - #k) }
|
chunks[#chunks + 1] = { '* ' .. k .. ':' .. string.rep(' ', 9 - #k) }
|
||||||
chunks[#chunks + 1] = { tostring(v) .. '\n', 'Number' }
|
chunks[#chunks + 1] = { tostring(v) .. '\n', 'Number' }
|
||||||
end
|
end
|
||||||
|
@ -171,7 +171,7 @@ local function for_each_buffer_client(bufnr, fn, restrict_client_ids)
|
|||||||
if restrict_client_ids and #restrict_client_ids > 0 then
|
if restrict_client_ids and #restrict_client_ids > 0 then
|
||||||
local filtered_client_ids = {}
|
local filtered_client_ids = {}
|
||||||
for client_id in pairs(client_ids) do
|
for client_id in pairs(client_ids) do
|
||||||
if vim.tbl_contains(restrict_client_ids, client_id) then
|
if vim.list_contains(restrict_client_ids, client_id) then
|
||||||
filtered_client_ids[client_id] = true
|
filtered_client_ids[client_id] = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -2186,7 +2186,7 @@ function lsp.formatexpr(opts)
|
|||||||
opts = opts or {}
|
opts = opts or {}
|
||||||
local timeout_ms = opts.timeout_ms or 500
|
local timeout_ms = opts.timeout_ms or 500
|
||||||
|
|
||||||
if vim.tbl_contains({ 'i', 'R', 'ic', 'ix' }, vim.fn.mode()) then
|
if vim.list_contains({ 'i', 'R', 'ic', 'ix' }, vim.fn.mode()) then
|
||||||
-- `formatexpr` is also called when exceeding `textwidth` in insert mode
|
-- `formatexpr` is also called when exceeding `textwidth` in insert mode
|
||||||
-- fall back to internal formatting
|
-- fall back to internal formatting
|
||||||
return 1
|
return 1
|
||||||
|
@ -17,14 +17,14 @@ P.take_until = function(targets, specials)
|
|||||||
table.insert(raw, '\\')
|
table.insert(raw, '\\')
|
||||||
new_pos = new_pos + 1
|
new_pos = new_pos + 1
|
||||||
c = string.sub(input, new_pos, new_pos)
|
c = string.sub(input, new_pos, new_pos)
|
||||||
if not vim.tbl_contains(targets, c) and not vim.tbl_contains(specials, c) then
|
if not vim.list_contains(targets, c) and not vim.list_contains(specials, c) then
|
||||||
table.insert(esc, '\\')
|
table.insert(esc, '\\')
|
||||||
end
|
end
|
||||||
table.insert(raw, c)
|
table.insert(raw, c)
|
||||||
table.insert(esc, c)
|
table.insert(esc, c)
|
||||||
new_pos = new_pos + 1
|
new_pos = new_pos + 1
|
||||||
else
|
else
|
||||||
if vim.tbl_contains(targets, c) then
|
if vim.list_contains(targets, c) then
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
table.insert(raw, c)
|
table.insert(raw, c)
|
||||||
|
@ -42,7 +42,7 @@ local function execute_lens(lens, bufnr, client_id)
|
|||||||
-- Need to use the client that returned the lens → must not use buf_request
|
-- Need to use the client that returned the lens → must not use buf_request
|
||||||
local command_provider = client.server_capabilities.executeCommandProvider
|
local command_provider = client.server_capabilities.executeCommandProvider
|
||||||
local commands = type(command_provider) == 'table' and command_provider.commands or {}
|
local commands = type(command_provider) == 'table' and command_provider.commands or {}
|
||||||
if not vim.tbl_contains(commands, command.command) then
|
if not vim.list_contains(commands, command.command) then
|
||||||
vim.notify(
|
vim.notify(
|
||||||
string.format(
|
string.format(
|
||||||
'Language server does not support command `%s`. This command may require a client extension.',
|
'Language server does not support command `%s`. This command may require a client extension.',
|
||||||
|
@ -1476,7 +1476,7 @@ end
|
|||||||
local function close_preview_window(winnr, bufnrs)
|
local function close_preview_window(winnr, bufnrs)
|
||||||
vim.schedule(function()
|
vim.schedule(function()
|
||||||
-- exit if we are in one of ignored buffers
|
-- exit if we are in one of ignored buffers
|
||||||
if bufnrs and vim.tbl_contains(bufnrs, api.nvim_get_current_buf()) then
|
if bufnrs and vim.list_contains(bufnrs, api.nvim_get_current_buf()) then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -252,12 +252,53 @@ function vim.tbl_filter(func, t)
|
|||||||
return rettab
|
return rettab
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Checks if a list-like (vector) table contains `value`.
|
--- Checks if a table contains a given value, specified either directly or via
|
||||||
|
--- a predicate that is checked for each value.
|
||||||
|
---
|
||||||
|
--- Example:
|
||||||
|
--- <pre>lua
|
||||||
|
--- vim.tbl_contains({ 'a', { 'b', 'c' } }, function(v)
|
||||||
|
--- return vim.deep_equal(v, { 'b', 'c' })
|
||||||
|
--- end, { predicate = true })
|
||||||
|
--- -- true
|
||||||
|
--- </pre>
|
||||||
|
---
|
||||||
|
---@see |vim.list_contains()| for checking values in list-like tables
|
||||||
---
|
---
|
||||||
---@param t table Table to check
|
---@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)
|
||||||
|
---@return boolean `true` if `t` contains `value`
|
||||||
|
function vim.tbl_contains(t, value, opts)
|
||||||
|
vim.validate({ t = { t, 't' }, opts = { opts, 't', true } })
|
||||||
|
|
||||||
|
local pred
|
||||||
|
if opts and opts.predicate then
|
||||||
|
vim.validate({ value = { value, 'c' } })
|
||||||
|
pred = value
|
||||||
|
else
|
||||||
|
pred = function(v)
|
||||||
|
return v == value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, v in pairs(t) do
|
||||||
|
if pred(v) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Checks if a list-like table (integer keys without gaps) contains `value`.
|
||||||
|
---
|
||||||
|
---@see |vim.tbl_contains()| for checking values in general tables
|
||||||
|
---
|
||||||
|
---@param t table Table to check (must be list-like, not validated)
|
||||||
---@param value any Value to compare
|
---@param value any Value to compare
|
||||||
---@return boolean `true` if `t` contains `value`
|
---@return boolean `true` if `t` contains `value`
|
||||||
function vim.tbl_contains(t, value)
|
function vim.list_contains(t, value)
|
||||||
vim.validate({ t = { t, 't' } })
|
vim.validate({ t = { t, 't' } })
|
||||||
|
|
||||||
for _, v in ipairs(t) do
|
for _, v in ipairs(t) do
|
||||||
|
@ -491,7 +491,7 @@ local function visit_node(root, level, lang_tree, headings, opt, stats)
|
|||||||
return '' -- Discard common "noise" lines.
|
return '' -- Discard common "noise" lines.
|
||||||
end
|
end
|
||||||
-- XXX: Avoid newlines (too much whitespace) after block elements in old (preformatted) layout.
|
-- XXX: Avoid newlines (too much whitespace) after block elements in old (preformatted) layout.
|
||||||
local div = opt.old and root:child(0) and vim.tbl_contains({'column_heading', 'h1', 'h2', 'h3'}, root:child(0):type())
|
local div = opt.old and root:child(0) and vim.list_contains({'column_heading', 'h1', 'h2', 'h3'}, root:child(0):type())
|
||||||
return string.format('%s%s', div and trim(text) or text, div and '' or '\n')
|
return string.format('%s%s', div and trim(text) or text, div and '' or '\n')
|
||||||
elseif node_name == 'line_li' then
|
elseif node_name == 'line_li' then
|
||||||
local sib = root:prev_sibling()
|
local sib = root:prev_sibling()
|
||||||
@ -522,7 +522,7 @@ local function visit_node(root, level, lang_tree, headings, opt, stats)
|
|||||||
s = fix_tab_after_conceal(s, node_text(root:next_sibling()))
|
s = fix_tab_after_conceal(s, node_text(root:next_sibling()))
|
||||||
end
|
end
|
||||||
return s
|
return s
|
||||||
elseif vim.tbl_contains({'codespan', 'keycode'}, node_name) then
|
elseif vim.list_contains({'codespan', 'keycode'}, node_name) then
|
||||||
if root:has_error() then
|
if root:has_error() then
|
||||||
return text
|
return text
|
||||||
end
|
end
|
||||||
@ -554,7 +554,7 @@ local function visit_node(root, level, lang_tree, headings, opt, stats)
|
|||||||
if root:has_error() then
|
if root:has_error() then
|
||||||
return text
|
return text
|
||||||
end
|
end
|
||||||
local in_heading = vim.tbl_contains({'h1', 'h2', 'h3'}, parent)
|
local in_heading = vim.list_contains({'h1', 'h2', 'h3'}, parent)
|
||||||
local cssclass = (not in_heading and get_indent(node_text()) > 8) and 'help-tag-right' or 'help-tag'
|
local cssclass = (not in_heading and get_indent(node_text()) > 8) and 'help-tag-right' or 'help-tag'
|
||||||
local tagname = node_text(root:child(1), false)
|
local tagname = node_text(root:child(1), false)
|
||||||
if vim.tbl_count(stats.first_tags) < 2 then
|
if vim.tbl_count(stats.first_tags) < 2 then
|
||||||
@ -601,7 +601,7 @@ local function get_helpfiles(include)
|
|||||||
for f, type in vim.fs.dir(dir) do
|
for f, type in vim.fs.dir(dir) do
|
||||||
if (vim.endswith(f, '.txt')
|
if (vim.endswith(f, '.txt')
|
||||||
and type == 'file'
|
and type == 'file'
|
||||||
and (not include or vim.tbl_contains(include, f))) then
|
and (not include or vim.list_contains(include, f))) then
|
||||||
local fullpath = vim.fn.fnamemodify(('%s/%s'):format(dir, f), ':p')
|
local fullpath = vim.fn.fnamemodify(('%s/%s'):format(dir, f), ':p')
|
||||||
table.insert(rv, fullpath)
|
table.insert(rv, fullpath)
|
||||||
end
|
end
|
||||||
|
@ -88,7 +88,7 @@ local function validate_commit(commit_message)
|
|||||||
-- Check if type is correct
|
-- Check if type is correct
|
||||||
local type = vim.split(before_colon, "(", {plain = true})[1]
|
local type = vim.split(before_colon, "(", {plain = true})[1]
|
||||||
local allowed_types = {'build', 'ci', 'docs', 'feat', 'fix', 'perf', 'refactor', 'revert', 'test', 'vim-patch'}
|
local allowed_types = {'build', 'ci', 'docs', 'feat', 'fix', 'perf', 'refactor', 'revert', 'test', 'vim-patch'}
|
||||||
if not vim.tbl_contains(allowed_types, type) then
|
if not vim.list_contains(allowed_types, type) then
|
||||||
return string.format(
|
return string.format(
|
||||||
[[Invalid commit type "%s". Allowed types are:
|
[[Invalid commit type "%s". Allowed types are:
|
||||||
%s.
|
%s.
|
||||||
|
@ -461,6 +461,22 @@ describe('lua stdlib', function()
|
|||||||
pcall_err(exec_lua, [[return vim.pesc(2)]]))
|
pcall_err(exec_lua, [[return vim.pesc(2)]]))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it('vim.list_contains', function()
|
||||||
|
eq(true, exec_lua("return vim.list_contains({'a','b','c'}, 'c')"))
|
||||||
|
eq(false, exec_lua("return vim.list_contains({'a','b','c'}, 'd')"))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('vim.tbl_contains', function()
|
||||||
|
eq(true, exec_lua("return vim.tbl_contains({'a','b','c'}, 'c')"))
|
||||||
|
eq(false, exec_lua("return vim.tbl_contains({'a','b','c'}, 'd')"))
|
||||||
|
eq(true, exec_lua("return vim.tbl_contains({[2]='a',foo='b',[5] = 'c'}, 'c')"))
|
||||||
|
eq(true, exec_lua([[
|
||||||
|
return vim.tbl_contains({ 'a', { 'b', 'c' } }, function(v)
|
||||||
|
return vim.deep_equal(v, { 'b', 'c' })
|
||||||
|
end, { predicate = true })
|
||||||
|
]]))
|
||||||
|
end)
|
||||||
|
|
||||||
it('vim.tbl_keys', function()
|
it('vim.tbl_keys', function()
|
||||||
eq({}, exec_lua("return vim.tbl_keys({})"))
|
eq({}, exec_lua("return vim.tbl_keys({})"))
|
||||||
for _, v in pairs(exec_lua("return vim.tbl_keys({'a', 'b', 'c'})")) do
|
for _, v in pairs(exec_lua("return vim.tbl_keys({'a', 'b', 'c'})")) do
|
||||||
|
Reference in New Issue
Block a user