perf(notifier): index queue by id

This commit is contained in:
Folke Lemaitre 2024-11-07 20:43:37 +01:00
parent f59237f1dc
commit 5df4394c60
No known key found for this signature in database
GPG key ID: 41F8B1FBACAE2040

View file

@ -31,16 +31,17 @@ local M = setmetatable({}, {
--- Notification object --- Notification object
---@class snacks.notifier.Notif: snacks.notifier.Notif.opts ---@class snacks.notifier.Notif: snacks.notifier.Notif.opts
---@field msg string
---@field id number|string ---@field id number|string
---@field msg string
---@field win? snacks.win ---@field win? snacks.win
---@field icon string ---@field icon string
---@field level snacks.notifier.level ---@field level snacks.notifier.level
---@field timeout number ---@field timeout number
---@field dirty? boolean ---@field dirty? boolean
---@field shown? number timestamp in ms ---@field added number timestamp with nano precision
---@field added number timestamp in ms ---@field updated number timestamp with nano precision
---@field added_hr number hrtime in ms ---@field shown? number timestamp with nano precision
---@field hidden? number timestamp with nano precision
---@field layout? { top?: number, size: { width: number, height: number }} ---@field layout? { top?: number, size: { width: number, height: number }}
--- ### Rendering --- ### Rendering
@ -93,7 +94,8 @@ local defaults = {
} }
---@class snacks.notifier.Class ---@class snacks.notifier.Class
---@field queue snacks.notifier.Notif[] ---@field queue table<string|number, snacks.notifier.Notif>
---@field sorted? snacks.notifier.Notif[]
---@field opts snacks.notifier.Config ---@field opts snacks.notifier.Config
---@field dirty boolean ---@field dirty boolean
local N = {} local N = {}
@ -168,6 +170,11 @@ local function hl(name, level)
return "SnacksNotifier" .. name .. (level and (level:sub(1, 1):upper() .. level:sub(2):lower()) or "") return "SnacksNotifier" .. name .. (level and (level:sub(1, 1):upper() .. level:sub(2):lower()) or "")
end end
local function ts()
local ret = assert(vim.uv.clock_gettime("realtime"))
return ret.sec + ret.nsec / 1e9
end
local _id = 0 local _id = 0
local function next_id() local function next_id()
@ -181,7 +188,6 @@ function N.new(opts)
local self = setmetatable({}, { __index = N }) local self = setmetatable({}, { __index = N })
self.opts = Snacks.config.get("notifier", defaults, opts) self.opts = Snacks.config.get("notifier", defaults, opts)
self.queue = {} self.queue = {}
self.dirty = false
self:init() self:init()
self:start() self:start()
return self return self
@ -208,7 +214,7 @@ function N:start()
100, 100,
100, 100,
vim.schedule_wrap(function() vim.schedule_wrap(function()
if #self.queue == 0 then if not next(self.queue) then
return return
end end
xpcall(function() xpcall(function()
@ -227,66 +233,66 @@ end
---@param opts snacks.notifier.Notif.opts ---@param opts snacks.notifier.Notif.opts
function N:add(opts) function N:add(opts)
local now = vim.uv.hrtime() / 1e6 local now = ts()
local notif = vim.deepcopy(opts) --[[@as snacks.notifier.Notif]] local notif = vim.deepcopy(opts) --[[@as snacks.notifier.Notif]]
notif.msg = notif.msg or "" notif.msg = notif.msg or ""
notif.id = notif.id or next_id() notif.id = notif.id or next_id()
notif.level = normlevel(notif.level) notif.level = normlevel(notif.level)
notif.icon = notif.icon or self.opts.icons[notif.level] notif.icon = notif.icon or self.opts.icons[notif.level]
notif.timeout = notif.timeout or self.opts.timeout notif.timeout = notif.timeout or self.opts.timeout
notif.added = os.time() notif.added = now
notif.added_hr = now
if opts.id then if opts.id and self.queue[opts.id] then
for i, n in ipairs(self.queue) do local n = self.queue[opts.id] --[[@as snacks.notifier.Notif]]
if n.id == notif.id then notif.added = n.added
notif.shown = n.shown and now or nil -- reset shown time notif.updated = now
notif.win = n.win notif.shown = n.shown and now or nil -- reset shown time
notif.layout = n.layout notif.win = n.win
notif.dirty = true notif.layout = n.layout
self.queue[i] = notif notif.dirty = true
return notif.id
end
end
end end
table.insert(self.queue, notif) self.sorted = nil
self.dirty = true
self.queue[notif.id] = notif
return notif.id return notif.id
end end
function N:update() function N:update()
local now = vim.uv.now() local now = ts()
--- Cleanup queue --- Cleanup queue
---@param notif snacks.notifier.Notif for id, notif in pairs(self.queue) do
self.queue = vim.tbl_filter(function(notif)
local timeout = notif.timeout or self.opts.timeout local timeout = notif.timeout or self.opts.timeout
local keep = not notif.shown -- not shown yet local keep = not notif.shown -- not shown yet
or (notif.win and notif.win:win_valid() and vim.api.nvim_get_current_win() == notif.win.win) -- current window or (notif.win and notif.win:win_valid() and vim.api.nvim_get_current_win() == notif.win.win) -- current window
or (notif.keep and notif.keep(notif)) -- custom keep or (notif.keep and notif.keep(notif)) -- custom keep
or (self.opts.keep and self.opts.keep(notif)) -- global keep or (self.opts.keep and self.opts.keep(notif)) -- global keep
or (notif.shown + timeout > now) -- not timed out or (notif.shown + timeout / 1e3 > now) -- not timed out
if not keep and notif.win then if not keep then
notif.win:close() self:hide(id)
notif.win = nil
self.dirty = true
end end
return keep
end, self.queue)
if self.dirty then
self:sort()
end end
self.dirty = false self.sorted = self.sorted or self:sort()
end end
---@param id? number|string ---@param id? number|string
function N:hide(id) function N:hide(id)
---@param notif snacks.notifier.Notif if not id then
self.queue = vim.tbl_filter(function(notif) for i in pairs(self.queue) do
if notif.win and id == nil or notif.id == id then self:hide(i)
notif.win:close()
return false
end end
return true return
end, self.queue) end
local notif = self.queue[id]
if not notif then
return
end
self.queue[id], self.sorted = nil, nil
notif.hidden = ts()
if notif.win then
notif.win:hide()
notif.win = nil
end
end end
---@param value number ---@param value number
@ -384,13 +390,13 @@ function N:render(notif)
end end
function N:sort() function N:sort()
table.sort(self.queue, function(a, b) ---@type snacks.notifier.Notif[]
local ret = vim.tbl_values(self.queue)
table.sort(ret, function(a, b)
for _, key in ipairs(self.opts.sort) do for _, key in ipairs(self.opts.sort) do
local function v(n) local function v(n)
if key == "level" then if key == "level" then
return 10 - vim.log.levels[n[key]:upper()] return 10 - vim.log.levels[n[key]:upper()]
elseif key == "added" then
return n.added_hr
end end
return n[key] return n[key]
end end
@ -401,6 +407,7 @@ function N:sort()
end end
return false return false
end) end)
return ret
end end
function N:layout() function N:layout()
@ -432,7 +439,7 @@ function N:layout()
local shown = 0 local shown = 0
local max_visible = vim.o.lines * (self.opts.height.min + 2) local max_visible = vim.o.lines * (self.opts.height.min + 2)
for _, notif in ipairs(self.queue) do for _, notif in ipairs(assert(self.sorted)) do
local skip = shown >= max_visible local skip = shown >= max_visible
if not skip then if not skip then
if not notif.win or notif.dirty or not notif.win:buf_valid() or type(notif.opts) == "function" then if not notif.win or notif.dirty or not notif.win:buf_valid() or type(notif.opts) == "function" then
@ -448,7 +455,7 @@ function N:layout()
mark(notif.layout.top, notif.layout.size.height, false) mark(notif.layout.top, notif.layout.size.height, false)
notif.win.opts.row = notif.layout.top - 1 notif.win.opts.row = notif.layout.top - 1
notif.win.opts.col = vim.o.columns - notif.layout.size.width - self.opts.margin.right notif.win.opts.col = vim.o.columns - notif.layout.size.width - self.opts.margin.right
notif.shown = notif.shown or vim.uv.now() notif.shown = notif.shown or ts()
notif.win:show() notif.win:show()
notif.win:update() notif.win:update()
elseif notif.win then elseif notif.win then