123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089 |
- --- @brief
- ---
- --- [vim.iter()]() is an interface for [iterable]s: it wraps a 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 to create iterator "pipelines": the output
- --- of each pipeline stage is input to the next stage. The first stage depends on the type passed to
- --- `vim.iter()`:
- ---
- --- - Lists or arrays (|lua-list|) yield only the value of each element.
- --- - Holes (nil values) are allowed (but discarded).
- --- - Use pairs() to treat array/list tables as dicts (preserve holes and non-contiguous integer
- --- keys): `vim.iter(pairs(…))`.
- --- - Use |Iter:enumerate()| to also pass the index to the next stage.
- --- - Or initialize with ipairs(): `vim.iter(ipairs(…))`.
- --- - Non-list tables (|lua-dict|) yield both the key and value of each element.
- --- - Function |iterator|s yield all values returned by the underlying function.
- --- - Tables with a |__call()| metamethod are treated as function iterators.
- ---
- --- The iterator pipeline terminates when the underlying |iterable| is exhausted (for function
- --- iterators this means it returned nil).
- ---
- --- Note: `vim.iter()` scans table input to decide if it is a list or a dict; to avoid this cost you
- --- can wrap the table with an iterator e.g. `vim.iter(ipairs({…}))`, but that precludes the use of
- --- |list-iterator| operations such as |Iter:rev()|).
- ---
- --- 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 }
- ---
- --- -- ipairs() is a function iterator which returns both the index (i) and the value (v)
- --- 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
- ---
- --- local rb = vim.ringbuf(3)
- --- rb:push("a")
- --- rb:push("b")
- --- vim.iter(rb):totable()
- --- -- { "a", "b" }
- --- ```
- --- LuaLS is bad at generics which this module mostly deals with
- --- @diagnostic disable:no-unknown
- ---@nodoc
- ---@class IterMod
- ---@operator call:Iter
- local M = {}
- ---@nodoc
- ---@class Iter
- local Iter = {}
- Iter.__index = Iter
- Iter.__call = function(self)
- return self:next()
- end
- --- Special case implementations for iterators on list tables.
- ---@nodoc
- ---@class ArrayIter : Iter
- ---@field _table table Underlying table data
- ---@field _head number Index to the front of a table iterator
- ---@field _tail number Index to the end of a table iterator (exclusive)
- local ArrayIter = {}
- ArrayIter.__index = setmetatable(ArrayIter, Iter)
- ArrayIter.__call = function(self)
- return self:next()
- end
- --- Packed tables use this as their metatable
- local packedmt = {}
- local function unpack(t)
- if type(t) == 'table' and getmetatable(t) == packedmt then
- return _G.unpack(t, 1, t.n)
- end
- return t
- end
- local function pack(...)
- local n = select('#', ...)
- if n > 1 then
- return setmetatable({ n = n, ... }, packedmt)
- end
- return ...
- end
- local function sanitize(t)
- if type(t) == 'table' and getmetatable(t) == packedmt then
- -- Remove length tag and metatable
- t.n = nil
- setmetatable(t, nil)
- end
- return t
- end
- --- Flattens a single array-like table. Errors if it attempts to flatten a
- --- dict-like table
- ---@param t table table which should be flattened
- ---@param max_depth number depth to which the table should be flattened
- ---@param depth number current iteration depth
- ---@param result table output table that contains flattened result
- ---@return table|nil flattened table if it can be flattened, otherwise nil
- local function flatten(t, max_depth, depth, result)
- if depth < max_depth and type(t) == 'table' then
- for k, v in pairs(t) do
- if type(k) ~= 'number' or k <= 0 or math.floor(k) ~= k then
- -- short-circuit: this is not a list like table
- return nil
- end
- if flatten(v, max_depth, depth + 1, result) == nil then
- return nil
- end
- end
- elseif t ~= nil then
- result[#result + 1] = t
- end
- return result
- end
- --- Determine if the current iterator stage should continue.
- ---
- --- If any arguments are passed to this function, then return those arguments
- --- and stop the current iterator stage. Otherwise, return true to signal that
- --- the current stage should continue.
- ---
- ---@param ... any Function arguments.
- ---@return boolean True if the iterator stage should continue, false otherwise
- ---@return any Function arguments.
- local function continue(...)
- if select(1, ...) ~= nil then
- return false, ...
- end
- return true
- end
- --- If no input arguments are given return false, indicating the current
- --- iterator stage should stop. Otherwise, apply the arguments to the function
- --- f. If that function returns no values, the current iterator stage continues.
- --- Otherwise, those values are returned.
- ---
- ---@param f function Function to call with the given arguments
- ---@param ... any Arguments to apply to f
- ---@return boolean True if the iterator pipeline should continue, false otherwise
- ---@return any Return values of f
- local function apply(f, ...)
- if select(1, ...) ~= nil then
- return continue(f(...))
- end
- return false
- end
- --- Filters an iterator pipeline.
- ---
- --- Example:
- ---
- --- ```lua
- --- local bufs = vim.iter(vim.api.nvim_list_bufs()):filter(vim.api.nvim_buf_is_loaded)
- --- ```
- ---
- ---@param f fun(...):boolean Takes all values returned from the previous stage
- --- in the pipeline and returns false or nil if the
- --- current iterator element should be removed.
- ---@return Iter
- function Iter:filter(f)
- return self:map(function(...)
- if f(...) then
- return ...
- end
- end)
- end
- ---@private
- function ArrayIter:filter(f)
- local inc = self._head < self._tail and 1 or -1
- local n = self._head
- for i = self._head, self._tail - inc, inc do
- local v = self._table[i]
- if f(unpack(v)) then
- self._table[n] = v
- n = n + inc
- end
- end
- self._tail = n
- return self
- end
- --- Flattens a |list-iterator|, un-nesting nested values up to the given {depth}.
- --- Errors if it attempts to flatten a dict-like value.
- ---
- --- Examples:
- ---
- --- ```lua
- --- vim.iter({ 1, { 2 }, { { 3 } } }):flatten():totable()
- --- -- { 1, 2, { 3 } }
- ---
- --- vim.iter({1, { { a = 2 } }, { 3 } }):flatten():totable()
- --- -- { 1, { a = 2 }, 3 }
- ---
- --- vim.iter({ 1, { { a = 2 } }, { 3 } }):flatten(math.huge):totable()
- --- -- error: attempt to flatten a dict-like table
- --- ```
- ---
- ---@param depth? number Depth to which |list-iterator| should be flattened
- --- (defaults to 1)
- ---@return Iter
- ---@diagnostic disable-next-line:unused-local
- function Iter:flatten(depth) -- luacheck: no unused args
- error('flatten() requires an array-like table')
- end
- ---@private
- function ArrayIter:flatten(depth)
- depth = depth or 1
- local inc = self._head < self._tail and 1 or -1
- local target = {}
- for i = self._head, self._tail - inc, inc do
- local flattened = flatten(self._table[i], depth, 0, {})
- -- exit early if we try to flatten a dict-like table
- if flattened == nil then
- error('flatten() requires an array-like table')
- end
- for _, v in pairs(flattened) do
- target[#target + 1] = v
- end
- end
- self._head = 1
- self._tail = #target + 1
- self._table = target
- return self
- end
- --- Maps the items of an iterator pipeline to the values returned by `f`.
- ---
- --- If the map function returns nil, the value is filtered from the iterator.
- ---
- --- Example:
- ---
- --- ```lua
- --- local it = vim.iter({ 1, 2, 3, 4 }):map(function(v)
- --- if v % 2 == 0 then
- --- return v * 3
- --- end
- --- end)
- --- it:totable()
- --- -- { 6, 12 }
- --- ```
- ---
- ---@param f fun(...):...:any Mapping function. Takes all values returned from
- --- the previous stage in the pipeline as arguments
- --- and returns one or more new values, which are used
- --- in the next pipeline stage. Nil return values
- --- are filtered from the output.
- ---@return Iter
- function Iter:map(f)
- -- Implementation note: the reader may be forgiven for observing that this
- -- function appears excessively convoluted. The problem to solve is that each
- -- stage of the iterator pipeline can return any number of values, and the
- -- number of values could even change per iteration. And the return values
- -- must be checked to determine if the pipeline has ended, so we cannot
- -- naively forward them along to the next stage.
- --
- -- A simple approach is to pack all of the return values into a table, check
- -- for nil, then unpack the table for the next stage. However, packing and
- -- unpacking tables is quite slow. There is no other way in Lua to handle an
- -- unknown number of function return values than to simply forward those
- -- values along to another function. Hence the intricate function passing you
- -- see here.
- local next = self.next
- --- Drain values from the upstream iterator source until a value can be
- --- returned.
- ---
- --- This is a recursive function. The base case is when the first argument is
- --- false, which indicates that the rest of the arguments should be returned
- --- as the values for the current iteration stage.
- ---
- ---@param cont boolean If true, the current iterator stage should continue to
- --- pull values from its upstream pipeline stage.
- --- Otherwise, this stage is complete and returns the
- --- values passed.
- ---@param ... any Values to return if cont is false.
- ---@return any
- local function fn(cont, ...)
- if cont then
- return fn(apply(f, next(self)))
- end
- return ...
- end
- self.next = function()
- return fn(apply(f, next(self)))
- end
- return self
- end
- ---@private
- function ArrayIter:map(f)
- local inc = self._head < self._tail and 1 or -1
- local n = self._head
- for i = self._head, self._tail - inc, inc do
- local v = pack(f(unpack(self._table[i])))
- if v ~= nil then
- self._table[n] = v
- n = n + inc
- end
- end
- self._tail = n
- return self
- end
- --- Calls a function once for each item in the pipeline, draining the iterator.
- ---
- --- For functions with side effects. To modify the values in the iterator, use |Iter:map()|.
- ---
- ---@param f fun(...) Function to execute for each item in the pipeline.
- --- Takes all of the values returned by the previous stage
- --- in the pipeline as arguments.
- function Iter:each(f)
- local function fn(...)
- if select(1, ...) ~= nil then
- f(...)
- return true
- end
- end
- while fn(self:next()) do
- end
- end
- ---@private
- function ArrayIter:each(f)
- local inc = self._head < self._tail and 1 or -1
- for i = self._head, self._tail - inc, inc do
- f(unpack(self._table[i]))
- end
- self._head = self._tail
- end
- --- Collect the iterator into a table.
- ---
- --- The resulting table depends on the initial source in the iterator pipeline.
- --- Array-like tables and function iterators will be collected into an array-like
- --- table. If multiple values are returned from the final stage in the iterator
- --- pipeline, each value will be included in a table.
- ---
- --- Examples:
- ---
- --- ```lua
- --- vim.iter(string.gmatch('100 20 50', '%d+')):map(tonumber):totable()
- --- -- { 100, 20, 50 }
- ---
- --- vim.iter({ 1, 2, 3 }):map(function(v) return v, 2 * v end):totable()
- --- -- { { 1, 2 }, { 2, 4 }, { 3, 6 } }
- ---
- --- vim.iter({ a = 1, b = 2, c = 3 }):filter(function(k, v) return v % 2 ~= 0 end):totable()
- --- -- { { 'a', 1 }, { 'c', 3 } }
- --- ```
- ---
- --- The generated table is an array-like table with consecutive, numeric indices.
- --- To create a map-like table with arbitrary keys, use |Iter:fold()|.
- ---
- ---
- ---@return table
- function Iter:totable()
- local t = {}
- while true do
- local args = pack(self:next())
- if args == nil then
- break
- end
- t[#t + 1] = sanitize(args)
- end
- return t
- end
- ---@private
- function ArrayIter:totable()
- if self.next ~= ArrayIter.next or self._head >= self._tail then
- return Iter.totable(self)
- end
- local needs_sanitize = getmetatable(self._table[self._head]) == packedmt
- -- Reindex and sanitize.
- local len = self._tail - self._head
- if needs_sanitize then
- for i = 1, len do
- self._table[i] = sanitize(self._table[self._head - 1 + i])
- end
- else
- for i = 1, len do
- self._table[i] = self._table[self._head - 1 + i]
- end
- end
- for i = len + 1, table.maxn(self._table) do
- self._table[i] = nil
- end
- self._head = 1
- self._tail = len + 1
- return self._table
- end
- --- Collect the iterator into a delimited string.
- ---
- --- Each element in the iterator is joined into a string separated by {delim}.
- ---
- --- Consumes the iterator.
- ---
- --- @param delim string Delimiter
- --- @return string
- function Iter:join(delim)
- return table.concat(self:totable(), delim)
- end
- --- Folds ("reduces") an iterator into a single value. [Iter:reduce()]()
- ---
- --- Examples:
- ---
- --- ```lua
- --- -- Create a new table with only even values
- --- vim.iter({ a = 1, b = 2, c = 3, d = 4 })
- --- :filter(function(k, v) return v % 2 == 0 end)
- --- :fold({}, function(acc, k, v)
- --- acc[k] = v
- --- return acc
- --- end) --> { b = 2, d = 4 }
- ---
- --- -- Get the "maximum" item of an iterable.
- --- vim.iter({ -99, -4, 3, 42, 0, 0, 7 })
- --- :fold({}, function(acc, v)
- --- acc.max = math.max(v, acc.max or v)
- --- return acc
- --- end) --> { max = 42 }
- --- ```
- ---
- ---@generic A
- ---
- ---@param init A Initial value of the accumulator.
- ---@param f fun(acc:A, ...):A Accumulation function.
- ---@return A
- function Iter:fold(init, f)
- local acc = init
- --- Use a closure to handle var args returned from iterator
- local function fn(...)
- if select(1, ...) ~= nil then
- acc = f(acc, ...)
- return true
- end
- end
- while fn(self:next()) do
- end
- return acc
- end
- ---@private
- function ArrayIter:fold(init, f)
- local acc = init
- local inc = self._head < self._tail and 1 or -1
- for i = self._head, self._tail - inc, inc do
- acc = f(acc, unpack(self._table[i]))
- end
- return acc
- end
- --- Gets the next value from the iterator.
- ---
- --- Example:
- ---
- --- ```lua
- ---
- --- local it = vim.iter(string.gmatch('1 2 3', '%d+')):map(tonumber)
- --- it:next()
- --- -- 1
- --- it:next()
- --- -- 2
- --- it:next()
- --- -- 3
- ---
- --- ```
- ---
- ---@return any
- function Iter:next()
- -- This function is provided by the source iterator in Iter.new. This definition exists only for
- -- the docstring
- end
- ---@private
- function ArrayIter:next()
- if self._head ~= self._tail then
- local v = self._table[self._head]
- local inc = self._head < self._tail and 1 or -1
- self._head = self._head + inc
- return unpack(v)
- end
- end
- --- Reverses a |list-iterator| pipeline.
- ---
- --- Example:
- ---
- --- ```lua
- ---
- --- local it = vim.iter({ 3, 6, 9, 12 }):rev()
- --- it:totable()
- --- -- { 12, 9, 6, 3 }
- ---
- --- ```
- ---
- ---@return Iter
- function Iter:rev()
- error('rev() requires an array-like table')
- end
- ---@private
- function ArrayIter:rev()
- local inc = self._head < self._tail and 1 or -1
- self._head, self._tail = self._tail - inc, self._head - inc
- return self
- end
- --- Gets the next value in a |list-iterator| without consuming it.
- ---
- --- Example:
- ---
- --- ```lua
- ---
- --- local it = vim.iter({ 3, 6, 9, 12 })
- --- it:peek()
- --- -- 3
- --- it:peek()
- --- -- 3
- --- it:next()
- --- -- 3
- ---
- --- ```
- ---
- ---@return any
- function Iter:peek()
- error('peek() requires an array-like table')
- end
- ---@private
- function ArrayIter:peek()
- if self._head ~= self._tail then
- return self._table[self._head]
- end
- end
- --- Find the first value in the iterator that satisfies the given predicate.
- ---
- --- Advances the iterator. Returns nil and drains the iterator if no value is found.
- ---
- --- Examples:
- ---
- --- ```lua
- ---
- --- local it = vim.iter({ 3, 6, 9, 12 })
- --- it:find(12)
- --- -- 12
- ---
- --- local it = vim.iter({ 3, 6, 9, 12 })
- --- it:find(20)
- --- -- nil
- ---
- --- local it = vim.iter({ 3, 6, 9, 12 })
- --- it:find(function(v) return v % 4 == 0 end)
- --- -- 12
- ---
- --- ```
- ---@param f any
- ---@return any
- function Iter:find(f)
- if type(f) ~= 'function' then
- local val = f
- f = function(v)
- return v == val
- end
- end
- local result = nil
- --- Use a closure to handle var args returned from iterator
- local function fn(...)
- if select(1, ...) ~= nil then
- if f(...) then
- result = pack(...)
- else
- return true
- end
- end
- end
- while fn(self:next()) do
- end
- return unpack(result)
- end
- --- Gets the first value satisfying a predicate, from the end of a |list-iterator|.
- ---
- --- Advances the iterator. Returns nil and drains the iterator if no value is found.
- ---
- --- Examples:
- ---
- --- ```lua
- ---
- --- local it = vim.iter({ 1, 2, 3, 2, 1 }):enumerate()
- --- it:rfind(1)
- --- -- 5 1
- --- it:rfind(1)
- --- -- 1 1
- ---
- --- ```
- ---
- ---@see Iter.find
- ---
- ---@param f any
- ---@return any
- ---@diagnostic disable-next-line: unused-local
- function Iter:rfind(f) -- luacheck: no unused args
- error('rfind() requires an array-like table')
- end
- ---@private
- function ArrayIter:rfind(f)
- if type(f) ~= 'function' then
- local val = f
- f = function(v)
- return v == val
- end
- end
- local inc = self._head < self._tail and 1 or -1
- for i = self._tail - inc, self._head, -inc do
- local v = self._table[i]
- if f(unpack(v)) then
- self._tail = i
- return unpack(v)
- end
- end
- self._head = self._tail
- end
- --- Transforms an iterator to yield only the first n values.
- ---
- --- Example:
- ---
- --- ```lua
- --- local it = vim.iter({ 1, 2, 3, 4 }):take(2)
- --- it:next()
- --- -- 1
- --- it:next()
- --- -- 2
- --- it:next()
- --- -- nil
- --- ```
- ---
- ---@param n integer
- ---@return Iter
- function Iter:take(n)
- local next = self.next
- local i = 0
- self.next = function()
- if i < n then
- i = i + 1
- return next(self)
- end
- end
- return self
- end
- ---@private
- function ArrayIter:take(n)
- local inc = self._head < self._tail and n or -n
- local cmp = self._head < self._tail and math.min or math.max
- self._tail = cmp(self._tail, self._head + inc)
- return self
- end
- --- "Pops" a value from a |list-iterator| (gets the last value and decrements the tail).
- ---
- --- Example:
- ---
- --- ```lua
- --- local it = vim.iter({1, 2, 3, 4})
- --- it:pop()
- --- -- 4
- --- it:pop()
- --- -- 3
- --- ```
- ---
- ---@return any
- function Iter:pop()
- error('pop() requires an array-like table')
- end
- --- @nodoc
- function ArrayIter:pop()
- if self._head ~= self._tail then
- local inc = self._head < self._tail and 1 or -1
- self._tail = self._tail - inc
- return self._table[self._tail]
- end
- end
- --- Gets the last value of a |list-iterator| without consuming it.
- ---
- --- Example:
- ---
- --- ```lua
- --- local it = vim.iter({1, 2, 3, 4})
- --- it:rpeek()
- --- -- 4
- --- it:rpeek()
- --- -- 4
- --- it:pop()
- --- -- 4
- --- ```
- ---
- ---@see Iter.last
- ---
- ---@return any
- function Iter:rpeek()
- error('rpeek() requires an array-like table')
- end
- ---@nodoc
- function ArrayIter:rpeek()
- if self._head ~= self._tail then
- local inc = self._head < self._tail and 1 or -1
- return self._table[self._tail - inc]
- end
- end
- --- Skips `n` values of an iterator pipeline.
- ---
- --- Example:
- ---
- --- ```lua
- ---
- --- local it = vim.iter({ 3, 6, 9, 12 }):skip(2)
- --- it:next()
- --- -- 9
- ---
- --- ```
- ---
- ---@param n number Number of values to skip.
- ---@return Iter
- function Iter:skip(n)
- for _ = 1, n do
- local _ = self:next()
- end
- return self
- end
- ---@private
- function ArrayIter:skip(n)
- local inc = self._head < self._tail and n or -n
- self._head = self._head + inc
- if (inc > 0 and self._head > self._tail) or (inc < 0 and self._head < self._tail) then
- self._head = self._tail
- end
- return self
- end
- --- Discards `n` values from the end of a |list-iterator| pipeline.
- ---
- --- Example:
- ---
- --- ```lua
- --- local it = vim.iter({ 1, 2, 3, 4, 5 }):rskip(2)
- --- it:next()
- --- -- 1
- --- it:pop()
- --- -- 3
- --- ```
- ---
- ---@param n number Number of values to skip.
- ---@return Iter
- ---@diagnostic disable-next-line: unused-local
- function Iter:rskip(n) -- luacheck: no unused args
- error('rskip() requires an array-like table')
- end
- ---@private
- function ArrayIter:rskip(n)
- local inc = self._head < self._tail and n or -n
- self._tail = self._tail - inc
- if (inc > 0 and self._head > self._tail) or (inc < 0 and self._head < self._tail) then
- self._head = self._tail
- end
- return self
- end
- --- Gets the nth value of an iterator (and advances to it).
- ---
- --- If `n` is negative, offsets from the end of a |list-iterator|.
- ---
- --- Example:
- ---
- --- ```lua
- --- local it = vim.iter({ 3, 6, 9, 12 })
- --- it:nth(2)
- --- -- 6
- --- it:nth(2)
- --- -- 12
- ---
- --- local it2 = vim.iter({ 3, 6, 9, 12 })
- --- it2:nth(-2)
- --- -- 9
- --- it2:nth(-2)
- --- -- 3
- --- ```
- ---
- ---@param n number Index of the value to return. May be negative if the source is a |list-iterator|.
- ---@return any
- function Iter:nth(n)
- if n > 0 then
- return self:skip(n - 1):next()
- elseif n < 0 then
- return self:rskip(math.abs(n) - 1):pop()
- end
- end
- --- Sets the start and end of a |list-iterator| pipeline.
- ---
- --- Equivalent to `:skip(first - 1):rskip(len - last + 1)`.
- ---
- ---@param first number
- ---@param last number
- ---@return Iter
- ---@diagnostic disable-next-line: unused-local
- function Iter:slice(first, last) -- luacheck: no unused args
- error('slice() requires an array-like table')
- end
- ---@private
- function ArrayIter:slice(first, last)
- return self:skip(math.max(0, first - 1)):rskip(math.max(0, self._tail - last - 1))
- end
- --- Returns true if any of the items in the iterator match the given predicate.
- ---
- ---@param pred fun(...):boolean Predicate function. Takes all values returned from the previous
- --- stage in the pipeline as arguments and returns true if the
- --- predicate matches.
- function Iter:any(pred)
- local any = false
- --- Use a closure to handle var args returned from iterator
- local function fn(...)
- if select(1, ...) ~= nil then
- if pred(...) then
- any = true
- else
- return true
- end
- end
- end
- while fn(self:next()) do
- end
- return any
- end
- --- Returns true if all items in the iterator match the given predicate.
- ---
- ---@param pred fun(...):boolean Predicate function. Takes all values returned from the previous
- --- stage in the pipeline as arguments and returns true if the
- --- predicate matches.
- function Iter:all(pred)
- local all = true
- local function fn(...)
- if select(1, ...) ~= nil then
- if not pred(...) then
- all = false
- else
- return true
- end
- end
- end
- while fn(self:next()) do
- end
- return all
- end
- --- Drains the iterator and returns the last item.
- ---
- --- Example:
- ---
- --- ```lua
- ---
- --- local it = vim.iter(vim.gsplit('abcdefg', ''))
- --- it:last()
- --- -- 'g'
- ---
- --- local it = vim.iter({ 3, 6, 9, 12, 15 })
- --- it:last()
- --- -- 15
- ---
- --- ```
- ---
- ---@see Iter.rpeek
- ---
- ---@return any
- function Iter:last()
- local last = self:next()
- local cur = self:next()
- while cur do
- last = cur
- cur = self:next()
- end
- return last
- end
- ---@private
- function ArrayIter:last()
- local inc = self._head < self._tail and 1 or -1
- local v = self._table[self._tail - inc]
- self._head = self._tail
- return v
- end
- --- Yields the item index (count) and value for each item of an iterator pipeline.
- ---
- --- For list tables, this is more efficient:
- ---
- --- ```lua
- --- vim.iter(ipairs(t))
- --- ```
- ---
- --- instead of:
- ---
- --- ```lua
- --- vim.iter(t):enumerate()
- --- ```
- ---
- --- Example:
- ---
- --- ```lua
- ---
- --- local it = vim.iter(vim.gsplit('abc', '')):enumerate()
- --- it:next()
- --- -- 1 'a'
- --- it:next()
- --- -- 2 'b'
- --- it:next()
- --- -- 3 'c'
- ---
- --- ```
- ---
- ---@return Iter
- function Iter:enumerate()
- local i = 0
- return self:map(function(...)
- i = i + 1
- return i, ...
- end)
- end
- ---@private
- function ArrayIter:enumerate()
- local inc = self._head < self._tail and 1 or -1
- for i = self._head, self._tail - inc, inc do
- local v = self._table[i]
- self._table[i] = pack(i, v)
- end
- return self
- end
- --- Creates a new Iter object from a table or other |iterable|.
- ---
- ---@param src table|function Table or iterator to drain values from
- ---@return Iter
- ---@private
- function Iter.new(src, ...)
- local it = {}
- if type(src) == 'table' then
- local mt = getmetatable(src)
- if mt and type(mt.__call) == 'function' then
- ---@private
- function it.next()
- return src()
- end
- setmetatable(it, Iter)
- return it
- end
- local t = {}
- -- O(n): scan the source table to decide if it is an array (only positive integer indices).
- for k, v in pairs(src) do
- if type(k) ~= 'number' or k <= 0 or math.floor(k) ~= k then
- return Iter.new(pairs(src))
- end
- t[#t + 1] = v -- Coerce to list-like table.
- end
- return ArrayIter.new(t)
- end
- if type(src) == 'function' then
- local s, var = ...
- --- Use a closure to handle var args returned from iterator
- local function fn(...)
- -- Per the Lua 5.1 reference manual, an iterator is complete when the first returned value is
- -- nil (even if there are other, non-nil return values). See |for-in|.
- if select(1, ...) ~= nil then
- var = select(1, ...)
- return ...
- end
- end
- ---@private
- function it.next()
- return fn(src(s, var))
- end
- setmetatable(it, Iter)
- else
- error('src must be a table or function')
- end
- return it
- end
- --- Create a new ArrayIter
- ---
- ---@param t table Array-like table. Caller guarantees that this table is a valid array. Can have
- --- holes (nil values).
- ---@return Iter
- ---@private
- function ArrayIter.new(t)
- local it = {}
- it._table = t
- it._head = 1
- it._tail = #t + 1
- setmetatable(it, ArrayIter)
- return it
- end
- return setmetatable(M, {
- __call = function(_, ...)
- return Iter.new(...)
- end,
- }) --[[@as IterMod]]
|