mirror of
https://github.com/folke/snacks.nvim
synced 2025-07-07 21:25:11 +00:00

## Description More info coming tomorrow. In short: - very fast. pretty much realtime filtering/sorting in huge repos (like 1.7 million files) - extensible - easy to customize the layout (and lots of presets) with `snacks.layout` - simple to create custom pickers - `vim.ui.select` - lots of builtin pickers - uses treesitter highlighting wherever it makes sense - fast lua fuzzy matcher which supports the [fzf syntax](https://junegunn.github.io/fzf/search-syntax/) and additionally supports field filters, like `file:lua$ 'function` There's no snacks picker command, just use lua. ```lua -- all pickers Snacks.picker() -- run files picker Snacks.picker.files(opts) Snacks.picker.pick("files", opts) Snacks.picker.pick({source = "files", ...}) ``` <!-- Describe the big picture of your changes to communicate to the maintainers why we should accept this pull request. --> ## Todo - [x] issue with preview loc not always correct when scrolling fast in list (probably due to `snacks.scroll`) - [x] `grep` (`live_grep`) is sometimes too fast in large repos and can impact ui rendering. Not very noticeable, but something I want to look at. - [x] docs - [x] treesitter highlights are broken. Messed something up somewhere ## Related Issue(s) <!-- If this PR fixes any issues, please link to the issue here. - Fixes #<issue_number> --> ## Screenshots <!-- Add screenshots of the changes if applicable. -->
147 lines
3.7 KiB
Lua
147 lines
3.7 KiB
Lua
---@class snacks.picker.MinHeap
|
|
---@field data any[] -- the heap array
|
|
---@field cmp fun(a:any, b:any):boolean -- determines "priority"; if cmp(a,b) == true, a is considered 'larger' for top-k
|
|
---@field capacity number
|
|
---@field sorted? snacks.picker.Item[]
|
|
local M = {}
|
|
M.__index = M
|
|
|
|
---@class snacks.picker.minheap.Config
|
|
---@field cmp? fun(a, b):boolean
|
|
---@field capacity number
|
|
|
|
---@param opts? snacks.picker.minheap.Config
|
|
function M.new(opts)
|
|
opts = opts or {}
|
|
local self = setmetatable({}, M)
|
|
|
|
-- Default comparator means: a > b => a is 'better' (we want the top by value)
|
|
-- So if we want the top K largest items, the heap is min-heap based on that comparator
|
|
self.cmp = opts.cmp or function(a, b)
|
|
return a > b
|
|
end
|
|
self.capacity = assert(opts.capacity, "capacity is required")
|
|
assert(self.capacity > 0, "capacity must be greater than 0")
|
|
self.data = {}
|
|
return self
|
|
end
|
|
|
|
function M:clear()
|
|
self.data = {}
|
|
end
|
|
|
|
-- Private: swap two indices
|
|
function M:_swap(i, j)
|
|
self.data[i], self.data[j] = self.data[j], self.data[i]
|
|
end
|
|
|
|
-- Private: heapify up (bubble up)
|
|
function M:_heapify_up(idx)
|
|
while idx > 1 do
|
|
local parent = math.floor(idx / 2)
|
|
-- If child is 'less' than parent under the min-heap logic, swap
|
|
-- Because self.cmp(child, parent) == true => child is 'bigger' => for min-heap we want bigger below
|
|
-- So we invert self.cmp because we want to keep the smallest at top:
|
|
if self.cmp(self.data[parent], self.data[idx]) then
|
|
self:_swap(parent, idx)
|
|
idx = parent
|
|
else
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Private: heapify down
|
|
function M:_heapify_down(idx)
|
|
local size = #self.data
|
|
while true do
|
|
local left = 2 * idx
|
|
local right = left + 1
|
|
local smallest = idx
|
|
|
|
if left <= size and self.cmp(self.data[smallest], self.data[left]) then
|
|
smallest = left
|
|
end
|
|
if right <= size and self.cmp(self.data[smallest], self.data[right]) then
|
|
smallest = right
|
|
end
|
|
if smallest ~= idx then
|
|
self:_swap(idx, smallest)
|
|
idx = smallest
|
|
else
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Insert value into the min-heap of capacity K.
|
|
--- If the heap is not full, just insert.
|
|
--- If it's full and the value is 'larger' than the min (root), replace the root & heapify.
|
|
---@generic T
|
|
---@param value T
|
|
---@return boolean added, T? evicted
|
|
function M:add(value)
|
|
local size = #self.data
|
|
if size < self.capacity then
|
|
-- Just insert at the end, heapify up
|
|
table.insert(self.data, value)
|
|
self:_heapify_up(#self.data)
|
|
self.sorted = nil
|
|
return true
|
|
else
|
|
-- If new value is larger than the root (which is the smallest in the min-heap),
|
|
-- then pop root & insert new value
|
|
if self.cmp(value, self.data[1]) then
|
|
local evicted = self.data[1]
|
|
self.data[1] = value
|
|
self:_heapify_down(1)
|
|
self.sorted = nil
|
|
return true, evicted
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
function M:count()
|
|
return #self.data
|
|
end
|
|
|
|
---@return any|nil
|
|
function M:min()
|
|
return self.data[1]
|
|
end
|
|
|
|
---@return any|nil
|
|
function M:max()
|
|
-- might need to scan if you want the max element in a min-heap
|
|
local size = #self.data
|
|
if size == 0 then
|
|
return nil
|
|
end
|
|
local maximum = self.data[1]
|
|
for i = 2, size do
|
|
if self.cmp(self.data[i], maximum) then
|
|
maximum = self.data[i]
|
|
end
|
|
end
|
|
return maximum
|
|
end
|
|
|
|
---@param idx number
|
|
---@return snacks.picker.Item?
|
|
---@overload fun(self: snacks.picker.MinHeap): snacks.picker.Item[]
|
|
function M:get(idx)
|
|
if not self.sorted then
|
|
self.sorted = {}
|
|
for i = 1, #self.data do
|
|
table.insert(self.sorted, self.data[i])
|
|
end
|
|
table.sort(self.sorted, self.cmp)
|
|
end
|
|
if idx then
|
|
return self.sorted[idx]
|
|
end
|
|
return self.sorted
|
|
end
|
|
|
|
return M
|