perf(picker): fixed some issues with closed pickers not always being garbage collected

This commit is contained in:
Folke Lemaitre 2025-01-20 10:30:44 +01:00
parent b59f4ff477
commit eebf44a34e
No known key found for this signature in database
GPG key ID: 41F8B1FBACAE2040
7 changed files with 82 additions and 24 deletions

View file

@ -382,18 +382,23 @@ function M:close(opts)
for w, win in pairs(self.wins) do
if opts.wins == false then
win.opts = self.win_opts[w]
elseif win:valid() then
win:close()
else
win:destroy()
end
end
for _, win in pairs(self.box_wins) do
win:close()
win:destroy()
end
self.opts = nil
self.root = nil
self.wins = nil
self.box_wins = nil
self.win_opts = nil
end
--- Check if layout is valid (visible)
function M:valid()
return self.root:valid()
return not self.closed and self.root:valid()
end
--- Check if the window has been used in the layout
@ -402,6 +407,28 @@ function M:is_enabled(w)
return not self:is_hidden(w) and self.wins[w].enabled
end
function M:hide()
for _, win in ipairs(self:get_wins()) do
if win:valid() then
vim.api.nvim_win_set_config(win.win, { hide = true })
if win.backdrop and win.backdrop:valid() then
vim.api.nvim_win_set_config(win.backdrop.win, { hide = true })
end
end
end
end
function M:unhide()
for _, win in ipairs(self:get_wins()) do
if win:valid() then
vim.api.nvim_win_set_config(win.win, { hide = false })
if win.backdrop and win.backdrop:valid() then
vim.api.nvim_win_set_config(win.backdrop.win, { hide = false })
end
end
end
end
--- Show the layout
function M:show()
if self:valid() then

View file

@ -13,7 +13,7 @@ local islist = vim.islist or vim.tbl_islist
---@param picker snacks.Picker
function M.get(picker)
local ref = Snacks.util.ref(picker)
local ref = picker:ref()
---@type table<string, snacks.win.Action>
local ret = {}
setmetatable(ret, {
@ -23,11 +23,7 @@ function M.get(picker)
if type(k) ~= "string" then
return
end
local p = ref()
if not p then
return
end
t[k] = M.wrap(k, p, k) or false
t[k] = M.wrap(k, ref, k) or false
return rawget(t, k)
end,
})
@ -35,16 +31,24 @@ function M.get(picker)
end
---@param action snacks.picker.Action.spec
---@param picker snacks.Picker
---@param ref snacks.Picker.ref
---@param name? string
---@return snacks.win.Action?
function M.wrap(action, picker, name)
function M.wrap(action, ref, name)
local picker = ref()
if not picker then
return
end
action = M.resolve(action, picker, name)
action.name = name
return {
name = name,
action = function()
return action.action(picker, picker:current(), action)
local p = ref()
if not p then
return
end
return action.action(p, p:current(), action)
end,
desc = action.desc,
}

View file

@ -2,7 +2,6 @@
---@field win snacks.win
---@field totals string
---@field picker snacks.Picker
---@field _statuscolumn string
---@field filter snacks.picker.Filter
local M = {}
M.__index = M
@ -41,10 +40,12 @@ function M.new(picker)
},
}))
local ref = picker:ref()
self.win:on(
{ "TextChangedI", "TextChanged" },
Snacks.util.throttle(function()
if not self.win:valid() then
local p = ref()
if not p or not self.win:valid() then
return
end
-- only one line
@ -56,18 +57,23 @@ function M.new(picker)
end
vim.bo[self.win.buf].modified = false
local pattern = self:get()
if self.picker.opts.live then
if p.opts.live then
self.filter.search = pattern
else
self.filter.pattern = pattern
end
picker:match()
p:match()
end, { ms = picker.opts.live and 100 or 30 }),
{ buf = true }
)
return self
end
function M:close()
self.win:destroy()
self.picker = nil -- needed for garbage collection of the picker
end
function M:statuscolumn()
local parts = {} ---@type string[]
local function add(str, hl)

View file

@ -132,9 +132,8 @@ function M:count()
end
function M:close()
self.win:close()
self.items = {}
self.topk:clear()
-- nothing todo. Keep all items so actions can be performed on them,
-- even when the picker closed
end
function M:scrolloff()

View file

@ -4,6 +4,8 @@ local Finder = require("snacks.picker.core.finder")
local uv = vim.uv or vim.loop
Async.BUDGET = 10
---@alias snacks.Picker.ref (fun():snacks.Picker?)|{value?: snacks.Picker}
---@class snacks.Picker
---@field opts snacks.picker.Config
---@field finder snacks.picker.Finder
@ -134,8 +136,12 @@ function M.new(opts)
end)
end)
local ref = self:ref()
self._throttled_preview = Snacks.util.throttle(function()
self._show_preview(self)
local this = ref()
if this then
this:_show_preview()
end
end, { ms = 60, name = "preview" })
self:find()
@ -412,6 +418,11 @@ function M:empty()
return self:count() == 0
end
---@return snacks.Picker.ref
function M:ref()
return Snacks.util.ref(self)
end
--- Close the picker
function M:close()
vim.cmd.stopinsert()
@ -428,15 +439,14 @@ function M:close()
if is_picker_win and vim.api.nvim_win_is_valid(self.main) then
vim.api.nvim_set_current_win(self.main)
end
self.preview.win:close()
self.layout:close()
self.updater:stop()
M._active[self] = nil
vim.schedule(function()
self.list:clear()
self.finder.items = {}
self.matcher:abort()
self.finder:abort()
self.input:close()
self.preview:close()
end)
end

View file

@ -80,6 +80,13 @@ function M.new(opts, main)
return self
end
function M:close()
self.win:destroy()
self.item = nil
self.win_opts = { main = {}, layout = {}, win = {} }
self.state = {}
end
---@param main? number
function M:update(main)
self.main = main

View file

@ -486,6 +486,11 @@ function M:scroll(up)
end)
end
function M:destroy()
self:close()
self.events = {}
end
---@param opts? { buf: boolean }
function M:close(opts)
opts = opts or {}