mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	 026cfa28d0
			
		
	
	026cfa28d0
	
	
	
		
			
			Co-authored-by: Au. <acehinnnqru@gmail.com> Co-authored-by: Daniel Rainer <daniel.rainer@localhost> Co-authored-by: Evgeni Chasnovski <evgeni.chasnovski@gmail.com> Co-authored-by: Lewis Russell <lewis6991@gmail.com> Co-authored-by: Luuk van Baal <luukvbaal@gmail.com> Co-authored-by: Pierre Barbin <pierre@heitzsystem.com> Co-authored-by: Riley Bruins <ribru17@hotmail.com> Co-authored-by: Yinzuo Jiang <jiangyinzuo@foxmail.com> Co-authored-by: phanium <91544758+phanen@users.noreply.github.com> Co-authored-by: zeertzjq <zeertzjq@outlook.com>
		
			
				
	
	
		
			1089 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			1089 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
| --- @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]]
 |