mirror of
https://github.com/folke/snacks.nvim
synced 2025-12-23 08:47:57 +00:00
feat(notifier): added history to notifier. Closes #14
This commit is contained in:
parent
cc5ee192ca
commit
65d8c8f00b
1 changed files with 146 additions and 34 deletions
|
|
@ -60,6 +60,14 @@ local M = setmetatable({}, {
|
||||||
---@field hl snacks.notifier.hl
|
---@field hl snacks.notifier.hl
|
||||||
---@field ns number
|
---@field ns number
|
||||||
|
|
||||||
|
--- ### History
|
||||||
|
---@class snacks.notifier.history
|
||||||
|
---@field filter? snacks.notifier.level|fun(notif: snacks.notifier.Notif): boolean
|
||||||
|
---@field sort? string[] # sort fields, default: {"added"}
|
||||||
|
local history_opts = {
|
||||||
|
sort = { "added" },
|
||||||
|
}
|
||||||
|
|
||||||
Snacks.config.style("notification", {
|
Snacks.config.style("notification", {
|
||||||
border = "rounded",
|
border = "rounded",
|
||||||
zindex = 100,
|
zindex = 100,
|
||||||
|
|
@ -71,6 +79,20 @@ Snacks.config.style("notification", {
|
||||||
bo = { filetype = "snacks_notif" },
|
bo = { filetype = "snacks_notif" },
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Snacks.config.style("notification.history", {
|
||||||
|
border = "rounded",
|
||||||
|
zindex = 100,
|
||||||
|
width = 0.6,
|
||||||
|
height = 0.6,
|
||||||
|
minimal = false,
|
||||||
|
title = "Notification History",
|
||||||
|
title_pos = "center",
|
||||||
|
ft = "markdown",
|
||||||
|
bo = { filetype = "snacks_notif_history" },
|
||||||
|
wo = { winhighlight = "Normal:SnacksNotifierHistory" },
|
||||||
|
keys = { q = "close" },
|
||||||
|
})
|
||||||
|
|
||||||
---@class snacks.notifier.Config
|
---@class snacks.notifier.Config
|
||||||
---@field keep? fun(notif: snacks.notifier.Notif): boolean # global keep function
|
---@field keep? fun(notif: snacks.notifier.Notif): boolean # global keep function
|
||||||
local defaults = {
|
local defaults = {
|
||||||
|
|
@ -91,10 +113,12 @@ local defaults = {
|
||||||
---@type snacks.notifier.style
|
---@type snacks.notifier.style
|
||||||
style = "compact",
|
style = "compact",
|
||||||
top_down = true, -- place notifications from top to bottom
|
top_down = true, -- place notifications from top to bottom
|
||||||
|
date_format = "%R", -- time format for notifications
|
||||||
}
|
}
|
||||||
|
|
||||||
---@class snacks.notifier.Class
|
---@class snacks.notifier.Class
|
||||||
---@field queue table<string|number, snacks.notifier.Notif>
|
---@field queue table<string|number, snacks.notifier.Notif>
|
||||||
|
---@field history table<string|number, snacks.notifier.Notif>
|
||||||
---@field sorted? snacks.notifier.Notif[]
|
---@field sorted? snacks.notifier.Notif[]
|
||||||
---@field opts snacks.notifier.Config
|
---@field opts snacks.notifier.Config
|
||||||
---@field dirty boolean
|
---@field dirty boolean
|
||||||
|
|
@ -102,6 +126,17 @@ local N = {}
|
||||||
|
|
||||||
N.ns = vim.api.nvim_create_namespace("snacks.notifier")
|
N.ns = vim.api.nvim_create_namespace("snacks.notifier")
|
||||||
|
|
||||||
|
---@param str string
|
||||||
|
local function cap(str)
|
||||||
|
return str:sub(1, 1):upper() .. str:sub(2):lower()
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param name string
|
||||||
|
---@param level? snacks.notifier.level
|
||||||
|
local function hl(name, level)
|
||||||
|
return "SnacksNotifier" .. name .. (level and cap(level) or "")
|
||||||
|
end
|
||||||
|
|
||||||
---@type table<string, snacks.notifier.render>
|
---@type table<string, snacks.notifier.render>
|
||||||
N.styles = {
|
N.styles = {
|
||||||
-- style using border title
|
-- style using border title
|
||||||
|
|
@ -122,6 +157,33 @@ N.styles = {
|
||||||
virt_text_pos = "right_align",
|
virt_text_pos = "right_align",
|
||||||
})
|
})
|
||||||
end,
|
end,
|
||||||
|
history = function(buf, notif, ctx)
|
||||||
|
local lines = vim.split(notif.msg, "\n", { plain = true })
|
||||||
|
local prefix = {
|
||||||
|
{ os.date(ctx.notifier.opts.date_format, notif.added), hl("HistoryDateTime") },
|
||||||
|
{ notif.icon, ctx.hl.icon },
|
||||||
|
{ notif.level:upper(), ctx.hl.title },
|
||||||
|
{ notif.title, hl("HistoryTitle") },
|
||||||
|
}
|
||||||
|
prefix = vim.tbl_filter(function(v)
|
||||||
|
return (v[1] or "") ~= ""
|
||||||
|
end, prefix)
|
||||||
|
local prefix_width = 0
|
||||||
|
for i = 1, #prefix do
|
||||||
|
prefix_width = prefix_width + vim.fn.strdisplaywidth(prefix[i * 2 - 1][1]) + 1
|
||||||
|
table.insert(prefix, i * 2, { " " })
|
||||||
|
end
|
||||||
|
local top = vim.api.nvim_buf_line_count(buf)
|
||||||
|
local empty = top == 1 and #vim.api.nvim_buf_get_lines(buf, 0, 1, false)[1] == 0
|
||||||
|
top = empty and 0 or top
|
||||||
|
lines[1] = string.rep(" ", prefix_width) .. (lines[1] or "")
|
||||||
|
vim.api.nvim_buf_set_lines(buf, top, -1, false, lines)
|
||||||
|
vim.api.nvim_buf_set_extmark(buf, ctx.ns, top, 0, {
|
||||||
|
virt_text = prefix,
|
||||||
|
virt_text_pos = "overlay",
|
||||||
|
priority = 10,
|
||||||
|
})
|
||||||
|
end,
|
||||||
-- similar to the default nvim-notify style
|
-- similar to the default nvim-notify style
|
||||||
fancy = function(buf, notif, ctx)
|
fancy = function(buf, notif, ctx)
|
||||||
vim.api.nvim_buf_set_lines(buf, 0, 1, false, { "", "" })
|
vim.api.nvim_buf_set_lines(buf, 0, 1, false, { "", "" })
|
||||||
|
|
@ -132,7 +194,7 @@ N.styles = {
|
||||||
priority = 10,
|
priority = 10,
|
||||||
})
|
})
|
||||||
vim.api.nvim_buf_set_extmark(buf, ctx.ns, 0, 0, {
|
vim.api.nvim_buf_set_extmark(buf, ctx.ns, 0, 0, {
|
||||||
virt_text = { { " " }, { os.date("%X", notif.added), ctx.hl.title }, { " " } },
|
virt_text = { { " " }, { os.date(ctx.notifier.opts.date_format, notif.added), ctx.hl.title }, { " " } },
|
||||||
virt_text_pos = "right_align",
|
virt_text_pos = "right_align",
|
||||||
priority = 10,
|
priority = 10,
|
||||||
})
|
})
|
||||||
|
|
@ -164,12 +226,6 @@ local function normlevel(level)
|
||||||
or "info"
|
or "info"
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param name string
|
|
||||||
---@param level? snacks.notifier.level
|
|
||||||
local function hl(name, level)
|
|
||||||
return "SnacksNotifier" .. name .. (level and (level:sub(1, 1):upper() .. level:sub(2):lower()) or "")
|
|
||||||
end
|
|
||||||
|
|
||||||
local function ts()
|
local function ts()
|
||||||
local ret = assert(vim.uv.clock_gettime("realtime"))
|
local ret = assert(vim.uv.clock_gettime("realtime"))
|
||||||
return ret.sec + ret.nsec / 1e9
|
return ret.sec + ret.nsec / 1e9
|
||||||
|
|
@ -188,21 +244,26 @@ 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.history = {}
|
||||||
self:init()
|
self:init()
|
||||||
self:start()
|
self:start()
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
function N:init()
|
function N:init()
|
||||||
local links = {} ---@type table<string, string>
|
local links = {
|
||||||
|
[hl("History")] = "Normal",
|
||||||
|
[hl("HistoryTitle")] = "Title",
|
||||||
|
[hl("HistoryDateTime")] = "Special",
|
||||||
|
}
|
||||||
for _, level in ipairs(N.level_names) do
|
for _, level in ipairs(N.level_names) do
|
||||||
local cap = level:sub(1, 1):upper() .. level:sub(2):lower()
|
local Level = cap(level)
|
||||||
cap = (cap == "Trace" or cap == "Debug") and "Hint" or cap
|
local link = vim.tbl_contains({ "Trace", "Debug" }, Level) and "NonText" or nil
|
||||||
links[hl("", level)] = "Normal"
|
links[hl("", level)] = "Normal"
|
||||||
links[hl("Icon", level)] = "DiagnosticSign" .. cap
|
links[hl("Icon", level)] = link or ("DiagnosticSign" .. Level)
|
||||||
links[hl("Border", level)] = "Diagnostic" .. cap
|
links[hl("Border", level)] = link or ("Diagnostic" .. Level)
|
||||||
links[hl("Title", level)] = "Diagnostic" .. cap
|
links[hl("Title", level)] = link or ("Diagnostic" .. Level)
|
||||||
links[hl("Footer", level)] = "Diagnostic" .. cap
|
links[hl("Footer", level)] = link or ("Diagnostic" .. Level)
|
||||||
end
|
end
|
||||||
for k, v in pairs(links) do
|
for k, v in pairs(links) do
|
||||||
vim.api.nvim_set_hl(0, k, { link = v, default = true })
|
vim.api.nvim_set_hl(0, k, { link = v, default = true })
|
||||||
|
|
@ -235,8 +296,9 @@ end
|
||||||
function N:add(opts)
|
function N:add(opts)
|
||||||
local now = ts()
|
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 ""
|
||||||
|
-- FIXME: normalize title, icon, etc to remove newlines
|
||||||
|
notif.title = notif.title 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]
|
||||||
|
|
@ -253,8 +315,8 @@ function N:add(opts)
|
||||||
notif.dirty = true
|
notif.dirty = true
|
||||||
end
|
end
|
||||||
self.sorted = nil
|
self.sorted = nil
|
||||||
|
|
||||||
self.queue[notif.id] = notif
|
self.queue[notif.id] = notif
|
||||||
|
self.history[notif.id] = notif
|
||||||
return notif.id
|
return notif.id
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -275,6 +337,38 @@ function N:update()
|
||||||
self.sorted = self.sorted or self:sort()
|
self.sorted = self.sorted or self:sort()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param opts? snacks.notifier.history
|
||||||
|
---@return snacks.notifier.Notif[]
|
||||||
|
function N:get_history(opts)
|
||||||
|
---@type snacks.notifier.history
|
||||||
|
opts = vim.tbl_deep_extend("force", {}, history_opts, opts or {})
|
||||||
|
local notifs = vim.tbl_values(self.history)
|
||||||
|
local filter = opts.filter
|
||||||
|
if type(filter) == "string" or type(filter) == "number" then
|
||||||
|
local level = normlevel(filter)
|
||||||
|
filter = function(n)
|
||||||
|
return n.level == level
|
||||||
|
end
|
||||||
|
end
|
||||||
|
notifs = filter and vim.tbl_filter(filter, notifs) or notifs
|
||||||
|
return self:sort(notifs, opts.sort)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param opts? snacks.notifier.history
|
||||||
|
function N:show_history(opts)
|
||||||
|
local win = Snacks.win({ style = "notification.history", enter = true, show = false })
|
||||||
|
local buf = win:open_buf()
|
||||||
|
for _, notif in ipairs(self:get_history(opts)) do
|
||||||
|
N.styles.history(buf, notif, {
|
||||||
|
opts = win.opts,
|
||||||
|
notifier = self,
|
||||||
|
ns = N.ns,
|
||||||
|
hl = self:hl(notif),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
return win:show()
|
||||||
|
end
|
||||||
|
|
||||||
---@param id? number|string
|
---@param id? number|string
|
||||||
function N:hide(id)
|
function N:hide(id)
|
||||||
if not id then
|
if not id then
|
||||||
|
|
@ -313,19 +407,25 @@ function N:get_render(style)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param notif snacks.notifier.Notif
|
---@param notif snacks.notifier.Notif
|
||||||
function N:render(notif)
|
function N:hl(notif)
|
||||||
if type(notif.opts) == "function" then
|
|
||||||
notif.opts(notif)
|
|
||||||
end
|
|
||||||
|
|
||||||
---@type snacks.notifier.hl
|
---@type snacks.notifier.hl
|
||||||
local hls = vim.tbl_extend("force", {
|
return vim.tbl_extend("force", {
|
||||||
title = hl("Title", notif.level),
|
title = hl("Title", notif.level),
|
||||||
icon = hl("Icon", notif.level),
|
icon = hl("Icon", notif.level),
|
||||||
border = hl("Border", notif.level),
|
border = hl("Border", notif.level),
|
||||||
footer = hl("Footer", notif.level),
|
footer = hl("Footer", notif.level),
|
||||||
msg = hl("", notif.level),
|
msg = hl("", notif.level),
|
||||||
}, notif.hl or {})
|
}, notif.hl or {})
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param notif snacks.notifier.Notif
|
||||||
|
function N:render(notif)
|
||||||
|
if type(notif.opts) == "function" then
|
||||||
|
notif.opts(notif)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@type snacks.notifier.hl
|
||||||
|
local notif_hl = self:hl(notif)
|
||||||
|
|
||||||
local win = notif.win
|
local win = notif.win
|
||||||
or Snacks.win({
|
or Snacks.win({
|
||||||
|
|
@ -337,11 +437,11 @@ function N:render(notif)
|
||||||
noautocmd = true,
|
noautocmd = true,
|
||||||
wo = {
|
wo = {
|
||||||
winhighlight = table.concat({
|
winhighlight = table.concat({
|
||||||
"Normal:" .. hls.msg,
|
"Normal:" .. notif_hl.msg,
|
||||||
"NormalNC:" .. hls.msg,
|
"NormalNC:" .. notif_hl.msg,
|
||||||
"FloatBorder:" .. hls.border,
|
"FloatBorder:" .. notif_hl.border,
|
||||||
"FloatTitle:" .. hls.title,
|
"FloatTitle:" .. notif_hl.title,
|
||||||
"FloatFooter:" .. hls.footer,
|
"FloatFooter:" .. notif_hl.footer,
|
||||||
}, ","),
|
}, ","),
|
||||||
},
|
},
|
||||||
keys = {
|
keys = {
|
||||||
|
|
@ -362,7 +462,7 @@ function N:render(notif)
|
||||||
opts = win.opts,
|
opts = win.opts,
|
||||||
notifier = self,
|
notifier = self,
|
||||||
ns = N.ns,
|
ns = N.ns,
|
||||||
hl = hls,
|
hl = notif_hl,
|
||||||
})
|
})
|
||||||
vim.bo[buf].modifiable = false
|
vim.bo[buf].modifiable = false
|
||||||
|
|
||||||
|
|
@ -389,11 +489,13 @@ function N:render(notif)
|
||||||
win.opts.height = height
|
win.opts.height = height
|
||||||
end
|
end
|
||||||
|
|
||||||
function N:sort()
|
---@param fields? string[]
|
||||||
---@type snacks.notifier.Notif[]
|
---@param notifs? snacks.notifier.Notif[]
|
||||||
local ret = vim.tbl_values(self.queue)
|
function N:sort(notifs, fields)
|
||||||
table.sort(ret, function(a, b)
|
fields = fields or self.opts.sort
|
||||||
for _, key in ipairs(self.opts.sort) do
|
notifs = notifs or vim.tbl_values(self.queue)
|
||||||
|
table.sort(notifs, function(a, b)
|
||||||
|
for _, key in ipairs(fields) 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()]
|
||||||
|
|
@ -407,7 +509,7 @@ function N:sort()
|
||||||
end
|
end
|
||||||
return false
|
return false
|
||||||
end)
|
end)
|
||||||
return ret
|
return notifs
|
||||||
end
|
end
|
||||||
|
|
||||||
function N:layout()
|
function N:layout()
|
||||||
|
|
@ -491,4 +593,14 @@ function M.hide(id)
|
||||||
return notifier:hide(id)
|
return notifier:hide(id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param opts? snacks.notifier.history
|
||||||
|
function M.get_history(opts)
|
||||||
|
return notifier:get_history(opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param opts? snacks.notifier.history
|
||||||
|
function M.show_history(opts)
|
||||||
|
return notifier:show_history(opts)
|
||||||
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue