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 for w, win in pairs(self.wins) do
if opts.wins == false then if opts.wins == false then
win.opts = self.win_opts[w] win.opts = self.win_opts[w]
elseif win:valid() then else
win:close() win:destroy()
end end
end end
for _, win in pairs(self.box_wins) do for _, win in pairs(self.box_wins) do
win:close() win:destroy()
end end
self.opts = nil
self.root = nil
self.wins = nil
self.box_wins = nil
self.win_opts = nil
end end
--- Check if layout is valid (visible) --- Check if layout is valid (visible)
function M:valid() function M:valid()
return self.root:valid() return not self.closed and self.root:valid()
end end
--- Check if the window has been used in the layout --- 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 return not self:is_hidden(w) and self.wins[w].enabled
end 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 --- Show the layout
function M:show() function M:show()
if self:valid() then if self:valid() then

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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