mirror of
https://github.com/folke/snacks.nvim
synced 2025-08-04 18:58:12 +00:00
perf(picker): gc optims
This commit is contained in:
parent
c1e2c619b2
commit
3fa2ea3115
8 changed files with 76 additions and 29 deletions
|
@ -87,7 +87,7 @@ local M = {}
|
|||
---@field confirm? snacks.picker.Action.spec shortcut for confirm action
|
||||
---@field auto_confirm? boolean automatically confirm if there is only one item
|
||||
---@field main? snacks.picker.main.Config main editor window config
|
||||
---@field on_change? fun(picker:snacks.Picker, item:snacks.picker.Item) called when the cursor changes
|
||||
---@field on_change? fun(picker:snacks.Picker, item?:snacks.picker.Item) called when the cursor changes
|
||||
---@field on_show? fun(picker:snacks.Picker) called when the picker is shown
|
||||
---@field jump? snacks.picker.jump.Config|{}
|
||||
--- Other
|
||||
|
|
|
@ -34,6 +34,14 @@ function M:count()
|
|||
return #self.items
|
||||
end
|
||||
|
||||
function M:close()
|
||||
self.task:abort()
|
||||
self.task = Async.nop()
|
||||
self._find = function()
|
||||
return {}
|
||||
end
|
||||
end
|
||||
|
||||
---@param search string
|
||||
function M:changed(search)
|
||||
search = vim.trim(search)
|
||||
|
|
|
@ -23,11 +23,11 @@ function M.new(picker)
|
|||
height = 1,
|
||||
text = picker.opts.live and self.filter.search or self.filter.pattern,
|
||||
ft = "regex",
|
||||
on_win = function()
|
||||
vim.fn.prompt_setprompt(self.win.buf, "")
|
||||
self.win:focus()
|
||||
vim.cmd.startinsert()
|
||||
vim.api.nvim_win_set_cursor(self.win.win, { 1, #self:get() + 1 })
|
||||
on_win = function(win)
|
||||
vim.fn.prompt_setprompt(win.buf, "")
|
||||
win:focus()
|
||||
end,
|
||||
bo = {
|
||||
filetype = "snacks_picker_input",
|
||||
|
@ -40,29 +40,29 @@ function M.new(picker)
|
|||
},
|
||||
}))
|
||||
|
||||
local ref = picker:ref()
|
||||
local ref = Snacks.util.ref(self)
|
||||
self.win:on(
|
||||
{ "TextChangedI", "TextChanged" },
|
||||
Snacks.util.throttle(function()
|
||||
local p = ref()
|
||||
if not p or not self.win:valid() then
|
||||
local input = ref()
|
||||
if not input or not input.win:valid() then
|
||||
return
|
||||
end
|
||||
-- only one line
|
||||
-- Can happen when someone pastes a multiline string
|
||||
if vim.api.nvim_buf_line_count(self.win.buf) > 1 then
|
||||
local line = vim.trim(self.win:text():gsub("\n", " "))
|
||||
vim.api.nvim_buf_set_lines(self.win.buf, 0, -1, false, { line })
|
||||
vim.api.nvim_win_set_cursor(self.win.win, { 1, #line + 1 })
|
||||
if vim.api.nvim_buf_line_count(input.win.buf) > 1 then
|
||||
local line = vim.trim(input.win:text():gsub("\n", " "))
|
||||
vim.api.nvim_buf_set_lines(input.win.buf, 0, -1, false, { line })
|
||||
vim.api.nvim_win_set_cursor(input.win.win, { 1, #line + 1 })
|
||||
end
|
||||
vim.bo[self.win.buf].modified = false
|
||||
local pattern = self:get()
|
||||
if p.opts.live then
|
||||
self.filter.search = pattern
|
||||
vim.bo[input.win.buf].modified = false
|
||||
local pattern = input:get()
|
||||
if input.picker.opts.live then
|
||||
input.filter.search = pattern
|
||||
else
|
||||
self.filter.pattern = pattern
|
||||
input.filter.pattern = pattern
|
||||
end
|
||||
p:match()
|
||||
input.picker:match()
|
||||
end, { ms = picker.opts.live and 100 or 30 }),
|
||||
{ buf = true }
|
||||
)
|
||||
|
|
|
@ -132,7 +132,9 @@ function M:count()
|
|||
end
|
||||
|
||||
function M:close()
|
||||
-- nothing todo. Keep all items so actions can be performed on them,
|
||||
self.win:destroy()
|
||||
self.picker = nil
|
||||
-- Keep all items so actions can be performed on them,
|
||||
-- even when the picker closed
|
||||
end
|
||||
|
||||
|
|
|
@ -59,6 +59,11 @@ function M:abort()
|
|||
self.task:abort()
|
||||
end
|
||||
|
||||
function M:close()
|
||||
self:abort()
|
||||
self.task = Async.nop()
|
||||
end
|
||||
|
||||
---@param picker snacks.Picker
|
||||
---@param opts? {prios?: snacks.picker.Item[]}
|
||||
function M:run(picker, opts)
|
||||
|
|
|
@ -3,10 +3,12 @@ local Finder = require("snacks.picker.core.finder")
|
|||
|
||||
local uv = vim.uv or vim.loop
|
||||
Async.BUDGET = 10
|
||||
local _id = 0
|
||||
|
||||
---@alias snacks.Picker.ref (fun():snacks.Picker?)|{value?: snacks.Picker}
|
||||
|
||||
---@class snacks.Picker
|
||||
---@field id number
|
||||
---@field opts snacks.picker.Config
|
||||
---@field finder snacks.picker.Finder
|
||||
---@field format snacks.picker.format
|
||||
|
@ -38,7 +40,7 @@ M._active = {}
|
|||
---@class snacks.picker.Last
|
||||
---@field cursor number
|
||||
---@field topline number
|
||||
---@field opts snacks.picker.Config
|
||||
---@field opts? snacks.picker.Config
|
||||
---@field selected snacks.picker.Item[]
|
||||
---@field filter snacks.picker.Filter
|
||||
|
||||
|
@ -51,6 +53,8 @@ M.last = nil
|
|||
---@param opts? snacks.picker.Config
|
||||
function M.new(opts)
|
||||
local self = setmetatable({}, M)
|
||||
_id = _id + 1
|
||||
self.id = _id
|
||||
self.opts = Snacks.picker.config.get(opts)
|
||||
if self.opts.source == "resume" then
|
||||
return M.resume()
|
||||
|
@ -70,19 +74,23 @@ function M.new(opts)
|
|||
if picker_count > 0 then
|
||||
-- clear items from previous pickers for garbage collection
|
||||
for picker, _ in pairs(M._pickers) do
|
||||
picker.list.items = {}
|
||||
picker.list.topk:clear()
|
||||
picker.finder.items = {}
|
||||
picker.list.items = {}
|
||||
picker.list:clear()
|
||||
picker.list.picker = nil
|
||||
end
|
||||
end
|
||||
|
||||
if self.opts.debug.leaks then
|
||||
if self.opts.debug.leaks and picker_count > 0 then
|
||||
collectgarbage("collect")
|
||||
picker_count = vim.tbl_count(M._pickers)
|
||||
if picker_count > 0 then
|
||||
local pickers = vim.tbl_keys(M._pickers) ---@type snacks.Picker[]
|
||||
table.sort(pickers, function(a, b)
|
||||
return a.id < b.id
|
||||
end)
|
||||
local lines = { ("# ` %d ` active pickers:"):format(picker_count) }
|
||||
for picker, _ in pairs(M._pickers) do
|
||||
for _, picker in ipairs(pickers) do
|
||||
lines[#lines + 1] = ("- [%s]: **pattern**=%q, **search**=%q"):format(
|
||||
picker.opts.source or "custom",
|
||||
picker.input.filter.pattern,
|
||||
|
@ -92,7 +100,10 @@ function M.new(opts)
|
|||
Snacks.notify.error(lines, { title = "Snacks Picker", id = "snacks_picker_leaks" })
|
||||
Snacks.debug.metrics()
|
||||
else
|
||||
Snacks.notifier.hide("snacks_picker_leaks")
|
||||
Snacks.notify(
|
||||
"Picker leaks cleared after `collectgarbage`",
|
||||
{ title = "Snacks Picker", id = "snacks_picker_leaks" }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -133,7 +144,7 @@ function M.new(opts)
|
|||
self.preview = require("snacks.picker.core.preview").new(self.opts, layout.preview == "main" and self.main or nil)
|
||||
|
||||
M.last = {
|
||||
opts = self.opts,
|
||||
opts = opts,
|
||||
selected = {},
|
||||
cursor = self.list.cursor,
|
||||
filter = self.input.filter,
|
||||
|
@ -457,6 +468,15 @@ function M:close()
|
|||
if self.closed then
|
||||
return
|
||||
end
|
||||
|
||||
-- FIXME: lsp definitions picker can't be gc-ed without the below,
|
||||
-- which makes no sense. Need to further investigate.
|
||||
|
||||
-- if not self.shown then
|
||||
-- self.input.win.opts.relative = "editor"
|
||||
-- self.input.win:show()
|
||||
-- end
|
||||
|
||||
self:hist_record(true)
|
||||
self.closed = true
|
||||
M.last.selected = self:selected({ fallback = false })
|
||||
|
@ -468,15 +488,22 @@ 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.layout:close()
|
||||
self.updater:stop()
|
||||
self.finder:abort()
|
||||
self.matcher:abort()
|
||||
M._active[self] = nil
|
||||
vim.schedule(function()
|
||||
-- order matters!
|
||||
self.finder:close()
|
||||
self.matcher:close()
|
||||
self.layout:close()
|
||||
self.list:close()
|
||||
self.input:close()
|
||||
self.preview:close()
|
||||
self.resolved_layout = nil
|
||||
self.preview = nil
|
||||
self.matcher = nil
|
||||
self.updater = nil
|
||||
self.history = nil
|
||||
end)
|
||||
end
|
||||
|
||||
|
@ -487,12 +514,12 @@ end
|
|||
|
||||
---@private
|
||||
function M:progress(ms)
|
||||
if self.updater:is_active() then
|
||||
if self.updater:is_active() or self.closed then
|
||||
return
|
||||
end
|
||||
self.updater = vim.defer_fn(function()
|
||||
self:update()
|
||||
if self:is_active() then
|
||||
if not self.closed and self:is_active() then
|
||||
-- slower progress when we filled topk
|
||||
local topk, height = self.list.topk:count(), self.list.state.height or 50
|
||||
self:progress(topk > height and 30 or 10)
|
||||
|
|
|
@ -140,6 +140,8 @@ function M.request(buf, method, params, cb)
|
|||
end)
|
||||
|
||||
async:suspend()
|
||||
cancel = {}
|
||||
async = Async.nop()
|
||||
end
|
||||
|
||||
-- Support for older versions of neovim
|
||||
|
|
|
@ -489,6 +489,9 @@ end
|
|||
function M:destroy()
|
||||
self:close()
|
||||
self.events = {}
|
||||
self.keys = {}
|
||||
self.meta = {}
|
||||
self.opts = {}
|
||||
end
|
||||
|
||||
---@param opts? { buf: boolean }
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue