Skip to content

Commit 145d09c

Browse files
authored
feat(#2415): combined hl groups (#2601)
* feat(#2415): create combined highlight groups * feat(#2415): create combined highlight groups * feat(#2415): create combined highlight groups * ci: allow workflow_dispatch (#2620) * one and only one hl namespace, required winhl removal
1 parent 11c0720 commit 145d09c

File tree

6 files changed

+113
-47
lines changed

6 files changed

+113
-47
lines changed

doc/nvim-tree-lua.txt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2247,9 +2247,6 @@ as per |:highlight|
22472247

22482248
Default linked group or definition follows name.
22492249

2250-
neovim 0.9 has a limit of two highlight groups per range. The two highest
2251-
priority groups as per |nvim-tree-opts-renderer| will be used.
2252-
22532250
Standard: >
22542251
NvimTreeNormal Normal
22552252
NvimTreeNormalFloat NormalFloat

lua/nvim-tree.lua

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -104,12 +104,6 @@ function M.open_on_directory()
104104
actions.root.change_dir.force_dirchange(bufname, true)
105105
end
106106

107-
function M.reset_highlight()
108-
colors.setup()
109-
view.reset_winhl()
110-
renderer.render_hl(view.get_bufnr())
111-
end
112-
113107
function M.place_cursor_on_node()
114108
local search = vim.fn.searchcount()
115109
if search and search.exact_match == 1 then
@@ -168,8 +162,13 @@ local function setup_autocommands(opts)
168162
vim.api.nvim_create_autocmd(name, vim.tbl_extend("force", default_opts, custom_opts))
169163
end
170164

171-
-- reset highlights when colorscheme is changed
172-
create_nvim_tree_autocmd("ColorScheme", { callback = M.reset_highlight })
165+
-- reset and draw highlights when colorscheme is changed
166+
create_nvim_tree_autocmd("ColorScheme", {
167+
callback = function()
168+
colors.setup()
169+
renderer.render_hl(view.get_bufnr())
170+
end,
171+
})
173172

174173
-- prevent new opened file from opening in the same window as nvim-tree
175174
create_nvim_tree_autocmd("BufWipeout", {

lua/nvim-tree/colors.lua

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
local M = {}
1+
local M = {
2+
-- namespace for all tree window highlights
3+
NS_ID = vim.api.nvim_create_namespace "nvim_tree",
4+
}
25

36
-- directly defined groups, please keep these to an absolute minimum
47
local DEFAULT_DEFS = {
@@ -122,6 +125,21 @@ local DEFAULT_LINKS = {
122125
NvimTreeDiagnosticHintFolderHL = "NvimTreeDiagnosticHintFileHL",
123126
}
124127

128+
-- namespace standard links
129+
local NS_LINKS = {
130+
EndOfBuffer = "NvimTreeEndOfBuffer",
131+
CursorLine = "NvimTreeCursorLine",
132+
CursorLineNr = "NvimTreeCursorLineNr",
133+
LineNr = "NvimTreeLineNr",
134+
WinSeparator = "NvimTreeWinSeparator",
135+
StatusLine = "NvimTreeStatusLine",
136+
StatusLineNC = "NvimTreeStatuslineNC",
137+
SignColumn = "NvimTreeSignColumn",
138+
Normal = "NvimTreeNormal",
139+
NormalNC = "NvimTreeNormalNC",
140+
NormalFloat = "NvimTreeNormalFloat",
141+
}
142+
125143
-- nvim-tree highlight groups to legacy
126144
local LEGACY_LINKS = {
127145
NvimTreeModifiedIcon = "NvimTreeModifiedFile",
@@ -189,6 +207,11 @@ function M.setup()
189207
for from, to in pairs(DEFAULT_LINKS) do
190208
vim.api.nvim_command("hi def link " .. from .. " " .. to)
191209
end
210+
211+
-- window namespace; these don't appear to be cleared on colorscheme however err on the side of caution
212+
for from, to in pairs(NS_LINKS) do
213+
vim.api.nvim_set_hl(M.NS_ID, from, { link = to })
214+
end
192215
end
193216

194217
return M

lua/nvim-tree/renderer/builder.lua

Lines changed: 77 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
local colors = require "nvim-tree.colors"
12
local utils = require "nvim-tree.utils"
23
local notify = require "nvim-tree.notify"
34

@@ -8,6 +9,7 @@ local icons = require "nvim-tree.renderer.components.icons"
89
---@field private index number
910
---@field private depth number
1011
---@field private highlights table[] hl_group, line, col_start, col_end arguments for vim.api.nvim_buf_add_highlight
12+
---@field private combined_groups boolean[] combined group names
1113
---@field private lines string[] includes icons etc.
1214
---@field private markers boolean[] indent markers
1315
---@field private sign_names string[] line signs
@@ -23,6 +25,7 @@ function Builder.new(root_cwd, decorators)
2325
index = 0,
2426
depth = 0,
2527
highlights = {},
28+
combined_groups = {},
2629
lines = {},
2730
markers = {},
2831
sign_names = {},
@@ -75,15 +78,11 @@ function Builder:configure_group_name_modifier(group_name_modifier)
7578
end
7679

7780
---Insert ranged highlight groups into self.highlights
78-
---neovim 0.9 is limited to two highlight groups for a range so choose the highest two
7981
---@param groups string[]
8082
---@param start number
8183
---@param end_ number|nil
8284
function Builder:_insert_highlight(groups, start, end_)
83-
local top_two_groups = {}
84-
table.insert(top_two_groups, groups[#groups - 1])
85-
table.insert(top_two_groups, groups[#groups])
86-
table.insert(self.highlights, { top_two_groups, self.index, start, end_ or -1 })
85+
table.insert(self.highlights, { groups, self.index, start, end_ or -1 })
8786
end
8887

8988
function Builder:_insert_line(line)
@@ -261,6 +260,76 @@ function Builder:_build_signs(node)
261260
end
262261
end
263262

263+
---Combined group name less than the 200 byte limit of highlight group names
264+
---@param groups string[] highlight group names
265+
---@return string name "NvimTreeCombinedHL" .. sha256
266+
function Builder:_combined_group_name(groups)
267+
return string.format("NvimTreeCombinedHL%s", vim.fn.sha256(table.concat(groups)))
268+
end
269+
270+
---Create a highlight group for groups with later groups overriding previous.
271+
---@param groups string[] highlight group names
272+
function Builder:_create_combined_group(groups)
273+
local combined_name = self:_combined_group_name(groups)
274+
275+
-- only create if necessary
276+
if not self.combined_groups[combined_name] then
277+
local combined_hl = {}
278+
279+
-- build the highlight, overriding values
280+
for _, group in ipairs(groups) do
281+
local hl = vim.api.nvim_get_hl(0, { name = group, link = false })
282+
combined_hl = vim.tbl_extend("force", combined_hl, hl)
283+
end
284+
285+
-- highlight directly in the namespace
286+
vim.api.nvim_set_hl_ns_fast(colors.NS_ID)
287+
vim.api.nvim_set_hl(colors.NS_ID, combined_name, combined_hl)
288+
289+
self.combined_groups[combined_name] = true
290+
end
291+
end
292+
293+
---Calculate highlight group for icon and name. A combined highlight group will be created
294+
---when there is more than one highlight.
295+
---A highlight group is always calculated and upserted for the case of highlights changing.
296+
---@param node Node
297+
---@return string|nil icon_hl_group
298+
---@return string|nil name_hl_group
299+
function Builder:_add_highlights(node)
300+
-- result
301+
local icon_hl_group, name_hl_group
302+
303+
-- calculate all groups
304+
local icon_groups = {}
305+
local name_groups = {}
306+
local d, icon, name
307+
for i = #self.decorators, 1, -1 do
308+
d = self.decorators[i]
309+
icon, name = d:groups_icon_name(node)
310+
table.insert(icon_groups, icon)
311+
table.insert(name_groups, name)
312+
end
313+
314+
-- one or many icon groups
315+
if #icon_groups > 1 then
316+
icon_hl_group = self:_combined_group_name(icon_groups)
317+
self:_create_combined_group(icon_groups)
318+
else
319+
icon_hl_group = icon_groups[1]
320+
end
321+
322+
-- one or many name groups
323+
if #name_groups > 1 then
324+
name_hl_group = self:_combined_group_name(name_groups)
325+
self:_create_combined_group(name_groups)
326+
else
327+
name_hl_group = name_groups[1]
328+
end
329+
330+
return icon_hl_group, name_hl_group
331+
end
332+
264333
function Builder:_build_line(node, idx, num_children)
265334
-- various components
266335
local indent_markers = pad.get_indent_markers(self.depth, idx, num_children, node, self.markers)
@@ -279,12 +348,9 @@ function Builder:_build_line(node, idx, num_children)
279348
end
280349

281350
-- highighting
282-
for i = #self.decorators, 1, -1 do
283-
local d = self.decorators[i]
284-
local icon_group, name_group = d:groups_icon_name(node)
285-
table.insert(icon.hl, icon_group)
286-
table.insert(name.hl, name_group)
287-
end
351+
local icon_hl_group, name_hl_group = self:_add_highlights(node)
352+
table.insert(icon.hl, icon_hl_group)
353+
table.insert(name.hl, name_hl_group)
288354

289355
local line = self:_format_line(indent_markers, arrows, icon, name, node)
290356
self:_insert_line(self:_unwrap_highlighted_strings(line))

lua/nvim-tree/renderer/init.lua

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
local colors = require "nvim-tree.colors"
12
local core = require "nvim-tree.core"
23
local log = require "nvim-tree.log"
34
local view = require "nvim-tree.view"
@@ -24,8 +25,6 @@ local M = {
2425

2526
local SIGN_GROUP = "NvimTreeRendererSigns"
2627

27-
local namespace_id = vim.api.nvim_create_namespace "NvimTreeHighlights"
28-
2928
local function _draw(bufnr, lines, hl, sign_names)
3029
vim.api.nvim_buf_set_option(bufnr, "modifiable", true)
3130
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
@@ -41,11 +40,11 @@ function M.render_hl(bufnr, hl)
4140
if not bufnr or not vim.api.nvim_buf_is_loaded(bufnr) then
4241
return
4342
end
44-
vim.api.nvim_buf_clear_namespace(bufnr, namespace_id, 0, -1)
43+
vim.api.nvim_buf_clear_namespace(bufnr, colors.NS_ID, 0, -1)
4544
for _, data in ipairs(hl or M.last_highlights) do
4645
if type(data[1]) == "table" then
4746
for _, group in ipairs(data[1]) do
48-
vim.api.nvim_buf_add_highlight(bufnr, namespace_id, group, data[2], data[3], data[4])
47+
vim.api.nvim_buf_add_highlight(bufnr, colors.NS_ID, group, data[2], data[3], data[4])
4948
end
5049
end
5150
end

lua/nvim-tree/view.lua

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
local colors = require "nvim-tree.colors"
12
local events = require "nvim-tree.events"
23
local utils = require "nvim-tree.utils"
34
local log = require "nvim-tree.log"
@@ -38,19 +39,6 @@ M.View = {
3839
cursorlineopt = "both",
3940
colorcolumn = "0",
4041
wrap = false,
41-
winhl = table.concat({
42-
"EndOfBuffer:NvimTreeEndOfBuffer",
43-
"CursorLine:NvimTreeCursorLine",
44-
"CursorLineNr:NvimTreeCursorLineNr",
45-
"LineNr:NvimTreeLineNr",
46-
"WinSeparator:NvimTreeWinSeparator",
47-
"StatusLine:NvimTreeStatusLine",
48-
"StatusLineNC:NvimTreeStatuslineNC",
49-
"SignColumn:NvimTreeSignColumn",
50-
"Normal:NvimTreeNormal",
51-
"NormalNC:NvimTreeNormalNC",
52-
"NormalFloat:NvimTreeNormalFloat",
53-
}, ","),
5442
},
5543
}
5644

@@ -147,6 +135,7 @@ local function set_window_options_and_buffer()
147135
vim.opt_local[k] = v
148136
end
149137
vim.opt.eventignore = eventignore
138+
vim.api.nvim_win_set_hl_ns(0, colors.NS_ID)
150139
end
151140

152141
---@return table
@@ -539,13 +528,6 @@ function M.is_root_folder_visible(cwd)
539528
return cwd ~= "/" and not M.View.hide_root_folder
540529
end
541530

542-
-- used on ColorScheme event
543-
function M.reset_winhl()
544-
if M.get_winnr() and vim.api.nvim_win_is_valid(M.get_winnr()) then
545-
vim.wo[M.get_winnr()].winhl = M.View.winopts.winhl
546-
end
547-
end
548-
549531
function M.setup(opts)
550532
local options = opts.view or {}
551533
M.View.centralize_selection = options.centralize_selection

0 commit comments

Comments
 (0)