Skip to content

Commit 6f298f2

Browse files
authored
feat: treesitter highlights (#86)
1 parent 2c8f744 commit 6f298f2

File tree

9 files changed

+167
-52
lines changed

9 files changed

+167
-52
lines changed

README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ A fancy, configurable, notification manager for NeoVim
66

77
Credit to [sunjon](https://github.com/sunjon) for [the design](https://neovim.discourse.group/t/wip-animated-notifications-plugin/448) that inspired the appearance of this plugin.
88

9+
* [Usage](#usage)
10+
- [Viewing History](#viewing-history)
11+
* [Configuration](#configuration)
12+
- [Setup](#setup)
13+
- [Highlights](#highlights)
14+
- [Render Style](#render-style)
15+
- [Animation Style](#animation-style)
16+
+ [Opening the window](#opening-the-window)
17+
+ [Changing the window](#changing-the-window)
18+
919
## Usage
1020

1121
Simply call the module with a message!
@@ -31,6 +41,10 @@ Updating an existing notification is also possible!
3141
![](https://user-images.githubusercontent.com/24252670/152641078-92f3da72-f49f-4705-aec8-86512693445f.gif)
3242

3343

44+
Use treesitter highlighting inside notifications with opacity changing
45+
46+
![](https://user-images.githubusercontent.com/24252670/165042795-565878a3-9c6d-4c0b-ab0d-6858515835c5.gif)
47+
3448
There are a number of custom options that can be supplied in a table as the third argument.
3549
See `:h NotifyOptions` for details.
3650

@@ -71,6 +85,19 @@ async.run(function()
7185
end)
7286
```
7387

88+
Set a custom filetype to take advantage of treesitter highlighting:
89+
90+
```lua
91+
vim.notify(text, "info", {
92+
title = "My Awesome Plugin",
93+
on_open = function(win)
94+
local buf = vim.api.nvim_win_get_buf(win)
95+
vim.api.nvim_buf_set_option(buf, "filetype", "markdown")
96+
end,
97+
})
98+
```
99+
100+
74101
Check out the wiki for more examples!
75102

76103
### Viewing History

lua/notify/config/init.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ function M.level()
102102
end
103103

104104
function M.background_colour()
105-
return user_config.background_colour()
105+
return tonumber(user_config.background_colour():gsub("#", "0x"), 16)
106106
end
107107

108108
function M.icons()

lua/notify/init.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ end
199199
---Dismiss all notification windows currently displayed
200200
---@param opts table
201201
---@field pending boolean: Clear pending notifications
202+
---@field silent boolean: Suppress notification that pending notifications were dismissed.
202203
function notify.dismiss(opts)
203204
if service then
204205
service:dismiss(opts or {})

lua/notify/render/default.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,6 @@ return function(bufnr, notif, highlights)
5050
hl_group = highlights.body,
5151
end_line = 1 + #notif.message,
5252
end_col = #notif.message[#notif.message],
53+
priority = 50, -- Allow treesitter to override
5354
})
5455
end

lua/notify/render/minimal.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@ return function(bufnr, notif, highlights)
99
hl_group = highlights.icon,
1010
end_line = #notif.message - 1,
1111
end_col = #notif.message[#notif.message],
12+
priority = 50,
1213
})
1314
end

lua/notify/service/buffer/highlights.lua

Lines changed: 123 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
local config = require("notify.config")
2+
23
local util = require("notify.util")
34

45
---@class NotifyBufHighlights
@@ -8,13 +9,25 @@ local util = require("notify.util")
89
---@field border string
910
---@field icon string
1011
---@field body string
12+
---@field buffer number
1113
local NotifyBufHighlights = {}
1214

13-
local function group_fields(group)
14-
return {
15-
guifg = vim.fn.synIDattr(vim.fn.synIDtrans(vim.fn.hlID(group)), "fg#"),
16-
guibg = vim.fn.synIDattr(vim.fn.synIDtrans(vim.fn.hlID(group)), "bg#"),
15+
local function manual_get_hl(name)
16+
local synID = vim.fn.synIDtrans(vim.fn.hlID(name))
17+
local result = {
18+
foreground = tonumber(vim.fn.synIDattr(synID, "fg"):gsub("#", ""), 16),
19+
background = tonumber(vim.fn.synIDattr(synID, "bg"):gsub("#", ""), 16),
1720
}
21+
return result
22+
end
23+
24+
local function get_hl(name)
25+
local definition = vim.api.nvim_get_hl_by_name(name, true)
26+
if definition[true] then
27+
-- https://github.com/neovim/neovim/issues/18024
28+
return manual_get_hl(name)
29+
end
30+
return definition
1831
end
1932

2033
function NotifyBufHighlights:new(level, buffer)
@@ -24,42 +37,132 @@ function NotifyBufHighlights:new(level, buffer)
2437
orig = "NotifyINFO" .. section
2538
end
2639
local new = orig .. buffer
27-
vim.cmd("silent! hi! link " .. new .. " " .. orig)
28-
return new
29-
end
30-
local title = linked_group("Title")
31-
local border = linked_group("Border")
32-
local body = linked_group("Body")
33-
local icon = linked_group("Icon")
34-
35-
local groups = {}
36-
for _, group in pairs({ title, border, body, icon }) do
37-
groups[group] = group_fields(group)
40+
41+
vim.api.nvim_set_hl(0, new, { link = orig })
42+
43+
return new, get_hl(new)
3844
end
45+
46+
local title, title_def = linked_group("Title")
47+
local border, border_def = linked_group("Border")
48+
local body, body_def = linked_group("Body")
49+
local icon, icon_def = linked_group("Icon")
50+
51+
local groups = {
52+
[title] = title_def,
53+
[border] = border_def,
54+
[body] = body_def,
55+
[icon] = icon_def,
56+
}
3957
local buf_highlights = {
4058
groups = groups,
4159
opacity = 100,
4260
border = border,
4361
body = body,
4462
title = title,
4563
icon = icon,
64+
buffer = buffer,
4665
}
4766
self.__index = self
4867
setmetatable(buf_highlights, self)
4968
return buf_highlights
5069
end
5170

71+
function NotifyBufHighlights:_redefine_treesitter()
72+
local buf_highlighter = require("vim.treesitter.highlighter").active[self.buffer]
73+
74+
if not buf_highlighter then
75+
return
76+
end
77+
local render_namespace = vim.api.nvim_create_namespace("notify-treesitter-override")
78+
vim.api.nvim_buf_clear_namespace(self.buffer, render_namespace, 0, -1)
79+
80+
local function link(orig)
81+
local new = orig .. self.buffer
82+
if self.groups[new] then
83+
return new
84+
end
85+
vim.api.nvim_set_hl(0, new, { link = orig })
86+
self.groups[new] = get_hl(new)
87+
return new
88+
end
89+
90+
local matches = {}
91+
92+
local i = 0
93+
buf_highlighter.tree:for_each_tree(function(tstree, tree)
94+
if not tstree then
95+
return
96+
end
97+
98+
local root = tstree:root()
99+
100+
local query = buf_highlighter:get_query(tree:lang())
101+
102+
-- Some injected languages may not have highlight queries.
103+
if not query:query() then
104+
return
105+
end
106+
107+
local iter = query:query():iter_captures(root, buf_highlighter.bufnr)
108+
109+
for capture, node, metadata in iter do
110+
-- Wait until we get at least a single capture as we don't know when parsing is complete.
111+
self._treesitter_redefined = true
112+
local hl = query.hl_cache[capture]
113+
114+
if hl then
115+
i = i + 1
116+
local c = query._query.captures[capture] -- name of the capture in the query
117+
if c ~= nil then
118+
local general_hl, is_vim_hl = query:_get_hl_from_capture(capture)
119+
local capture_hl = is_vim_hl and general_hl or (tree:lang() .. general_hl)
120+
local start_row, start_col, end_row, end_col = node:range()
121+
local custom_hl = link(capture_hl)
122+
123+
vim.api.nvim_buf_set_extmark(self.buffer, render_namespace, start_row, start_col, {
124+
end_row = end_row,
125+
end_col = end_col,
126+
hl_group = custom_hl,
127+
-- TODO: Not sure how neovim's highlighter doesn't have issues with overriding highlights
128+
-- Three marks on same region always show the second for some reason AFAICT
129+
priority = metadata.priority or i + 200,
130+
conceal = metadata.conceal,
131+
})
132+
end
133+
end
134+
end
135+
end, true)
136+
return matches
137+
end
138+
52139
function NotifyBufHighlights:set_opacity(alpha)
140+
if
141+
not self._treesitter_redefined
142+
and vim.api.nvim_buf_get_option(self.buffer, "filetype") ~= "notify"
143+
then
144+
self:_redefine_treesitter()
145+
end
53146
self.opacity = alpha
54147
local background = config.background_colour()
55148
for group, fields in pairs(self.groups) do
56149
local updated_fields = {}
57-
for name, value in pairs(fields) do
58-
if value ~= "" and value ~= "none" then
59-
updated_fields[name] = util.blend(value, background, alpha / 100)
60-
end
150+
vim.api.nvim_set_hl(0, group, updated_fields)
151+
local hl_string = ""
152+
if fields.foreground then
153+
hl_string = "guifg=#"
154+
.. string.format("%06x", util.blend(fields.foreground, background, alpha / 100))
155+
end
156+
if fields.background then
157+
hl_string = hl_string
158+
.. " guibg=#"
159+
.. string.format("%06x", util.blend(fields.background, background, alpha / 100))
160+
end
161+
162+
if hl_string ~= "" then
163+
-- Can't use nvim_set_hl https://github.com/neovim/neovim/issues/18160
164+
vim.cmd("hi " .. group .. " " .. hl_string)
61165
end
62-
util.highlight(group, updated_fields)
63166
end
64167
end
65168

lua/notify/service/init.lua

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,7 @@ function NotificationService:replace(id, notif)
7878
end
7979

8080
function NotificationService:dismiss(opts)
81-
local bufs = vim.api.nvim_list_bufs()
82-
local notif_wins = {}
83-
for _, buf in pairs(bufs) do
84-
local win = vim.fn.bufwinid(buf)
85-
if win ~= -1 and vim.api.nvim_buf_get_option(buf, "filetype") == "notify" then
86-
notif_wins[#notif_wins + 1] = win
87-
end
88-
end
81+
local notif_wins = vim.tbl_keys(self._animator.win_stages)
8982
for _, win in pairs(notif_wins) do
9083
pcall(vim.api.nvim_win_close, win, true)
9184
end

lua/notify/util/init.lua

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
local M = {}
22

3+
local min, max, floor = math.min, math.max, math.floor
4+
local rshift, lshift, band, bor = bit.rshift, bit.lshift, bit.band, bit.bor
35
function M.is_callable(obj)
46
return type(obj) == "function" or (type(obj) == "table" and obj.__call)
57
end
@@ -27,32 +29,18 @@ function M.pop(tbl, key, default)
2729
return val
2830
end
2931

30-
function M.crop(val, min, max)
31-
return math.min(math.max(min, val), max)
32-
end
33-
34-
function M.zip(first, second)
35-
local new = {}
36-
for i, val in pairs(first) do
37-
new[i] = { val, second[i] }
38-
end
39-
return new
40-
end
41-
42-
local function split_hex_colour(hex)
43-
hex = hex:gsub("#", "")
44-
return { tonumber(hex:sub(1, 2), 16), tonumber(hex:sub(3, 4), 16), tonumber(hex:sub(5, 6), 16) }
45-
end
46-
4732
function M.blend(fg_hex, bg_hex, alpha)
48-
local channels = M.zip(split_hex_colour(fg_hex), split_hex_colour(bg_hex))
49-
50-
local blended = {}
51-
for i, i_chans in pairs(channels) do
52-
blended[i] = M.round(M.crop(alpha * i_chans[1] + (1 - alpha) * i_chans[2], 0, 255))
33+
local segment = 0xFF0000
34+
local result = 0
35+
for i = 2, 0, -1 do
36+
local blended = alpha * rshift(band(fg_hex, segment), i * 8)
37+
+ (1 - alpha) * rshift(band(bg_hex, segment), i * 8)
38+
39+
result = bor(lshift(result, 8), floor((min(max(blended, 0), 255)) + 0.5))
40+
segment = rshift(segment, 8)
5341
end
5442

55-
return string.format("#%02x%02x%02x", unpack(blended))
43+
return result
5644
end
5745

5846
function M.round(num, decimals)

tests/init.vim

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
set rtp+=.
22
set rtp+=../plenary.nvim
3+
set termguicolors
34
runtime! plugin/plenary.vim

0 commit comments

Comments
 (0)