feat(lua): add ringbuffer (#22894)

https://en.wikipedia.org/wiki/Circular_buffer
This commit is contained in:
Mathias Fußenegger 2023-06-08 12:11:24 +02:00 committed by GitHub
parent 38b0bb3c93
commit 7c661207cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 190 additions and 0 deletions

View File

@ -1847,6 +1847,60 @@ pesc({s}) *vim.pesc()*
See also: ~
• https://github.com/rxi/lume
ringbuf({size}) *vim.ringbuf()*
Create a ring buffer limited to a maximal number of items. Once the buffer
is full, adding a new entry overrides the oldest entry.
>
local ringbuf = vim.ringbuf(4)
ringbuf:push("a")
ringbuf:push("b")
ringbuf:push("c")
ringbuf:push("d")
ringbuf:push("e") -- overrides "a"
print(ringbuf:pop()) -- returns "b"
print(ringbuf:pop()) -- returns "c"
-- Can be used as iterator. Pops remaining items:
for val in ringbuf do
print(val)
end
<
Returns a Ringbuf instance with the following methods:
• |Ringbuf:push()|
• |Ringbuf:pop()|
• |Ringbuf:peek()|
• |Ringbuf:clear()|
Parameters: ~
• {size} (integer)
Return: ~
(table)
Ringbuf:clear({self}) *Ringbuf:clear()*
Clear all items.
Ringbuf:peek({self}) *Ringbuf:peek()*
Returns the first unread item without removing it
Return: ~
any?|ni
Ringbuf:pop({self}) *Ringbuf:pop()*
Removes and returns the first unread item
Return: ~
any?|ni
Ringbuf:push({self}, {item}) *Ringbuf:push()*
Adds an item, overriding the oldest item if the buffer is full.
Parameters: ~
• {item} any
spairs({t}) *vim.spairs()*
Enumerate a table sorted by its keys.

View File

@ -59,6 +59,8 @@ The following new APIs or features were added.
• |vim.iter()| provides a generic iterator interface for tables and Lua
iterators |luaref-in|.
• Added |vim.ringbuf()| to create ring buffers.
• Added |vim.keycode()| for translating keycodes in a string.
• Added |vim.treesitter.query.omnifunc()| for treesitter query files (set by

View File

@ -881,4 +881,98 @@ function vim.defaulttable(create)
})
end
do
---@class vim.Ringbuf<T>
---@field private _items table[]
---@field private _idx_read integer
---@field private _idx_write integer
---@field private _size integer
local Ringbuf = {}
--- Clear all items
function Ringbuf.clear(self)
self._items = {}
self._idx_read = 0
self._idx_write = 0
end
--- Adds an item, overriding the oldest item if the buffer is full.
---@generic T
---@param item T
function Ringbuf.push(self, item)
self._items[self._idx_write] = item
self._idx_write = (self._idx_write + 1) % self._size
if self._idx_write == self._idx_read then
self._idx_read = (self._idx_read + 1) % self._size
end
end
--- Removes and returns the first unread item
---@generic T
---@return T?
function Ringbuf.pop(self)
local idx_read = self._idx_read
if idx_read == self._idx_write then
return nil
end
local item = self._items[idx_read]
self._items[idx_read] = nil
self._idx_read = (idx_read + 1) % self._size
return item
end
--- Returns the first unread item without removing it
---@generic T
---@return T?
function Ringbuf.peek(self)
if self._idx_read == self._idx_write then
return nil
end
return self._items[self._idx_read]
end
--- Create a ring buffer limited to a maximal number of items.
--- Once the buffer is full, adding a new entry overrides the oldest entry.
---
--- <pre>
--- local ringbuf = vim.ringbuf(4)
--- ringbuf:push("a")
--- ringbuf:push("b")
--- ringbuf:push("c")
--- ringbuf:push("d")
--- ringbuf:push("e") -- overrides "a"
--- print(ringbuf:pop()) -- returns "b"
--- print(ringbuf:pop()) -- returns "c"
---
--- -- Can be used as iterator. Pops remaining items:
--- for val in ringbuf do
--- print(val)
--- end
--- </pre>
---
--- Returns a Ringbuf instance with the following methods:
---
--- - |Ringbuf:push()|
--- - |Ringbuf:pop()|
--- - |Ringbuf:peek()|
--- - |Ringbuf:clear()|
---
---@param size integer
---@return vim.Ringbuf ringbuf (table)
function vim.ringbuf(size)
local ringbuf = {
_items = {},
_size = size + 1,
_idx_read = 0,
_idx_write = 0,
}
return setmetatable(ringbuf, {
__index = Ringbuf,
__call = function(self)
return self:pop()
end,
})
end
end
return vim

View File

@ -3040,6 +3040,46 @@ describe('lua stdlib', function()
eq(4, exec_lua [[ return vim.re.match("abcde", '[a-c]+') ]])
end)
it("vim.ringbuf", function()
local results = exec_lua([[
local ringbuf = vim.ringbuf(3)
ringbuf:push("a") -- idx: 0
local peeka1 = ringbuf:peek()
local peeka2 = ringbuf:peek()
local popa = ringbuf:pop()
local popnil = ringbuf:pop()
ringbuf:push("a") -- idx: 1
ringbuf:push("b") -- idx: 2
-- doesn't read last added item, but uses separate read index
local pop_after_add_b = ringbuf:pop()
ringbuf:push("c") -- idx: 3 wraps around, overrides idx: 0 "a"
ringbuf:push("d") -- idx: 4 wraps around, overrides idx: 1 "a"
return {
peeka1 = peeka1,
peeka2 = peeka2,
pop1 = popa,
pop2 = popnil,
pop3 = ringbuf:pop(),
pop4 = ringbuf:pop(),
pop5 = ringbuf:pop(),
pop_after_add_b = pop_after_add_b,
}
]])
local expected = {
peeka1 = "a",
peeka2 = "a",
pop1 = "a",
pop2 = nil,
pop3 = "b",
pop4 = "c",
pop5 = "d",
pop_after_add_b = "a",
}
eq(expected, results)
end)
end)
describe('lua: builtin modules', function()