Skip to content

Commit

Permalink
feat: deduplicate repeated messages (close #162)
Browse files Browse the repository at this point in the history
  • Loading branch information
j-hui committed Jan 8, 2024
1 parent 0fb9e3f commit 09f0c91
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 13 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,10 @@ Available options are shown below:
group_separator = "---", -- Separator between notification groups
group_separator_hl = -- Highlight group used for group separator
"Comment",
render_message = -- How to render notification messages
function(msg, cnt)
return cnt == 1 and msg or string.format("(%dx) %s", cnt, msg)
end,
},

-- Options related to the notification window and buffer
Expand Down
43 changes: 41 additions & 2 deletions lua/fidget/notification.lua
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ local logger = require("fidget.logger")
--- Notification element containing a message and optional annotation.
---
---@class Item
---@field key Key Used to distinguish this item from others
---@field key Key Identity of this item (for in-place updates)
---@field content_key Key What to deduplicate items by (do not deduplicate if `nil`)
---@field message string Displayed message for the item
---@field annote string|nil Optional title that accompanies the message
---@field style string Style used to render the annote/title, if any
Expand Down Expand Up @@ -121,6 +122,11 @@ local state = {
--- :lua print(vim.inspect(require("fidget.notification").default_config))
---<
---
--- Note that the default `update_hook` function performs a few book-keeping
--- tasks, e.g., calling |fidget.notification.set_content_key| to keep its
--- `content_key` up to date. You may want to do the same if writing your own;
--- check the source code to see what it's doing.
---
--- See also:~
--- |fidget.notification.Config|
---
Expand All @@ -140,9 +146,42 @@ notification.default_config = {
info_annote = "INFO",
warn_annote = "WARN",
error_annote = "ERROR",
update_hook = false,
update_hook = function(item)
notification.set_content_key(item)
end,
}

--- Sets a |fidget.notification.Item|'s `content_key`, for deduplication.
---
--- This default implementation sets an item's `content_key` to its `message`,
--- appended with its `annote` (or a null byte if it has no `annote`), a rough
--- "hash" of its contents. You can write your own `update_hook` that "hashes"
--- the message differently, e.g., only considering the `message`, or taking the
--- `data` or style fields into account.
---
--- If you would like to disable message deduplication, don't call this
--- function, leaving the `content_key` field as `nil`. Assuming you're not
--- using the `update_hook` for anything else, you can achieve this by simply
--- the option to `false`, e.g.:
---
--->lua
--- { -- In options table
--- notification = {
--- configs = {
--- -- Opt out of deduplication by default, i.e., in default config
--- default = vim.tbl_extend("force", require('fidget.notification').default_config, {
--- update_hook = false,
--- },
--- },
--- },
--- }
---<
---
---@param item Item
function notification.set_content_key(item)
item.content_key = item.message .. " " .. (item.annote and item.annote or string.char(0))
end

---@options notification [[
---@protected
--- Notification options
Expand Down
63 changes: 52 additions & 11 deletions lua/fidget/notification/view.lua
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,23 @@ M.options = {
---
---@type string|false
group_separator_hl = "Comment",

--- How to render notification messages
---
--- Messages that appear multiple times (have the same `content_key`) will
--- only be rendered once, with a `cnt` greater than 1. This hook provides an
--- opportunity to customize how such messages should appear.
---
--- Note that if this returns an empty string, the notification will not be
--- rendered.
---
--- See also:~
--- |fidget.notification.Config|
--- |fidget.notification.default_config|
--- |fidget.notification.set_content_key|
---
---@type fun(msg: string, cnt: number): string
render_message = function(msg, cnt) return cnt == 1 and msg or string.format("(%dx) %s", cnt, msg) end,
}
---@options ]]

Expand Down Expand Up @@ -160,15 +177,17 @@ end
---
---@param item Item
---@param config Config
---@param count number
---@return NotificationRenderItem|nil render_item
function M.render_item(item, config)
function M.render_item(item, config, count)
if item.hidden then
return nil
end

local lines, highlights = {}, {}

for line in vim.gsplit(item.message, "\n", { plain = true, trimempty = true }) do
local msg = M.options.render_message(item.message, count)
for line in vim.gsplit(msg, "\n", { plain = true, trimempty = true }) do
table.insert(lines, line)
end

Expand All @@ -178,16 +197,17 @@ function M.render_item(item, config)
end

if item.annote then
local msg = lines[1] -- Append annote to first line of message
local line1 = lines[1] -- Append annote to first line
local col_start = #line1
local sep = config.annote_separator or " "
local line = string.format("%s%s%s", msg, sep, item.annote)
lines[1] = line
line1 = string.format("%s%s%s", line1, sep, item.annote)
lines[1] = line1

-- Insert highlight for annote
table.insert(highlights, {
hl_group = item.style,
line = 0, -- 0-indexed
col_start = #msg, -- byte-indexed
line = 0, -- 0-indexed
col_start = col_start, -- byte-indexed
col_end = -1,
})
end
Expand Down Expand Up @@ -217,16 +237,37 @@ function M.render(now, groups)
table.insert(render_items, group_header)
end

local counts = {}
for _, item in ipairs(group.items) do
local content_key = item.content_key
if content_key ~= nil then
if counts[content_key] then
counts[content_key] = counts[content_key] + 1
else
counts[content_key] = 1
end
end
end

local i = 1
for _, item in ipairs(group.items) do
if group.config.render_limit and i > group.config.render_limit then
-- Don't bother rendering the rest (though they still exist)
break
end
local render_item = M.render_item(item, group.config)
if render_item then
table.insert(render_items, render_item)
i = i + 1
local content_key = item.content_key
if content_key == nil or counts[content_key] then
local count = 1
if content_key ~= nil then
count = counts[content_key]
counts[content_key] = nil
end

local render_item = M.render_item(item, group.config, count)
if render_item then
table.insert(render_items, render_item)
i = i + 1
end
end
end
end
Expand Down

0 comments on commit 09f0c91

Please sign in to comment.