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 confirm? snacks.picker.Action.spec shortcut for confirm action
|
||||||
---@field auto_confirm? boolean automatically confirm if there is only one item
|
---@field auto_confirm? boolean automatically confirm if there is only one item
|
||||||
---@field main? snacks.picker.main.Config main editor window config
|
---@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 on_show? fun(picker:snacks.Picker) called when the picker is shown
|
||||||
---@field jump? snacks.picker.jump.Config|{}
|
---@field jump? snacks.picker.jump.Config|{}
|
||||||
--- Other
|
--- Other
|
||||||
|
|
|
@ -34,6 +34,14 @@ function M:count()
|
||||||
return #self.items
|
return #self.items
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function M:close()
|
||||||
|
self.task:abort()
|
||||||
|
self.task = Async.nop()
|
||||||
|
self._find = function()
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
---@param search string
|
---@param search string
|
||||||
function M:changed(search)
|
function M:changed(search)
|
||||||
search = vim.trim(search)
|
search = vim.trim(search)
|
||||||
|
|
|
@ -23,11 +23,11 @@ function M.new(picker)
|
||||||
height = 1,
|
height = 1,
|
||||||
text = picker.opts.live and self.filter.search or self.filter.pattern,
|
text = picker.opts.live and self.filter.search or self.filter.pattern,
|
||||||
ft = "regex",
|
ft = "regex",
|
||||||
on_win = function()
|
|
||||||
vim.fn.prompt_setprompt(self.win.buf, "")
|
|
||||||
self.win:focus()
|
|
||||||
vim.cmd.startinsert()
|
vim.cmd.startinsert()
|
||||||
vim.api.nvim_win_set_cursor(self.win.win, { 1, #self:get() + 1 })
|
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,
|
end,
|
||||||
bo = {
|
bo = {
|
||||||
filetype = "snacks_picker_input",
|
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(
|
self.win:on(
|
||||||
{ "TextChangedI", "TextChanged" },
|
{ "TextChangedI", "TextChanged" },
|
||||||
Snacks.util.throttle(function()
|
Snacks.util.throttle(function()
|
||||||
local p = ref()
|
local input = ref()
|
||||||
if not p or not self.win:valid() then
|
if not input or not input.win:valid() then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
-- only one line
|
-- only one line
|
||||||
-- Can happen when someone pastes a multiline string
|
-- Can happen when someone pastes a multiline string
|
||||||
if vim.api.nvim_buf_line_count(self.win.buf) > 1 then
|
if vim.api.nvim_buf_line_count(input.win.buf) > 1 then
|
||||||
local line = vim.trim(self.win:text():gsub("\n", " "))
|
local line = vim.trim(input.win:text():gsub("\n", " "))
|
||||||
vim.api.nvim_buf_set_lines(self.win.buf, 0, -1, false, { line })
|
vim.api.nvim_buf_set_lines(input.win.buf, 0, -1, false, { line })
|
||||||
vim.api.nvim_win_set_cursor(self.win.win, { 1, #line + 1 })
|
vim.api.nvim_win_set_cursor(input.win.win, { 1, #line + 1 })
|
||||||
end
|
end
|
||||||
vim.bo[self.win.buf].modified = false
|
vim.bo[input.win.buf].modified = false
|
||||||
local pattern = self:get()
|
local pattern = input:get()
|
||||||
if p.opts.live then
|
if input.picker.opts.live then
|
||||||
self.filter.search = pattern
|
input.filter.search = pattern
|
||||||
else
|
else
|
||||||
self.filter.pattern = pattern
|
input.filter.pattern = pattern
|
||||||
end
|
end
|
||||||
p:match()
|
input.picker:match()
|
||||||
end, { ms = picker.opts.live and 100 or 30 }),
|
end, { ms = picker.opts.live and 100 or 30 }),
|
||||||
{ buf = true }
|
{ buf = true }
|
||||||
)
|
)
|
||||||
|
|
|
@ -132,7 +132,9 @@ function M:count()
|
||||||
end
|
end
|
||||||
|
|
||||||
function M:close()
|
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
|
-- even when the picker closed
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -59,6 +59,11 @@ function M:abort()
|
||||||
self.task:abort()
|
self.task:abort()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function M:close()
|
||||||
|
self:abort()
|
||||||
|
self.task = Async.nop()
|
||||||
|
end
|
||||||
|
|
||||||
---@param picker snacks.Picker
|
---@param picker snacks.Picker
|
||||||
---@param opts? {prios?: snacks.picker.Item[]}
|
---@param opts? {prios?: snacks.picker.Item[]}
|
||||||
function M:run(picker, opts)
|
function M:run(picker, opts)
|
||||||
|
|
|
@ -3,10 +3,12 @@ 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
|
||||||
|
local _id = 0
|
||||||
|
|
||||||
---@alias snacks.Picker.ref (fun():snacks.Picker?)|{value?: snacks.Picker}
|
---@alias snacks.Picker.ref (fun():snacks.Picker?)|{value?: snacks.Picker}
|
||||||
|
|
||||||
---@class snacks.Picker
|
---@class snacks.Picker
|
||||||
|
---@field id number
|
||||||
---@field opts snacks.picker.Config
|
---@field opts snacks.picker.Config
|
||||||
---@field finder snacks.picker.Finder
|
---@field finder snacks.picker.Finder
|
||||||
---@field format snacks.picker.format
|
---@field format snacks.picker.format
|
||||||
|
@ -38,7 +40,7 @@ M._active = {}
|
||||||
---@class snacks.picker.Last
|
---@class snacks.picker.Last
|
||||||
---@field cursor number
|
---@field cursor number
|
||||||
---@field topline number
|
---@field topline number
|
||||||
---@field opts snacks.picker.Config
|
---@field opts? snacks.picker.Config
|
||||||
---@field selected snacks.picker.Item[]
|
---@field selected snacks.picker.Item[]
|
||||||
---@field filter snacks.picker.Filter
|
---@field filter snacks.picker.Filter
|
||||||
|
|
||||||
|
@ -51,6 +53,8 @@ M.last = nil
|
||||||
---@param opts? snacks.picker.Config
|
---@param opts? snacks.picker.Config
|
||||||
function M.new(opts)
|
function M.new(opts)
|
||||||
local self = setmetatable({}, M)
|
local self = setmetatable({}, M)
|
||||||
|
_id = _id + 1
|
||||||
|
self.id = _id
|
||||||
self.opts = Snacks.picker.config.get(opts)
|
self.opts = Snacks.picker.config.get(opts)
|
||||||
if self.opts.source == "resume" then
|
if self.opts.source == "resume" then
|
||||||
return M.resume()
|
return M.resume()
|
||||||
|
@ -70,19 +74,23 @@ function M.new(opts)
|
||||||
if picker_count > 0 then
|
if picker_count > 0 then
|
||||||
-- clear items from previous pickers for garbage collection
|
-- clear items from previous pickers for garbage collection
|
||||||
for picker, _ in pairs(M._pickers) do
|
for picker, _ in pairs(M._pickers) do
|
||||||
picker.list.items = {}
|
|
||||||
picker.list.topk:clear()
|
|
||||||
picker.finder.items = {}
|
picker.finder.items = {}
|
||||||
|
picker.list.items = {}
|
||||||
|
picker.list:clear()
|
||||||
picker.list.picker = nil
|
picker.list.picker = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if self.opts.debug.leaks then
|
if self.opts.debug.leaks and picker_count > 0 then
|
||||||
collectgarbage("collect")
|
collectgarbage("collect")
|
||||||
picker_count = vim.tbl_count(M._pickers)
|
picker_count = vim.tbl_count(M._pickers)
|
||||||
if picker_count > 0 then
|
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) }
|
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(
|
lines[#lines + 1] = ("- [%s]: **pattern**=%q, **search**=%q"):format(
|
||||||
picker.opts.source or "custom",
|
picker.opts.source or "custom",
|
||||||
picker.input.filter.pattern,
|
picker.input.filter.pattern,
|
||||||
|
@ -92,7 +100,10 @@ function M.new(opts)
|
||||||
Snacks.notify.error(lines, { title = "Snacks Picker", id = "snacks_picker_leaks" })
|
Snacks.notify.error(lines, { title = "Snacks Picker", id = "snacks_picker_leaks" })
|
||||||
Snacks.debug.metrics()
|
Snacks.debug.metrics()
|
||||||
else
|
else
|
||||||
Snacks.notifier.hide("snacks_picker_leaks")
|
Snacks.notify(
|
||||||
|
"Picker leaks cleared after `collectgarbage`",
|
||||||
|
{ title = "Snacks Picker", id = "snacks_picker_leaks" }
|
||||||
|
)
|
||||||
end
|
end
|
||||||
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)
|
self.preview = require("snacks.picker.core.preview").new(self.opts, layout.preview == "main" and self.main or nil)
|
||||||
|
|
||||||
M.last = {
|
M.last = {
|
||||||
opts = self.opts,
|
opts = opts,
|
||||||
selected = {},
|
selected = {},
|
||||||
cursor = self.list.cursor,
|
cursor = self.list.cursor,
|
||||||
filter = self.input.filter,
|
filter = self.input.filter,
|
||||||
|
@ -457,6 +468,15 @@ function M:close()
|
||||||
if self.closed then
|
if self.closed then
|
||||||
return
|
return
|
||||||
end
|
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:hist_record(true)
|
||||||
self.closed = true
|
self.closed = true
|
||||||
M.last.selected = self:selected({ fallback = false })
|
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
|
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.layout:close()
|
|
||||||
self.updater:stop()
|
self.updater:stop()
|
||||||
self.finder:abort()
|
self.finder:abort()
|
||||||
self.matcher:abort()
|
self.matcher:abort()
|
||||||
M._active[self] = nil
|
M._active[self] = nil
|
||||||
vim.schedule(function()
|
vim.schedule(function()
|
||||||
-- order matters!
|
self.finder:close()
|
||||||
|
self.matcher:close()
|
||||||
|
self.layout:close()
|
||||||
|
self.list:close()
|
||||||
self.input:close()
|
self.input:close()
|
||||||
self.preview:close()
|
self.preview:close()
|
||||||
|
self.resolved_layout = nil
|
||||||
|
self.preview = nil
|
||||||
|
self.matcher = nil
|
||||||
|
self.updater = nil
|
||||||
|
self.history = nil
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -487,12 +514,12 @@ end
|
||||||
|
|
||||||
---@private
|
---@private
|
||||||
function M:progress(ms)
|
function M:progress(ms)
|
||||||
if self.updater:is_active() then
|
if self.updater:is_active() or self.closed then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
self.updater = vim.defer_fn(function()
|
self.updater = vim.defer_fn(function()
|
||||||
self:update()
|
self:update()
|
||||||
if self:is_active() then
|
if not self.closed and self:is_active() then
|
||||||
-- slower progress when we filled topk
|
-- slower progress when we filled topk
|
||||||
local topk, height = self.list.topk:count(), self.list.state.height or 50
|
local topk, height = self.list.topk:count(), self.list.state.height or 50
|
||||||
self:progress(topk > height and 30 or 10)
|
self:progress(topk > height and 30 or 10)
|
||||||
|
|
|
@ -140,6 +140,8 @@ function M.request(buf, method, params, cb)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
async:suspend()
|
async:suspend()
|
||||||
|
cancel = {}
|
||||||
|
async = Async.nop()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Support for older versions of neovim
|
-- Support for older versions of neovim
|
||||||
|
|
|
@ -489,6 +489,9 @@ end
|
||||||
function M:destroy()
|
function M:destroy()
|
||||||
self:close()
|
self:close()
|
||||||
self.events = {}
|
self.events = {}
|
||||||
|
self.keys = {}
|
||||||
|
self.meta = {}
|
||||||
|
self.opts = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param opts? { buf: boolean }
|
---@param opts? { buf: boolean }
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue