perf(picker): dont use async when not needed

This commit is contained in:
Folke Lemaitre 2025-01-14 11:05:30 +01:00
parent 076feecf20
commit 6a523768cf
No known key found for this signature in database
GPG key ID: 41F8B1FBACAE2040
4 changed files with 30 additions and 10 deletions

View file

@ -41,22 +41,27 @@ end
---@param picker snacks.Picker
function M:run(picker)
local score = require("snacks.picker.core.matcher").DEFAULT_SCORE
self.task:abort()
self.items = {}
local yield ---@type fun()
collectgarbage("stop") -- moar speed
self.filter = picker.input.filter:clone({ trim = true })
local finder = self._find(picker.opts, self.filter)
local limit = picker.opts.limit or math.huge
-- PERF: if finder is a table, we can skip the async part
if type(finder) == "table" then
local items = finder --[[@as snacks.picker.finder.Item[] ]]
finder = function(cb)
for _, item in ipairs(items) do
cb(item)
end
for i, item in ipairs(items) do
item.idx, item.score = i, score
self.items[i] = item
end
return
end
collectgarbage("stop") -- moar speed
---@cast finder fun(cb:async fun(item:snacks.picker.finder.Item))
---@diagnostic disable-next-line: await-in-sync
self.task = Async.new(function()
---@async
finder(function(item)
@ -67,7 +72,7 @@ function M:run(picker)
end
return
end
item.idx = #self.items + 1
item.idx, item.score = #self.items + 1, score
self.items[item.idx] = item
picker.matcher.task:resume()
yield = yield or Async.yielder(YIELD_FIND)

View file

@ -11,6 +11,8 @@ local Async = require("snacks.picker.util.async")
---@field live? boolean
local M = {}
M.__index = M
M.DEFAULT_SCORE = 1000
M.INVERSE_SCORE = 1000
local YIELD_MATCH = 5 -- ms
local clear = require("table.clear")
@ -79,6 +81,14 @@ function M:run(picker, opts)
if self.task:running() then
return
end
-- PERF: fast path for empty pattern
if self:empty() then
picker.list.items = picker.finder.items
picker:update()
return
end
---@async
self.task = Async.new(function()
local yield = Async.yielder(YIELD_MATCH)
@ -230,7 +240,7 @@ end
function M:match(item, opts)
opts = opts or {}
if self:empty() or (self.live and not opts.force) then
return 1000 -- empty pattern matches everything
return M.DEFAULT_SCORE -- empty pattern matches everything
end
local score = 0
local positions = opts.positions and {} or nil ---@type number[]?
@ -288,7 +298,7 @@ function M:_match(item, mods, positions)
if mods.field then
if item[mods.field] == nil then
if mods.inverse then
return 1000
return M.INVERSE_SCORE
end
return
end
@ -323,7 +333,7 @@ function M:_match(item, mods, positions)
end
if mods.inverse then
if not from then
return 1000
return M.INVERSE_SCORE
end
return
end

View file

@ -349,6 +349,7 @@ function M:update()
end
local count = self.finder:count()
local list_count = self.list:count()
-- Check if we should show the picker
if not self.shown then
-- Always show live pickers
@ -376,7 +377,7 @@ function M:update()
self:show()
self:debug("show")
end
elseif count > 1 or (count == 1 and not self.opts.auto_confirm) then -- show the picker if we have results
elseif list_count > 1 or (list_count == 1 and not self.opts.auto_confirm) then -- show the picker if we have results
self:show()
self:debug("show")
end

View file

@ -72,6 +72,10 @@ end
---@param event snacks.picker.AsyncEvent
---@param cb async fun(res:any, async:snacks.picker.Async)
function Async:on(event, cb)
if event == "done" and not self:running() then
cb(nil, self)
return self
end
self._on[event] = self._on[event] or {}
table.insert(self._on[event], cb)
return self