Merge pull request #23303 from gpanders/more-vim-iter

Create iter_spec and vim.iter module
This commit is contained in:
Gregory Anders 2023-04-25 09:17:46 -06:00 committed by GitHub
commit cacc2dc419
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 639 additions and 651 deletions

View File

@ -1670,26 +1670,6 @@ endswith({s}, {suffix}) *vim.endswith()*
Return: ~ Return: ~
(boolean) `true` if `suffix` is a suffix of `s` (boolean) `true` if `suffix` is a suffix of `s`
filter({f}, {src}, {...}) *vim.filter()*
Filter a table or iterator.
This is a convenience function that performs: >lua
vim.iter(src):filter(f):totable()
<
Parameters: ~
• {f} function(...):bool Filter function. Accepts the current
iterator or table values as arguments and returns true if those
values should be kept in the final table
• {src} table|function Table or iterator function to filter
Return: ~
(table)
See also: ~
• |Iter:filter()|
gsplit({s}, {sep}, {opts}) *vim.gsplit()* gsplit({s}, {sep}, {opts}) *vim.gsplit()*
Splits a string at each instance of a separator. Splits a string at each instance of a separator.
@ -1735,64 +1715,6 @@ is_callable({f}) *vim.is_callable()*
Return: ~ Return: ~
(boolean) `true` if `f` is callable, else `false` (boolean) `true` if `f` is callable, else `false`
iter({src}, {...}) *vim.iter()*
Create an Iter |lua-iter| object from a table or iterator.
The input value can be a table or a function iterator (see |luaref-in|).
This function wraps the input value into an interface which allows
chaining multiple pipeline stages in an efficient manner. Each pipeline
stage receives as input the output values from the prior stage. The values
used in the first stage of the pipeline depend on the type passed to this
function:
• List tables pass only the value of each element
• Non-list tables pass both the key and value of each element
• Function iterators pass all of the values returned by their respective
function
Examples: >lua
local it = vim.iter({ 1, 2, 3, 4, 5 })
it:map(function(v)
return v * 3
end)
it:rev()
it:skip(2)
it:totable()
-- { 9, 6, 3 }
vim.iter(ipairs({ 1, 2, 3, 4, 5 })):map(function(i, v)
if i > 2 then return v end
end):totable()
-- { 3, 4, 5 }
local it = vim.iter(vim.gsplit('1,2,3,4,5', ','))
it:map(function(s) return tonumber(s) end)
for i, d in it:enumerate() do
print(string.format("Column %d is %d", i, d))
end
-- Column 1 is 1
-- Column 2 is 2
-- Column 3 is 3
-- Column 4 is 4
-- Column 5 is 5
vim.iter({ a = 1, b = 2, c = 3, z = 26 }):any(function(k, v)
return k == 'z'
end)
-- true
<
Parameters: ~
• {src} table|function Table or iterator.
Return: ~
Iter |lua-iter|
See also: ~
• |lua-iter|
list_contains({t}, {value}) *vim.list_contains()* list_contains({t}, {value}) *vim.list_contains()*
Checks if a list-like table (integer keys without gaps) contains `value`. Checks if a list-like table (integer keys without gaps) contains `value`.
@ -1835,26 +1757,6 @@ list_slice({list}, {start}, {finish}) *vim.list_slice()*
Return: ~ Return: ~
(list) Copy of table sliced from start to finish (inclusive) (list) Copy of table sliced from start to finish (inclusive)
map({f}, {src}, {...}) *vim.map()*
Map and filter a table or iterator.
This is a convenience function that performs: >lua
vim.iter(src):map(f):totable()
<
Parameters: ~
• {f} function(...):?any Map function. Accepts the current iterator
or table values as arguments and returns one or more new
values. Nil values are removed from the final table.
• {src} table|function Table or iterator function to filter
Return: ~
(table)
See also: ~
• |Iter:map()|
pesc({s}) *vim.pesc()* pesc({s}) *vim.pesc()*
Escapes magic chars in |lua-patterns|. Escapes magic chars in |lua-patterns|.
@ -2116,20 +2018,6 @@ tbl_values({t}) *vim.tbl_values()*
Return: ~ Return: ~
(list) List of values (list) List of values
totable({f}, {...}) *vim.totable()*
Collect an iterator into a table.
This is a convenience function that performs: >lua
vim.iter(f):totable()
<
Parameters: ~
• {f} (function) Iterator function
Return: ~
(table)
trim({s}) *vim.trim()* trim({s}) *vim.trim()*
Trim whitespace (Lua pattern "%s") from both sides of a string. Trim whitespace (Lua pattern "%s") from both sides of a string.
@ -2950,6 +2838,79 @@ range({spec}) *vim.version.range()*
============================================================================== ==============================================================================
Lua module: iter *lua-iter* Lua module: iter *lua-iter*
The *vim.iter* module provides a generic "iterator" interface over tables
and iterator functions.
*vim.iter()* wraps its table or function argument into an *Iter* object
with methods (such as |Iter:filter()| and |Iter:map()|) that transform the
underlying source data. These methods can be chained together to create
iterator "pipelines". Each pipeline stage receives as input the output
values from the prior stage. The values used in the first stage of the
pipeline depend on the type passed to this function:
• List tables pass only the value of each element
• Non-list tables pass both the key and value of each element
• Function iterators pass all of the values returned by their respective
function
Examples: >lua
local it = vim.iter({ 1, 2, 3, 4, 5 })
it:map(function(v)
return v * 3
end)
it:rev()
it:skip(2)
it:totable()
-- { 9, 6, 3 }
vim.iter(ipairs({ 1, 2, 3, 4, 5 })):map(function(i, v)
if i > 2 then return v end
end):totable()
-- { 3, 4, 5 }
local it = vim.iter(vim.gsplit('1,2,3,4,5', ','))
it:map(function(s) return tonumber(s) end)
for i, d in it:enumerate() do
print(string.format("Column %d is %d", i, d))
end
-- Column 1 is 1
-- Column 2 is 2
-- Column 3 is 3
-- Column 4 is 4
-- Column 5 is 5
vim.iter({ a = 1, b = 2, c = 3, z = 26 }):any(function(k, v)
return k == 'z'
end)
-- true
<
In addition to the |vim.iter()| function, the |vim.iter| module provides
convenience functions like |vim.iter.filter()| and |vim.iter.totable()|.
filter({f}, {src}, {...}) *vim.iter.filter()*
Filter a table or iterator.
This is a convenience function that performs: >lua
vim.iter(src):filter(f):totable()
<
Parameters: ~
• {f} function(...):bool Filter function. Accepts the current
iterator or table values as arguments and returns true if those
values should be kept in the final table
• {src} table|function Table or iterator function to filter
Return: ~
(table)
See also: ~
• |Iter:filter()|
Iter:all({self}, {pred}) *Iter:all()* Iter:all({self}, {pred}) *Iter:all()*
Return true if all of the items in the iterator match the given predicate. Return true if all of the items in the iterator match the given predicate.
@ -2988,9 +2949,7 @@ Iter:enumerate({self}) *Iter:enumerate()*
vim.iter(ipairs(t)) vim.iter(ipairs(t))
< <
over over >lua
>lua
vim.iter(t):enumerate() vim.iter(t):enumerate()
< <
@ -3348,16 +3307,38 @@ Iter:totable({self}) *Iter:totable()*
Return: ~ Return: ~
(table) (table)
new({src}, {...}) *new()* map({f}, {src}, {...}) *vim.iter.map()*
Create a new Iter object from a table or iterator. Map and filter a table or iterator.
This is a convenience function that performs: >lua
vim.iter(src):map(f):totable()
<
Parameters: ~ Parameters: ~
• {src} table|function Table or iterator to drain values from • {f} function(...):?any Map function. Accepts the current iterator
or table values as arguments and returns one or more new
values. Nil values are removed from the final table.
• {src} table|function Table or iterator function to filter
Return: ~ Return: ~
Iter (table)
next() *next()* See also: ~
TODO: Documentation • |Iter:map()|
totable({f}, {...}) *vim.iter.totable()*
Collect an iterator into a table.
This is a convenience function that performs: >lua
vim.iter(f):totable()
<
Parameters: ~
• {f} (function) Iterator function
Return: ~
(table)
vim:tw=78:ts=8:sw=4:sts=4:et:ft=help:norl: vim:tw=78:ts=8:sw=4:sts=4:et:ft=help:norl:

View File

@ -55,6 +55,7 @@ vim._submodules = {
inspect = true, inspect = true,
version = true, version = true,
fs = true, fs = true,
iter = true,
} }
-- These are for loading runtime modules in the vim namespace lazily. -- These are for loading runtime modules in the vim namespace lazily.

View File

@ -1,4 +1,56 @@
--- Iterator implementation. ---@defgroup lua-iter
---
--- The \*vim.iter\* module provides a generic "iterator" interface over tables and iterator
--- functions.
---
--- \*vim.iter()\* wraps its table or function argument into an \*Iter\* object with methods (such
--- as |Iter:filter()| and |Iter:map()|) that transform the underlying source data. These methods
--- can be chained together to create iterator "pipelines". Each pipeline stage receives as input
--- the output values from the prior stage. The values used in the first stage of the pipeline
--- depend on the type passed to this function:
---
--- - List tables pass only the value of each element
--- - Non-list tables pass both the key and value of each element
--- - Function iterators pass all of the values returned by their respective
--- function
---
--- Examples:
--- <pre>lua
--- local it = vim.iter({ 1, 2, 3, 4, 5 })
--- it:map(function(v)
--- return v * 3
--- end)
--- it:rev()
--- it:skip(2)
--- it:totable()
--- -- { 9, 6, 3 }
---
--- vim.iter(ipairs({ 1, 2, 3, 4, 5 })):map(function(i, v)
--- if i > 2 then return v end
--- end):totable()
--- -- { 3, 4, 5 }
---
--- local it = vim.iter(vim.gsplit('1,2,3,4,5', ','))
--- it:map(function(s) return tonumber(s) end)
--- for i, d in it:enumerate() do
--- print(string.format("Column %d is %d", i, d))
--- end
--- -- Column 1 is 1
--- -- Column 2 is 2
--- -- Column 3 is 3
--- -- Column 4 is 4
--- -- Column 5 is 5
---
--- vim.iter({ a = 1, b = 2, c = 3, z = 26 }):any(function(k, v)
--- return k == 'z'
--- end)
--- -- true
--- </pre>
---
--- In addition to the |vim.iter()| function, the |vim.iter| module provides convenience functions
--- like |vim.iter.filter()| and |vim.iter.totable()|.
local M = {}
---@class Iter ---@class Iter
local Iter = {} local Iter = {}
@ -733,7 +785,6 @@ end
--- </pre> --- </pre>
--- ---
--- over --- over
---
--- <pre>lua --- <pre>lua
--- vim.iter(t):enumerate() --- vim.iter(t):enumerate()
--- </pre> --- </pre>
@ -776,6 +827,7 @@ end
--- ---
---@param src table|function Table or iterator to drain values from ---@param src table|function Table or iterator to drain values from
---@return Iter ---@return Iter
---@private
function Iter.new(src, ...) function Iter.new(src, ...)
local it = {} local it = {}
if type(src) == 'table' then if type(src) == 'table' then
@ -807,6 +859,7 @@ function Iter.new(src, ...)
end end
end end
---@private
function it.next() function it.next()
return fn(src(s, var)) return fn(src(s, var))
end end
@ -832,4 +885,57 @@ function ListIter.new(t)
return it return it
end end
return Iter --- Collect an iterator into a table.
---
--- This is a convenience function that performs:
--- <pre>lua
--- vim.iter(f):totable()
--- </pre>
---
---@param f function Iterator function
---@return table
function M.totable(f, ...)
return Iter.new(f, ...):totable()
end
--- Filter a table or iterator.
---
--- This is a convenience function that performs:
--- <pre>lua
--- vim.iter(src):filter(f):totable()
--- </pre>
---
---@see |Iter:filter()|
---
---@param f function(...):bool Filter function. Accepts the current iterator or table values as
--- arguments and returns true if those values should be kept in the
--- final table
---@param src table|function Table or iterator function to filter
---@return table
function M.filter(f, src, ...)
return Iter.new(src, ...):filter(f):totable()
end
--- Map and filter a table or iterator.
---
--- This is a convenience function that performs:
--- <pre>lua
--- vim.iter(src):map(f):totable()
--- </pre>
---
---@see |Iter:map()|
---
---@param f function(...):?any Map function. Accepts the current iterator or table values as
--- arguments and returns one or more new values. Nil values are removed
--- from the final table.
---@param src table|function Table or iterator function to filter
---@return table
function M.map(f, src, ...)
return Iter.new(src, ...):map(f):totable()
end
return setmetatable(M, {
__call = function(_, ...)
return Iter.new(...)
end,
})

View File

@ -881,109 +881,4 @@ function vim.defaulttable(create)
}) })
end end
--- Create an Iter |lua-iter| object from a table or iterator.
---
--- The input value can be a table or a function iterator (see |luaref-in|).
---
--- This function wraps the input value into an interface which allows chaining
--- multiple pipeline stages in an efficient manner. Each pipeline stage
--- receives as input the output values from the prior stage. The values used in
--- the first stage of the pipeline depend on the type passed to this function:
---
--- - List tables pass only the value of each element
--- - Non-list tables pass both the key and value of each element
--- - Function iterators pass all of the values returned by their respective
--- function
---
--- Examples:
--- <pre>lua
--- local it = vim.iter({ 1, 2, 3, 4, 5 })
--- it:map(function(v)
--- return v * 3
--- end)
--- it:rev()
--- it:skip(2)
--- it:totable()
--- -- { 9, 6, 3 }
---
--- vim.iter(ipairs({ 1, 2, 3, 4, 5 })):map(function(i, v)
--- if i > 2 then return v end
--- end):totable()
--- -- { 3, 4, 5 }
---
--- local it = vim.iter(vim.gsplit('1,2,3,4,5', ','))
--- it:map(function(s) return tonumber(s) end)
--- for i, d in it:enumerate() do
--- print(string.format("Column %d is %d", i, d))
--- end
--- -- Column 1 is 1
--- -- Column 2 is 2
--- -- Column 3 is 3
--- -- Column 4 is 4
--- -- Column 5 is 5
---
--- vim.iter({ a = 1, b = 2, c = 3, z = 26 }):any(function(k, v)
--- return k == 'z'
--- end)
--- -- true
--- </pre>
---
---@see |lua-iter|
---
---@param src table|function Table or iterator.
---@return Iter @|lua-iter|
function vim.iter(src, ...)
local Iter = require('vim.iter')
return Iter.new(src, ...)
end
--- Collect an iterator into a table.
---
--- This is a convenience function that performs:
--- <pre>lua
--- vim.iter(f):totable()
--- </pre>
---
---@param f function Iterator function
---@return table
function vim.totable(f, ...)
return vim.iter(f, ...):totable()
end
--- Filter a table or iterator.
---
--- This is a convenience function that performs:
--- <pre>lua
--- vim.iter(src):filter(f):totable()
--- </pre>
---
---@see |Iter:filter()|
---
---@param f function(...):bool Filter function. Accepts the current iterator or table values as
--- arguments and returns true if those values should be kept in the
--- final table
---@param src table|function Table or iterator function to filter
---@return table
function vim.filter(f, src, ...)
return vim.iter(src, ...):filter(f):totable()
end
--- Map and filter a table or iterator.
---
--- This is a convenience function that performs:
--- <pre>lua
--- vim.iter(src):map(f):totable()
--- </pre>
---
---@see |Iter:map()|
---
---@param f function(...):?any Map function. Accepts the current iterator or table values as
--- arguments and returns one or more new values. Nil values are removed
--- from the final table.
---@param src table|function Table or iterator function to filter
---@return table
function vim.map(f, src, ...)
return vim.iter(src, ...):map(f):totable()
end
return vim return vim

View File

@ -188,7 +188,7 @@ CONFIG = {
f'*vim.{name}()*' f'*vim.{name}()*'
if fstem.lower() == '_editor' if fstem.lower() == '_editor'
else f'*{name}()*' else f'*{name}()*'
if fstem in ('iter.lua') if name[0].isupper()
else f'*{fstem}.{name}()*'), else f'*{fstem}.{name}()*'),
'module_override': { 'module_override': {
# `shared` functions are exposed on the `vim` module. # `shared` functions are exposed on the `vim` module.
@ -202,6 +202,7 @@ CONFIG = {
'fs': 'vim.fs', 'fs': 'vim.fs',
'secure': 'vim.secure', 'secure': 'vim.secure',
'version': 'vim.version', 'version': 'vim.version',
'iter': 'vim.iter',
}, },
'append_only': [ 'append_only': [
'shared.lua', 'shared.lua',

View File

@ -0,0 +1,425 @@
local helpers = require('test.functional.helpers')(after_each)
local eq = helpers.eq
local matches = helpers.matches
local pcall_err = helpers.pcall_err
describe('vim.iter', function()
it('filter()', function()
local function odd(v)
return v % 2 ~= 0
end
local t = { 1, 2, 3, 4, 5 }
eq({ 1, 3, 5 }, vim.iter(t):filter(odd):totable())
eq({ 2, 4 }, vim.iter(t):filter(function(v) return not odd(v) end):totable())
eq({}, vim.iter(t):filter(function(v) return v > 5 end):totable())
do
local it = vim.iter(ipairs(t))
it:filter(function(i, v) return i > 1 and v < 5 end)
it:map(function(_, v) return v * 2 end)
eq({ 4, 6, 8 }, it:totable())
end
local it = vim.iter(string.gmatch('the quick brown fox', '%w+'))
eq({'the', 'fox'}, it:filter(function(s) return #s <= 3 end):totable())
end)
it('map()', function()
local t = { 1, 2, 3, 4, 5 }
eq(
{ 2, 4, 6, 8, 10 },
vim
.iter(t)
:map(function(v)
return 2 * v
end)
:totable()
)
local it = vim.gsplit(
[[
Line 1
Line 2
Line 3
Line 4
]],
'\n'
)
eq(
{ 'Lion 2', 'Lion 4' },
vim
.iter(it)
:map(function(s)
local lnum = s:match('(%d+)')
if lnum and tonumber(lnum) % 2 == 0 then
return vim.trim(s:gsub('Line', 'Lion'))
end
end)
:totable()
)
end)
it('for loops', function()
local t = {1, 2, 3, 4, 5}
local acc = 0
for v in vim.iter(t):map(function(v) return v * 3 end) do
acc = acc + v
end
eq(45, acc)
end)
it('totable()', function()
do
local it = vim.iter({1, 2, 3}):map(function(v) return v, v*v end)
eq({{1, 1}, {2, 4}, {3, 9}}, it:totable())
end
do
local it = vim.iter(string.gmatch('1,4,lol,17,blah,2,9,3', '%d+')):map(tonumber)
eq({1, 4, 17, 2, 9, 3}, it:totable())
end
end)
it('next()', function()
local it = vim.iter({1, 2, 3}):map(function(v) return 2 * v end)
eq(2, it:next())
eq(4, it:next())
eq(6, it:next())
eq(nil, it:next())
end)
it('rev()', function()
eq({3, 2, 1}, vim.iter({1, 2, 3}):rev():totable())
local it = vim.iter(string.gmatch("abc", "%w"))
matches('rev%(%) requires a list%-like table', pcall_err(it.rev, it))
end)
it('skip()', function()
do
local t = {4, 3, 2, 1}
eq(t, vim.iter(t):skip(0):totable())
eq({3, 2, 1}, vim.iter(t):skip(1):totable())
eq({2, 1}, vim.iter(t):skip(2):totable())
eq({1}, vim.iter(t):skip(#t - 1):totable())
eq({}, vim.iter(t):skip(#t):totable())
eq({}, vim.iter(t):skip(#t + 1):totable())
end
do
local function skip(n)
return vim.iter(vim.gsplit('a|b|c|d', '|')):skip(n):totable()
end
eq({'a', 'b', 'c', 'd'}, skip(0))
eq({'b', 'c', 'd'}, skip(1))
eq({'c', 'd'}, skip(2))
eq({'d'}, skip(3))
eq({}, skip(4))
eq({}, skip(5))
end
end)
it('skipback()', function()
do
local t = {4, 3, 2, 1}
eq(t, vim.iter(t):skipback(0):totable())
eq({4, 3, 2}, vim.iter(t):skipback(1):totable())
eq({4, 3}, vim.iter(t):skipback(2):totable())
eq({4}, vim.iter(t):skipback(#t - 1):totable())
eq({}, vim.iter(t):skipback(#t):totable())
eq({}, vim.iter(t):skipback(#t + 1):totable())
end
local it = vim.iter(vim.gsplit('a|b|c|d', '|'))
matches('skipback%(%) requires a list%-like table', pcall_err(it.skipback, it, 0))
end)
it('slice()', function()
local t = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
eq({3, 4, 5, 6, 7}, vim.iter(t):slice(3, 7):totable())
eq({}, vim.iter(t):slice(6, 5):totable())
eq({}, vim.iter(t):slice(0, 0):totable())
eq({1}, vim.iter(t):slice(1, 1):totable())
eq({1, 2}, vim.iter(t):slice(1, 2):totable())
eq({10}, vim.iter(t):slice(10, 10):totable())
eq({8, 9, 10}, vim.iter(t):slice(8, 11):totable())
end)
it('nth()', function()
do
local t = {4, 3, 2, 1}
eq(nil, vim.iter(t):nth(0))
eq(4, vim.iter(t):nth(1))
eq(3, vim.iter(t):nth(2))
eq(2, vim.iter(t):nth(3))
eq(1, vim.iter(t):nth(4))
eq(nil, vim.iter(t):nth(5))
end
do
local function nth(n)
return vim.iter(vim.gsplit('a|b|c|d', '|')):nth(n)
end
eq(nil, nth(0))
eq('a', nth(1))
eq('b', nth(2))
eq('c', nth(3))
eq('d', nth(4))
eq(nil, nth(5))
end
end)
it('nthback()', function()
do
local t = {4, 3, 2, 1}
eq(nil, vim.iter(t):nthback(0))
eq(1, vim.iter(t):nthback(1))
eq(2, vim.iter(t):nthback(2))
eq(3, vim.iter(t):nthback(3))
eq(4, vim.iter(t):nthback(4))
eq(nil, vim.iter(t):nthback(5))
end
local it = vim.iter(vim.gsplit('a|b|c|d', '|'))
matches('skipback%(%) requires a list%-like table', pcall_err(it.nthback, it, 1))
end)
it('any()', function()
local function odd(v)
return v % 2 ~= 0
end
do
local t = { 4, 8, 9, 10 }
eq(true, vim.iter(t):any(odd))
end
do
local t = { 4, 8, 10 }
eq(false, vim.iter(t):any(odd))
end
do
eq(true, vim.iter(vim.gsplit('a|b|c|d', '|')):any(function(s) return s == 'd' end))
eq(false, vim.iter(vim.gsplit('a|b|c|d', '|')):any(function(s) return s == 'e' end))
end
end)
it('all()', function()
local function odd(v)
return v % 2 ~= 0
end
do
local t = { 3, 5, 7, 9 }
eq(true, vim.iter(t):all(odd))
end
do
local t = { 3, 5, 7, 10 }
eq(false, vim.iter(t):all(odd))
end
do
eq(true, vim.iter(vim.gsplit('a|a|a|a', '|')):all(function(s) return s == 'a' end))
eq(false, vim.iter(vim.gsplit('a|a|a|b', '|')):all(function(s) return s == 'a' end))
end
end)
it('last()', function()
local s = 'abcdefghijklmnopqrstuvwxyz'
eq('z', vim.iter(vim.split(s, '')):last())
eq('z', vim.iter(vim.gsplit(s, '')):last())
end)
it('enumerate()', function()
local it = vim.iter(vim.gsplit('abc', '')):enumerate()
eq({1, 'a'}, {it:next()})
eq({2, 'b'}, {it:next()})
eq({3, 'c'}, {it:next()})
eq({}, {it:next()})
end)
it('peek()', function()
do
local it = vim.iter({ 3, 6, 9, 12 })
eq(3, it:peek())
eq(3, it:peek())
eq(3, it:next())
end
do
local it = vim.iter(vim.gsplit('hi', ''))
matches('peek%(%) requires a list%-like table', pcall_err(it.peek, it))
end
end)
it('find()', function()
local t = {3, 6, 9, 12}
eq(12, vim.iter(t):find(12))
eq(nil, vim.iter(t):find(15))
eq(12, vim.iter(t):find(function(v) return v % 4 == 0 end))
do
local it = vim.iter(t)
local pred = function(v) return v % 3 == 0 end
eq(3, it:find(pred))
eq(6, it:find(pred))
eq(9, it:find(pred))
eq(12, it:find(pred))
eq(nil, it:find(pred))
end
do
local it = vim.iter(vim.gsplit('AbCdE', ''))
local pred = function(s) return s:match('[A-Z]') end
eq('A', it:find(pred))
eq('C', it:find(pred))
eq('E', it:find(pred))
eq(nil, it:find(pred))
end
end)
it('rfind()', function()
local t = {1, 2, 3, 2, 1}
do
local it = vim.iter(t)
eq(1, it:rfind(1))
eq(1, it:rfind(1))
eq(nil, it:rfind(1))
end
do
local it = vim.iter(t):enumerate()
local pred = function(i) return i % 2 ~= 0 end
eq({5, 1}, {it:rfind(pred)})
eq({3, 3}, {it:rfind(pred)})
eq({1, 1}, {it:rfind(pred)})
eq(nil, it:rfind(pred))
end
do
local it = vim.iter(vim.gsplit('AbCdE', ''))
matches('rfind%(%) requires a list%-like table', pcall_err(it.rfind, it, 'E'))
end
end)
it('nextback()', function()
do
local it = vim.iter({ 1, 2, 3, 4 })
eq(4, it:nextback())
eq(3, it:nextback())
eq(2, it:nextback())
eq(1, it:nextback())
eq(nil, it:nextback())
eq(nil, it:nextback())
end
do
local it = vim.iter(vim.gsplit('hi', ''))
matches('nextback%(%) requires a list%-like table', pcall_err(it.nextback, it))
end
end)
it('peekback()', function()
do
local it = vim.iter({ 1, 2, 3, 4 })
eq(4, it:peekback())
eq(4, it:peekback())
eq(4, it:nextback())
end
do
local it = vim.iter(vim.gsplit('hi', ''))
matches('peekback%(%) requires a list%-like table', pcall_err(it.peekback, it))
end
end)
it('fold()', function()
local t = {1, 2, 3, 4, 5}
eq(115, vim.iter(t):fold(100, function(acc, v) return acc + v end))
eq({5, 4, 3, 2, 1}, vim.iter(t):fold({}, function(acc, v)
table.insert(acc, 1, v)
return acc
end))
end)
it('handles map-like tables', function()
local it = vim.iter({ a = 1, b = 2, c = 3 }):map(function(k, v)
if v % 2 ~= 0 then
return k:upper(), v * 2
end
end)
local t = it:fold({}, function(t, k, v)
t[k] = v
return t
end)
eq({ A = 2, C = 6 }, t)
end)
it('handles table values mid-pipeline', function()
local map = {
item = {
file = 'test',
},
item_2 = {
file = 'test',
},
item_3 = {
file = 'test',
},
}
local output = vim.iter(map):map(function(key, value)
return { [key] = value.file }
end):totable()
table.sort(output, function(a, b)
return next(a) < next(b)
end)
eq({
{ item = 'test' },
{ item_2 = 'test' },
{ item_3 = 'test' },
}, output)
end)
it('handles nil values', function()
local t = {1, 2, 3, 4, 5}
do
local it = vim.iter(t):enumerate():map(function(i, v)
if i % 2 == 0 then
return nil, v*v
end
return v, nil
end)
eq({
{ [1] = 1 },
{ [2] = 4 },
{ [1] = 3 },
{ [2] = 16 },
{ [1] = 5 },
}, it:totable())
end
do
local it = vim.iter(ipairs(t)):map(function(i, v)
if i % 2 == 0 then
return nil, v*v
end
return v, nil
end)
eq({
{ [1] = 1 },
{ [2] = 4 },
{ [1] = 3 },
{ [2] = 16 },
{ [1] = 5 },
}, it:totable())
end
end)
end)

View File

@ -3031,427 +3031,6 @@ describe('lua stdlib', function()
eq(false, if_nil(d, c)) eq(false, if_nil(d, c))
eq(NIL, if_nil(a)) eq(NIL, if_nil(a))
end) end)
describe('vim.iter', function()
it('filter()', function()
local function odd(v)
return v % 2 ~= 0
end
local t = { 1, 2, 3, 4, 5 }
eq({ 1, 3, 5 }, vim.iter(t):filter(odd):totable())
eq({ 2, 4 }, vim.iter(t):filter(function(v) return not odd(v) end):totable())
eq({}, vim.iter(t):filter(function(v) if v > 5 then return v end end):totable())
do
local it = vim.iter(ipairs(t))
it:filter(function(i, v) return i > 1 and v < 5 end)
it:map(function(_, v) return v * 2 end)
eq({ 4, 6, 8 }, it:totable())
end
local it = vim.iter(string.gmatch('the quick brown fox', '%w+'))
eq({'the', 'fox'}, it:filter(function(s) return #s <= 3 end):totable())
end)
it('map()', function()
local t = { 1, 2, 3, 4, 5 }
eq(
{ 2, 4, 6, 8, 10 },
vim
.iter(t)
:map(function(v)
return 2 * v
end)
:totable()
)
local it = vim.gsplit(
[[
Line 1
Line 2
Line 3
Line 4
]],
'\n'
)
eq(
{ 'Lion 2', 'Lion 4' },
vim
.iter(it)
:map(function(s)
local lnum = s:match('(%d+)')
if lnum and tonumber(lnum) % 2 == 0 then
return vim.trim(s:gsub('Line', 'Lion'))
end
end)
:totable()
)
end)
it('for loops', function()
local t = {1, 2, 3, 4, 5}
local acc = 0
for v in vim.iter(t):map(function(v) return v * 3 end) do
acc = acc + v
end
eq(45, acc)
end)
it('totable()', function()
do
local it = vim.iter({1, 2, 3}):map(function(v) return v, v*v end)
eq({{1, 1}, {2, 4}, {3, 9}}, it:totable())
end
do
local it = vim.iter(string.gmatch('1,4,lol,17,blah,2,9,3', '%d+')):map(tonumber)
eq({1, 4, 17, 2, 9, 3}, it:totable())
end
end)
it('next()', function()
local it = vim.iter({1, 2, 3}):map(function(v) return 2 * v end)
eq(2, it:next())
eq(4, it:next())
eq(6, it:next())
eq(nil, it:next())
end)
it('rev()', function()
eq({3, 2, 1}, vim.iter({1, 2, 3}):rev():totable())
local it = vim.iter(string.gmatch("abc", "%w"))
matches('rev%(%) requires a list%-like table', pcall_err(it.rev, it))
end)
it('skip()', function()
do
local t = {4, 3, 2, 1}
eq(t, vim.iter(t):skip(0):totable())
eq({3, 2, 1}, vim.iter(t):skip(1):totable())
eq({2, 1}, vim.iter(t):skip(2):totable())
eq({1}, vim.iter(t):skip(#t - 1):totable())
eq({}, vim.iter(t):skip(#t):totable())
eq({}, vim.iter(t):skip(#t + 1):totable())
end
do
local function skip(n)
return vim.iter(vim.gsplit('a|b|c|d', '|')):skip(n):totable()
end
eq({'a', 'b', 'c', 'd'}, skip(0))
eq({'b', 'c', 'd'}, skip(1))
eq({'c', 'd'}, skip(2))
eq({'d'}, skip(3))
eq({}, skip(4))
eq({}, skip(5))
end
end)
it('skipback()', function()
do
local t = {4, 3, 2, 1}
eq(t, vim.iter(t):skipback(0):totable())
eq({4, 3, 2}, vim.iter(t):skipback(1):totable())
eq({4, 3}, vim.iter(t):skipback(2):totable())
eq({4}, vim.iter(t):skipback(#t - 1):totable())
eq({}, vim.iter(t):skipback(#t):totable())
eq({}, vim.iter(t):skipback(#t + 1):totable())
end
local it = vim.iter(vim.gsplit('a|b|c|d', '|'))
matches('skipback%(%) requires a list%-like table', pcall_err(it.skipback, it, 0))
end)
it('slice()', function()
local t = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
eq({3, 4, 5, 6, 7}, vim.iter(t):slice(3, 7):totable())
eq({}, vim.iter(t):slice(6, 5):totable())
eq({}, vim.iter(t):slice(0, 0):totable())
eq({1}, vim.iter(t):slice(1, 1):totable())
eq({1, 2}, vim.iter(t):slice(1, 2):totable())
eq({10}, vim.iter(t):slice(10, 10):totable())
eq({8, 9, 10}, vim.iter(t):slice(8, 11):totable())
end)
it('nth()', function()
do
local t = {4, 3, 2, 1}
eq(nil, vim.iter(t):nth(0))
eq(4, vim.iter(t):nth(1))
eq(3, vim.iter(t):nth(2))
eq(2, vim.iter(t):nth(3))
eq(1, vim.iter(t):nth(4))
eq(nil, vim.iter(t):nth(5))
end
do
local function nth(n)
return vim.iter(vim.gsplit('a|b|c|d', '|')):nth(n)
end
eq(nil, nth(0))
eq('a', nth(1))
eq('b', nth(2))
eq('c', nth(3))
eq('d', nth(4))
eq(nil, nth(5))
end
end)
it('nthback()', function()
do
local t = {4, 3, 2, 1}
eq(nil, vim.iter(t):nthback(0))
eq(1, vim.iter(t):nthback(1))
eq(2, vim.iter(t):nthback(2))
eq(3, vim.iter(t):nthback(3))
eq(4, vim.iter(t):nthback(4))
eq(nil, vim.iter(t):nthback(5))
end
local it = vim.iter(vim.gsplit('a|b|c|d', '|'))
matches('skipback%(%) requires a list%-like table', pcall_err(it.nthback, it, 1))
end)
it('any()', function()
local function odd(v)
return v % 2 ~= 0
end
do
local t = { 4, 8, 9, 10 }
eq(true, vim.iter(t):any(odd))
end
do
local t = { 4, 8, 10 }
eq(false, vim.iter(t):any(odd))
end
do
eq(true, vim.iter(vim.gsplit('a|b|c|d', '|')):any(function(s) return s == 'd' end))
eq(false, vim.iter(vim.gsplit('a|b|c|d', '|')):any(function(s) return s == 'e' end))
end
end)
it('all()', function()
local function odd(v)
return v % 2 ~= 0
end
do
local t = { 3, 5, 7, 9 }
eq(true, vim.iter(t):all(odd))
end
do
local t = { 3, 5, 7, 10 }
eq(false, vim.iter(t):all(odd))
end
do
eq(true, vim.iter(vim.gsplit('a|a|a|a', '|')):all(function(s) return s == 'a' end))
eq(false, vim.iter(vim.gsplit('a|a|a|b', '|')):all(function(s) return s == 'a' end))
end
end)
it('last()', function()
local s = 'abcdefghijklmnopqrstuvwxyz'
eq('z', vim.iter(vim.split(s, '')):last())
eq('z', vim.iter(vim.gsplit(s, '')):last())
end)
it('enumerate()', function()
local it = vim.iter(vim.gsplit('abc', '')):enumerate()
eq({1, 'a'}, {it:next()})
eq({2, 'b'}, {it:next()})
eq({3, 'c'}, {it:next()})
eq({}, {it:next()})
end)
it('peek()', function()
do
local it = vim.iter({ 3, 6, 9, 12 })
eq(3, it:peek())
eq(3, it:peek())
eq(3, it:next())
end
do
local it = vim.iter(vim.gsplit('hi', ''))
matches('peek%(%) requires a list%-like table', pcall_err(it.peek, it))
end
end)
it('find()', function()
local t = {3, 6, 9, 12}
eq(12, vim.iter(t):find(12))
eq(nil, vim.iter(t):find(15))
eq(12, vim.iter(t):find(function(v) return v % 4 == 0 end))
do
local it = vim.iter(t)
local pred = function(v) return v % 3 == 0 end
eq(3, it:find(pred))
eq(6, it:find(pred))
eq(9, it:find(pred))
eq(12, it:find(pred))
eq(nil, it:find(pred))
end
do
local it = vim.iter(vim.gsplit('AbCdE', ''))
local pred = function(s) return s:match('[A-Z]') end
eq('A', it:find(pred))
eq('C', it:find(pred))
eq('E', it:find(pred))
eq(nil, it:find(pred))
end
end)
it('rfind()', function()
local t = {1, 2, 3, 2, 1}
do
local it = vim.iter(t)
eq(1, it:rfind(1))
eq(1, it:rfind(1))
eq(nil, it:rfind(1))
end
do
local it = vim.iter(t):enumerate()
local pred = function(i) return i % 2 ~= 0 end
eq({5, 1}, {it:rfind(pred)})
eq({3, 3}, {it:rfind(pred)})
eq({1, 1}, {it:rfind(pred)})
eq(nil, it:rfind(pred))
end
do
local it = vim.iter(vim.gsplit('AbCdE', ''))
matches('rfind%(%) requires a list%-like table', pcall_err(it.rfind, it, 'E'))
end
end)
it('nextback()', function()
do
local it = vim.iter({ 1, 2, 3, 4 })
eq(4, it:nextback())
eq(3, it:nextback())
eq(2, it:nextback())
eq(1, it:nextback())
eq(nil, it:nextback())
eq(nil, it:nextback())
end
do
local it = vim.iter(vim.gsplit('hi', ''))
matches('nextback%(%) requires a list%-like table', pcall_err(it.nextback, it))
end
end)
it('peekback()', function()
do
local it = vim.iter({ 1, 2, 3, 4 })
eq(4, it:peekback())
eq(4, it:peekback())
eq(4, it:peekback())
end
do
local it = vim.iter(vim.gsplit('hi', ''))
matches('peekback%(%) requires a list%-like table', pcall_err(it.peekback, it))
end
end)
it('fold()', function()
local t = {1, 2, 3, 4, 5}
eq(115, vim.iter(t):fold(100, function(acc, v) return acc + v end))
eq({5, 4, 3, 2, 1}, vim.iter(t):fold({}, function(acc, v)
table.insert(acc, 1, v)
return acc
end))
end)
it('handles map-like tables', function()
local it = vim.iter({ a = 1, b = 2, c = 3 }):map(function(k, v)
if v % 2 ~= 0 then
return k:upper(), v * 2
end
end)
local t = it:fold({}, function(t, k, v)
t[k] = v
return t
end)
eq({ A = 2, C = 6 }, t)
end)
it('handles table values mid-pipeline', function()
local map = {
item = {
file = 'test',
},
item_2 = {
file = 'test',
},
item_3 = {
file = 'test',
},
}
local output = vim.iter(map):map(function(key, value)
return { [key] = value.file }
end):totable()
table.sort(output, function(a, b)
return next(a) < next(b)
end)
eq({
{ item = 'test' },
{ item_2 = 'test' },
{ item_3 = 'test' },
}, output)
end)
it('handles nil values', function()
local t = {1, 2, 3, 4, 5}
do
local it = vim.iter(t):enumerate():map(function(i, v)
if i % 2 == 0 then
return nil, v*v
end
return v, nil
end)
eq({
{ [1] = 1 },
{ [2] = 4 },
{ [1] = 3 },
{ [2] = 16 },
{ [1] = 5 },
}, it:totable())
end
do
local it = vim.iter(ipairs(t)):map(function(i, v)
if i % 2 == 0 then
return nil, v*v
end
return v, nil
end)
eq({
{ [1] = 1 },
{ [2] = 4 },
{ [1] = 3 },
{ [2] = 16 },
{ [1] = 5 },
}, it:totable())
end
end)
end)
end) end)
describe('lua: builtin modules', function() describe('lua: builtin modules', function()