From 145d09c1bf5f3deedc48d745090a7ad8b7e2c08e Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Sun, 7 Jan 2024 12:17:42 +1100 Subject: [PATCH] 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 --- doc/nvim-tree-lua.txt | 3 - lua/nvim-tree.lua | 15 +++-- lua/nvim-tree/colors.lua | 25 ++++++++- lua/nvim-tree/renderer/builder.lua | 88 ++++++++++++++++++++++++++---- lua/nvim-tree/renderer/init.lua | 7 +-- lua/nvim-tree/view.lua | 22 +------- 6 files changed, 113 insertions(+), 47 deletions(-) diff --git a/doc/nvim-tree-lua.txt b/doc/nvim-tree-lua.txt index a7a4bd94ca3..f87c0d7a24b 100644 --- a/doc/nvim-tree-lua.txt +++ b/doc/nvim-tree-lua.txt @@ -2247,9 +2247,6 @@ as per |:highlight| Default linked group or definition follows name. -neovim 0.9 has a limit of two highlight groups per range. The two highest -priority groups as per |nvim-tree-opts-renderer| will be used. - Standard: > NvimTreeNormal Normal NvimTreeNormalFloat NormalFloat diff --git a/lua/nvim-tree.lua b/lua/nvim-tree.lua index 532d9ed4f98..a6ed2ac520a 100644 --- a/lua/nvim-tree.lua +++ b/lua/nvim-tree.lua @@ -104,12 +104,6 @@ function M.open_on_directory() actions.root.change_dir.force_dirchange(bufname, true) end -function M.reset_highlight() - colors.setup() - view.reset_winhl() - renderer.render_hl(view.get_bufnr()) -end - function M.place_cursor_on_node() local search = vim.fn.searchcount() if search and search.exact_match == 1 then @@ -168,8 +162,13 @@ local function setup_autocommands(opts) vim.api.nvim_create_autocmd(name, vim.tbl_extend("force", default_opts, custom_opts)) end - -- reset highlights when colorscheme is changed - create_nvim_tree_autocmd("ColorScheme", { callback = M.reset_highlight }) + -- reset and draw highlights when colorscheme is changed + create_nvim_tree_autocmd("ColorScheme", { + callback = function() + colors.setup() + renderer.render_hl(view.get_bufnr()) + end, + }) -- prevent new opened file from opening in the same window as nvim-tree create_nvim_tree_autocmd("BufWipeout", { diff --git a/lua/nvim-tree/colors.lua b/lua/nvim-tree/colors.lua index 2f3752da70e..f0b3f8b72bf 100644 --- a/lua/nvim-tree/colors.lua +++ b/lua/nvim-tree/colors.lua @@ -1,4 +1,7 @@ -local M = {} +local M = { + -- namespace for all tree window highlights + NS_ID = vim.api.nvim_create_namespace "nvim_tree", +} -- directly defined groups, please keep these to an absolute minimum local DEFAULT_DEFS = { @@ -122,6 +125,21 @@ local DEFAULT_LINKS = { NvimTreeDiagnosticHintFolderHL = "NvimTreeDiagnosticHintFileHL", } +-- namespace standard links +local NS_LINKS = { + EndOfBuffer = "NvimTreeEndOfBuffer", + CursorLine = "NvimTreeCursorLine", + CursorLineNr = "NvimTreeCursorLineNr", + LineNr = "NvimTreeLineNr", + WinSeparator = "NvimTreeWinSeparator", + StatusLine = "NvimTreeStatusLine", + StatusLineNC = "NvimTreeStatuslineNC", + SignColumn = "NvimTreeSignColumn", + Normal = "NvimTreeNormal", + NormalNC = "NvimTreeNormalNC", + NormalFloat = "NvimTreeNormalFloat", +} + -- nvim-tree highlight groups to legacy local LEGACY_LINKS = { NvimTreeModifiedIcon = "NvimTreeModifiedFile", @@ -189,6 +207,11 @@ function M.setup() for from, to in pairs(DEFAULT_LINKS) do vim.api.nvim_command("hi def link " .. from .. " " .. to) end + + -- window namespace; these don't appear to be cleared on colorscheme however err on the side of caution + for from, to in pairs(NS_LINKS) do + vim.api.nvim_set_hl(M.NS_ID, from, { link = to }) + end end return M diff --git a/lua/nvim-tree/renderer/builder.lua b/lua/nvim-tree/renderer/builder.lua index a3fa3ce2009..381f22336e5 100644 --- a/lua/nvim-tree/renderer/builder.lua +++ b/lua/nvim-tree/renderer/builder.lua @@ -1,3 +1,4 @@ +local colors = require "nvim-tree.colors" local utils = require "nvim-tree.utils" local notify = require "nvim-tree.notify" @@ -8,6 +9,7 @@ local icons = require "nvim-tree.renderer.components.icons" ---@field private index number ---@field private depth number ---@field private highlights table[] hl_group, line, col_start, col_end arguments for vim.api.nvim_buf_add_highlight +---@field private combined_groups boolean[] combined group names ---@field private lines string[] includes icons etc. ---@field private markers boolean[] indent markers ---@field private sign_names string[] line signs @@ -23,6 +25,7 @@ function Builder.new(root_cwd, decorators) index = 0, depth = 0, highlights = {}, + combined_groups = {}, lines = {}, markers = {}, sign_names = {}, @@ -75,15 +78,11 @@ function Builder:configure_group_name_modifier(group_name_modifier) end ---Insert ranged highlight groups into self.highlights ----neovim 0.9 is limited to two highlight groups for a range so choose the highest two ---@param groups string[] ---@param start number ---@param end_ number|nil function Builder:_insert_highlight(groups, start, end_) - local top_two_groups = {} - table.insert(top_two_groups, groups[#groups - 1]) - table.insert(top_two_groups, groups[#groups]) - table.insert(self.highlights, { top_two_groups, self.index, start, end_ or -1 }) + table.insert(self.highlights, { groups, self.index, start, end_ or -1 }) end function Builder:_insert_line(line) @@ -261,6 +260,76 @@ function Builder:_build_signs(node) end end +---Combined group name less than the 200 byte limit of highlight group names +---@param groups string[] highlight group names +---@return string name "NvimTreeCombinedHL" .. sha256 +function Builder:_combined_group_name(groups) + return string.format("NvimTreeCombinedHL%s", vim.fn.sha256(table.concat(groups))) +end + +---Create a highlight group for groups with later groups overriding previous. +---@param groups string[] highlight group names +function Builder:_create_combined_group(groups) + local combined_name = self:_combined_group_name(groups) + + -- only create if necessary + if not self.combined_groups[combined_name] then + local combined_hl = {} + + -- build the highlight, overriding values + for _, group in ipairs(groups) do + local hl = vim.api.nvim_get_hl(0, { name = group, link = false }) + combined_hl = vim.tbl_extend("force", combined_hl, hl) + end + + -- highlight directly in the namespace + vim.api.nvim_set_hl_ns_fast(colors.NS_ID) + vim.api.nvim_set_hl(colors.NS_ID, combined_name, combined_hl) + + self.combined_groups[combined_name] = true + end +end + +---Calculate highlight group for icon and name. A combined highlight group will be created +---when there is more than one highlight. +---A highlight group is always calculated and upserted for the case of highlights changing. +---@param node Node +---@return string|nil icon_hl_group +---@return string|nil name_hl_group +function Builder:_add_highlights(node) + -- result + local icon_hl_group, name_hl_group + + -- calculate all groups + local icon_groups = {} + local name_groups = {} + local d, icon, name + for i = #self.decorators, 1, -1 do + d = self.decorators[i] + icon, name = d:groups_icon_name(node) + table.insert(icon_groups, icon) + table.insert(name_groups, name) + end + + -- one or many icon groups + if #icon_groups > 1 then + icon_hl_group = self:_combined_group_name(icon_groups) + self:_create_combined_group(icon_groups) + else + icon_hl_group = icon_groups[1] + end + + -- one or many name groups + if #name_groups > 1 then + name_hl_group = self:_combined_group_name(name_groups) + self:_create_combined_group(name_groups) + else + name_hl_group = name_groups[1] + end + + return icon_hl_group, name_hl_group +end + function Builder:_build_line(node, idx, num_children) -- various components 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) end -- highighting - for i = #self.decorators, 1, -1 do - local d = self.decorators[i] - local icon_group, name_group = d:groups_icon_name(node) - table.insert(icon.hl, icon_group) - table.insert(name.hl, name_group) - end + local icon_hl_group, name_hl_group = self:_add_highlights(node) + table.insert(icon.hl, icon_hl_group) + table.insert(name.hl, name_hl_group) local line = self:_format_line(indent_markers, arrows, icon, name, node) self:_insert_line(self:_unwrap_highlighted_strings(line)) diff --git a/lua/nvim-tree/renderer/init.lua b/lua/nvim-tree/renderer/init.lua index c275dcce79e..13b90824315 100644 --- a/lua/nvim-tree/renderer/init.lua +++ b/lua/nvim-tree/renderer/init.lua @@ -1,3 +1,4 @@ +local colors = require "nvim-tree.colors" local core = require "nvim-tree.core" local log = require "nvim-tree.log" local view = require "nvim-tree.view" @@ -24,8 +25,6 @@ local M = { local SIGN_GROUP = "NvimTreeRendererSigns" -local namespace_id = vim.api.nvim_create_namespace "NvimTreeHighlights" - local function _draw(bufnr, lines, hl, sign_names) vim.api.nvim_buf_set_option(bufnr, "modifiable", true) vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) @@ -41,11 +40,11 @@ function M.render_hl(bufnr, hl) if not bufnr or not vim.api.nvim_buf_is_loaded(bufnr) then return end - vim.api.nvim_buf_clear_namespace(bufnr, namespace_id, 0, -1) + vim.api.nvim_buf_clear_namespace(bufnr, colors.NS_ID, 0, -1) for _, data in ipairs(hl or M.last_highlights) do if type(data[1]) == "table" then for _, group in ipairs(data[1]) do - vim.api.nvim_buf_add_highlight(bufnr, namespace_id, group, data[2], data[3], data[4]) + vim.api.nvim_buf_add_highlight(bufnr, colors.NS_ID, group, data[2], data[3], data[4]) end end end diff --git a/lua/nvim-tree/view.lua b/lua/nvim-tree/view.lua index 81fe36e96c7..25383c4e978 100644 --- a/lua/nvim-tree/view.lua +++ b/lua/nvim-tree/view.lua @@ -1,3 +1,4 @@ +local colors = require "nvim-tree.colors" local events = require "nvim-tree.events" local utils = require "nvim-tree.utils" local log = require "nvim-tree.log" @@ -38,19 +39,6 @@ M.View = { cursorlineopt = "both", colorcolumn = "0", wrap = false, - winhl = table.concat({ - "EndOfBuffer:NvimTreeEndOfBuffer", - "CursorLine:NvimTreeCursorLine", - "CursorLineNr:NvimTreeCursorLineNr", - "LineNr:NvimTreeLineNr", - "WinSeparator:NvimTreeWinSeparator", - "StatusLine:NvimTreeStatusLine", - "StatusLineNC:NvimTreeStatuslineNC", - "SignColumn:NvimTreeSignColumn", - "Normal:NvimTreeNormal", - "NormalNC:NvimTreeNormalNC", - "NormalFloat:NvimTreeNormalFloat", - }, ","), }, } @@ -147,6 +135,7 @@ local function set_window_options_and_buffer() vim.opt_local[k] = v end vim.opt.eventignore = eventignore + vim.api.nvim_win_set_hl_ns(0, colors.NS_ID) end ---@return table @@ -539,13 +528,6 @@ function M.is_root_folder_visible(cwd) return cwd ~= "/" and not M.View.hide_root_folder end --- used on ColorScheme event -function M.reset_winhl() - if M.get_winnr() and vim.api.nvim_win_is_valid(M.get_winnr()) then - vim.wo[M.get_winnr()].winhl = M.View.winopts.winhl - end -end - function M.setup(opts) local options = opts.view or {} M.View.centralize_selection = options.centralize_selection