diff --git a/doc/nvim-tree-lua.txt b/doc/nvim-tree-lua.txt index ea74d25d1a0..bdf11e63306 100644 --- a/doc/nvim-tree-lua.txt +++ b/doc/nvim-tree-lua.txt @@ -893,7 +893,7 @@ Configuration options for tree indent markers. Configuration options for icons. Icon sign column precedence: - diagnostics > modified > git > bookmarked + diagnostics > bookmarked > modified > git *nvim-tree.renderer.icons.web_devicons* Configure optional plugin `"nvim-tree/nvim-web-devicons"` diff --git a/lua/nvim-tree/renderer/builder.lua b/lua/nvim-tree/renderer/builder.lua index f6b05511802..982eeac9c76 100644 --- a/lua/nvim-tree/renderer/builder.lua +++ b/lua/nvim-tree/renderer/builder.lua @@ -5,26 +5,23 @@ local notify = require "nvim-tree.notify" local pad = require "nvim-tree.renderer.components.padding" local icons = require "nvim-tree.renderer.components.icons" -local HL_POSITION = require("nvim-tree.enum").HL_POSITION -local ICON_PLACEMENT = require("nvim-tree.enum").ICON_PLACEMENT - --- @class Builder ---- @field decorators Decorator[] +--- @field deco Decorator[] local Builder = {} Builder.__index = Builder local DEFAULT_ROOT_FOLDER_LABEL = ":~:s?$?/..?" -function Builder.new(root_cwd, decorators) +function Builder.new(root_cwd, deco) return setmetatable({ index = 0, depth = 0, highlights = {}, lines = {}, markers = {}, - signs = {}, + sign_names = {}, root_cwd = root_cwd, - decorators = decorators, + deco = deco, }, Builder) end @@ -195,95 +192,18 @@ function Builder:_build_file(node) return icon, { str = node.name, hl = { hl } } end ----@param node table ----@return HighlightedString[]|nil icon -function Builder:_get_git_icons(node) - local git_icons = self.decorators.git:get_icons(node) - if git_icons and #git_icons > 0 and self.decorators.git.icon_placement == ICON_PLACEMENT.signcolumn then - table.insert(self.signs, { - sign = git_icons[1].hl[1], - lnum = self.index + 1, - priority = 1, - }) - git_icons = nil - end - return git_icons -end - ----@param node table ----@return HighlightedString[]|nil icon -function Builder:_get_diagnostics_icon(node) - local diagnostics_icon = self.decorators.diagnostics:get_icon(node) - if diagnostics_icon and self.decorators.diagnostics.icon_placement == ICON_PLACEMENT.signcolumn then - table.insert(self.signs, { - sign = diagnostics_icon.hl[1], - lnum = self.index + 1, - priority = 2, - }) - diagnostics_icon = nil - end - return diagnostics_icon -end - ----@param node table ----@return HighlightedString|nil icon -function Builder:_get_modified_icon(node) - local modified_icon = self.decorators.modified:get_icon(node) - if modified_icon and self.decorators.modified.icon_placement == ICON_PLACEMENT.signcolumn then - table.insert(self.signs, { - sign = modified_icon.hl[1], - lnum = self.index + 1, - priority = 3, - }) - modified_icon = nil - end - return modified_icon -end - ----@param node table ----@return HighlightedString[]|nil icon -function Builder:_get_bookmark_icon(node) - local bookmark_icon = self.decorators.bookmarks:get_icon(node) - if bookmark_icon and self.decorators.bookmarks.icon_placement == ICON_PLACEMENT.signcolumn then - table.insert(self.signs, { - sign = bookmark_icon.hl[1], - lnum = self.index + 1, - priority = 4, - }) - bookmark_icon = nil - end - return bookmark_icon -end - ----Append optional highlighting to icon or name. ----@param node table ----@param decorator Decorator ----@param icon_hl string[] icons to append to ----@param name_hl string[] names to append to -function Builder:_append_dec_highlight(node, decorator, icon_hl, name_hl) - local hl = decorator:get_highlight(node) - if hl then - if decorator.hl_pos == HL_POSITION.all or decorator.hl_pos == HL_POSITION.icon then - table.insert(icon_hl, hl) - end - if decorator.hl_pos == HL_POSITION.all or decorator.hl_pos == HL_POSITION.name then - table.insert(name_hl, hl) - end - end -end - ---@param indent_markers HighlightedString[] ---@param arrows HighlightedString[]|nil ---@param icon HighlightedString ---@param name HighlightedString ----@param git_icons HighlightedString[]|nil ----@param diagnostics_icon HighlightedString|nil ----@param modified_icon HighlightedString|nil ----@param bookmark_icon HighlightedString|nil +---@param node table ---@return HighlightedString[] -function Builder:_format_line(indent_markers, arrows, icon, name, git_icons, diagnostics_icon, modified_icon, bookmark_icon) +function Builder:_format_line(indent_markers, arrows, icon, name, node) local added_len = 0 local function add_to_end(t1, t2) + if not t2 then + return + end for _, v in ipairs(t2) do if added_len > 0 then table.insert(t1, { str = self.icon_padding }) @@ -301,32 +221,15 @@ function Builder:_format_line(indent_markers, arrows, icon, name, git_icons, dia local line = { indent_markers, arrows } add_to_end(line, { icon }) - if git_icons and self.decorators.git.icon_placement == ICON_PLACEMENT.before then - add_to_end(line, git_icons) - end - if modified_icon and self.decorators.modified.icon_placement == ICON_PLACEMENT.before then - add_to_end(line, { modified_icon }) - end - if diagnostics_icon and self.decorators.diagnostics.icon_placement == ICON_PLACEMENT.before then - add_to_end(line, { diagnostics_icon }) - end - if bookmark_icon and self.decorators.bookmarks.icon_placement == ICON_PLACEMENT.before then - add_to_end(line, { bookmark_icon }) + + for i = #self.deco, 1, -1 do + add_to_end(line, self.deco[i]:icons_before(node)) end add_to_end(line, { name }) - if git_icons and self.decorators.git.icon_placement == ICON_PLACEMENT.after then - add_to_end(line, git_icons) - end - if modified_icon and self.decorators.modified.icon_placement == ICON_PLACEMENT.after then - add_to_end(line, { modified_icon }) - end - if diagnostics_icon and self.decorators.diagnostics.icon_placement == ICON_PLACEMENT.after then - add_to_end(line, { diagnostics_icon }) - end - if bookmark_icon and self.decorators.bookmarks.icon_placement == ICON_PLACEMENT.after then - add_to_end(line, { bookmark_icon }) + for i = #self.deco, 1, -1 do + add_to_end(line, self.deco[i]:icons_after(node)) end return line @@ -337,11 +240,15 @@ function Builder:_build_line(node, idx, num_children) local indent_markers = pad.get_indent_markers(self.depth, idx, num_children, node, self.markers) local arrows = pad.get_arrows(node) - -- adds icons to signcolumn - local bookmark_icon = self:_get_bookmark_icon(node) - local git_icons = self:_get_git_icons(node) - local modified_icon = self:_get_modified_icon(node) - local diagnostics_icon = self:_get_diagnostics_icon(node) + -- signs, use the highest priority + local sign_name + for _, d in ipairs(self.deco) do + sign_name = d:sign_name(node) + if sign_name then + self.sign_names[self.index] = sign_name + break + end + end -- main components local is_folder = node.nodes ~= nil @@ -355,16 +262,14 @@ function Builder:_build_line(node, idx, num_children) icon, name = self:_build_file(node) end - -- extra highighting - self:_append_dec_highlight(node, self.decorators.git, icon.hl, name.hl) - self:_append_dec_highlight(node, self.decorators.opened, icon.hl, name.hl) - self:_append_dec_highlight(node, self.decorators.modified, icon.hl, name.hl) - self:_append_dec_highlight(node, self.decorators.bookmarks, icon.hl, name.hl) - self:_append_dec_highlight(node, self.decorators.diagnostics, icon.hl, name.hl) - self:_append_dec_highlight(node, self.decorators.copied, icon.hl, name.hl) - self:_append_dec_highlight(node, self.decorators.cut, icon.hl, name.hl) + -- highighting + for _, d in ipairs(self.deco) do + local icon_group, name_group = d:groups_icon_name(node) + table.insert(icon.hl, icon_group) + table.insert(name.hl, name_group) + end - local line = self:_format_line(indent_markers, arrows, icon, name, git_icons, diagnostics_icon, modified_icon, bookmark_icon) + local line = self:_format_line(indent_markers, arrows, icon, name, node) self:_insert_line(self:_unwrap_highlighted_strings(line)) self.index = self.index + 1 @@ -438,7 +343,7 @@ function Builder:build_header(show_header) end function Builder:unwrap() - return self.lines, self.highlights, self.signs + return self.lines, self.highlights, self.sign_names end return Builder diff --git a/lua/nvim-tree/renderer/decorator/bookmarks.lua b/lua/nvim-tree/renderer/decorator/bookmarks.lua index b41dbd45508..a37b476e0eb 100644 --- a/lua/nvim-tree/renderer/decorator/bookmarks.lua +++ b/lua/nvim-tree/renderer/decorator/bookmarks.lua @@ -13,6 +13,7 @@ local DecoratorBookmarks = Decorator:new() --- @return DecoratorBookmarks function DecoratorBookmarks:new(opts) local o = Decorator.new(self, { + enabled = true, hl_pos = HL_POSITION[opts.renderer.highlight_bookmarks] or HL_POSITION.none, icon_placement = ICON_PLACEMENT[opts.renderer.icons.bookmarks_placement] or ICON_PLACEMENT.none, }) @@ -30,14 +31,14 @@ function DecoratorBookmarks:new(opts) end --- Bookmark icon: renderer.icons.show.bookmarks and node is marked -function DecoratorBookmarks:get_icon(node) +function DecoratorBookmarks:calculate_icons(node) if marks.get_mark(node) then - return self.icon + return { self.icon } end end --- Bookmark highlight: renderer.highlight_bookmarks and node is marked -function DecoratorBookmarks:get_highlight(node) +function DecoratorBookmarks:calculate_highlight(node) if self.hl_pos ~= HL_POSITION.none and marks.get_mark(node) then return "NvimTreeBookmarkHL" end diff --git a/lua/nvim-tree/renderer/decorator/copied.lua b/lua/nvim-tree/renderer/decorator/copied.lua index bd5a078d4aa..aa5b4bcad0a 100644 --- a/lua/nvim-tree/renderer/decorator/copied.lua +++ b/lua/nvim-tree/renderer/decorator/copied.lua @@ -14,6 +14,7 @@ local DecoratorCopied = Decorator:new() --- @return DecoratorCopied function DecoratorCopied:new(opts) local o = Decorator.new(self, { + enabled = true, hl_pos = HL_POSITION[opts.renderer.highlight_clipboard] or HL_POSITION.none, icon_placement = ICON_PLACEMENT.none, }) @@ -26,7 +27,7 @@ function DecoratorCopied:new(opts) end --- Cut highlight: renderer.highlight_clipboard and node is copied -function DecoratorCopied:get_highlight(node) +function DecoratorCopied:calculate_highlight(node) if self.hl_pos ~= HL_POSITION.none and copy_paste.is_copied(node) then return "NvimTreeCopiedHL" end diff --git a/lua/nvim-tree/renderer/decorator/cut.lua b/lua/nvim-tree/renderer/decorator/cut.lua index bfac11a40cb..6525aaddb1d 100644 --- a/lua/nvim-tree/renderer/decorator/cut.lua +++ b/lua/nvim-tree/renderer/decorator/cut.lua @@ -14,6 +14,7 @@ local DecoratorCut = Decorator:new() --- @return DecoratorCut function DecoratorCut:new(opts) local o = Decorator.new(self, { + enabled = true, hl_pos = HL_POSITION[opts.renderer.highlight_clipboard] or HL_POSITION.none, icon_placement = ICON_PLACEMENT.none, }) @@ -26,7 +27,7 @@ function DecoratorCut:new(opts) end --- Cut highlight: renderer.highlight_clipboard and node is cut -function DecoratorCut:get_highlight(node) +function DecoratorCut:calculate_highlight(node) if self.hl_pos ~= HL_POSITION.none and copy_paste.is_cut(node) then return "NvimTreeCutHL" end diff --git a/lua/nvim-tree/renderer/decorator/diagnostics.lua b/lua/nvim-tree/renderer/decorator/diagnostics.lua index 9fe2edc9065..8dd43b27ae7 100644 --- a/lua/nvim-tree/renderer/decorator/diagnostics.lua +++ b/lua/nvim-tree/renderer/decorator/diagnostics.lua @@ -31,7 +31,6 @@ local ICON_KEYS = { } --- @class DecoratorDiagnostics: Decorator ---- @field enabled boolean --- @field icons HighlightedString[] local DecoratorDiagnostics = Decorator:new() @@ -39,12 +38,12 @@ local DecoratorDiagnostics = Decorator:new() --- @return DecoratorDiagnostics function DecoratorDiagnostics:new(opts) local o = Decorator.new(self, { + enabled = opts.diagnostics.enable, hl_pos = HL_POSITION[opts.renderer.highlight_diagnostics] or HL_POSITION.none, icon_placement = ICON_PLACEMENT[opts.renderer.icons.diagnostics_placement] or ICON_PLACEMENT.none, }) ---@cast o DecoratorDiagnostics - o.enabled = opts.diagnostics.enable if not o.enabled then return o end @@ -64,14 +63,16 @@ function DecoratorDiagnostics:new(opts) end --- Diagnostic icon: diagnostics.enable, renderer.icons.show.diagnostics and node has status -function DecoratorDiagnostics:get_icon(node) +function DecoratorDiagnostics:calculate_icons(node) if node and self.enabled and self.icons then - return self.icons[node.diag_status] + if node.diag_status then + return { self.icons[node.diag_status] } + end end end --- Diagnostic highlight: diagnostics.enable, renderer.highlight_diagnostics and node has status -function DecoratorDiagnostics:get_highlight(node) +function DecoratorDiagnostics:calculate_highlight(node) if not node or not self.enabled or self.hl_pos == HL_POSITION.none then return nil end diff --git a/lua/nvim-tree/renderer/decorator/git.lua b/lua/nvim-tree/renderer/decorator/git.lua index 62e7070cb14..a42721f07ae 100644 --- a/lua/nvim-tree/renderer/decorator/git.lua +++ b/lua/nvim-tree/renderer/decorator/git.lua @@ -7,7 +7,6 @@ local ICON_PLACEMENT = require("nvim-tree.enum").ICON_PLACEMENT local Decorator = require "nvim-tree.renderer.decorator" --- @class DecoratorGit: Decorator ---- @field enabled boolean --- @field file_hl string[] --- @field folder_hl string[] --- @field git_icons table @@ -115,12 +114,12 @@ end --- @return DecoratorGit function DecoratorGit:new(opts) local o = Decorator.new(self, { + enabled = opts.git.enable, hl_pos = HL_POSITION[opts.renderer.highlight_git] or HL_POSITION.none, icon_placement = ICON_PLACEMENT[opts.renderer.icons.git_placement] or ICON_PLACEMENT.none, }) ---@cast o DecoratorGit - o.enabled = opts.git.enable if not o.enabled then return o end @@ -140,7 +139,7 @@ end --- Git icons: git.enable, renderer.icons.show.git and node has status --- @param node table --- @return HighlightedString[]|nil modified icon -function DecoratorGit:get_icons(node) +function DecoratorGit:calculate_icons(node) if not node or not self.enabled or not self.git_icons then return nil end @@ -184,8 +183,20 @@ function DecoratorGit:get_icons(node) return iconss end +--- Get the first icon as the sign if appropriate +function DecoratorGit:sign_name(node) + if self.icon_placement ~= ICON_PLACEMENT.signcolumn then + return + end + + local icons = self:calculate_icons(node) + if icons and #icons > 0 then + return icons[1].hl[1] + end +end + --- Git highlight: git.enable, renderer.highlight_git and node has status -function DecoratorGit:get_highlight(node) +function DecoratorGit:calculate_highlight(node) if not node or not self.enabled or self.hl_pos == HL_POSITION.none then return nil end diff --git a/lua/nvim-tree/renderer/decorator/init.lua b/lua/nvim-tree/renderer/decorator/init.lua index b61813451b3..5c8aece350b 100644 --- a/lua/nvim-tree/renderer/decorator/init.lua +++ b/lua/nvim-tree/renderer/decorator/init.lua @@ -1,6 +1,10 @@ +local HL_POSITION = require("nvim-tree.enum").HL_POSITION +local ICON_PLACEMENT = require("nvim-tree.enum").ICON_PLACEMENT + --- @class Decorator ---- @field hl_pos HL_POSITION ---- @field icon_placement ICON_PLACEMENT +--- @field protected enabled boolean +--- @field protected hl_pos HL_POSITION +--- @field protected icon_placement ICON_PLACEMENT local Decorator = {} --- @param o Decorator|nil @@ -13,19 +17,80 @@ function Decorator:new(o) return o end +--- Maybe highlight groups +--- @param node table +--- @return string|nil icon highlight group +--- @return string|nil name highlight group +function Decorator:groups_icon_name(node) + local icon_hl, name_hl + + if self.enabled and self.hl_pos ~= HL_POSITION.none then + local hl = self:calculate_highlight(node) + + if self.hl_pos == HL_POSITION.all or self.hl_pos == HL_POSITION.icon then + icon_hl = hl + end + if self.hl_pos == HL_POSITION.all or self.hl_pos == HL_POSITION.name then + name_hl = hl + end + end + + return icon_hl, name_hl +end + +--- Maybe icon sign +--- @param node table +--- @return string|nil name +function Decorator:sign_name(node) + if not self.enabled or self.icon_placement ~= ICON_PLACEMENT.signcolumn then + return + end + + local icons = self:calculate_icons(node) + if icons and #icons > 0 then + return icons[1].hl[1] + end +end + +--- Icons when ICON_PLACEMENT.before +--- @param node table +--- @return HighlightedString[]|nil icons +function Decorator:icons_before(node) + if not self.enabled or self.icon_placement ~= ICON_PLACEMENT.before then + return + end + + return self:calculate_icons(node) +end + +--- Icons when ICON_PLACEMENT.after +--- @param node table +--- @return HighlightedString[]|nil icons +function Decorator:icons_after(node) + if not self.enabled or self.icon_placement ~= ICON_PLACEMENT.after then + return + end + + return self:calculate_icons(node) +end + ---@diagnostic disable: unused-local +-- luacheck: push no unused args ---- Node icon ---- @param _ table node ---- @return HighlightedString|nil modified icon -function Decorator:get_icon(_) end +--- Maybe icons - abstract +--- @protected +--- @param node table +--- @return HighlightedString[]|nil icons +function Decorator:calculate_icons(node) end ---- Node highlight group ---- @param _ table node +--- Maybe highlight group - abstract +--- @protected +--- @param node table --- @return string|nil group -function Decorator:get_highlight(_) end +function Decorator:calculate_highlight(node) end ---@diagnostic enable: unused-local +-- luacheck: pop --- Define a sign --- @protected diff --git a/lua/nvim-tree/renderer/decorator/modified.lua b/lua/nvim-tree/renderer/decorator/modified.lua index 4197b60b300..30cc61bbebe 100644 --- a/lua/nvim-tree/renderer/decorator/modified.lua +++ b/lua/nvim-tree/renderer/decorator/modified.lua @@ -6,7 +6,6 @@ local ICON_PLACEMENT = require("nvim-tree.enum").ICON_PLACEMENT local Decorator = require "nvim-tree.renderer.decorator" --- @class DecoratorModified: Decorator ---- @field enabled boolean --- @field icon HighlightedString|nil local DecoratorModified = Decorator:new() @@ -14,12 +13,12 @@ local DecoratorModified = Decorator:new() --- @return DecoratorModified function DecoratorModified:new(opts) local o = Decorator.new(self, { + enabled = opts.modified.enable, hl_pos = HL_POSITION[opts.renderer.highlight_modified] or HL_POSITION.none, icon_placement = ICON_PLACEMENT[opts.renderer.icons.modified_placement] or ICON_PLACEMENT.none, }) ---@cast o DecoratorModified - o.enabled = opts.modified.enable if not o.enabled then return o end @@ -36,14 +35,14 @@ function DecoratorModified:new(opts) end --- Modified icon: modified.enable, renderer.icons.show.modified and node is modified -function DecoratorModified:get_icon(node) +function DecoratorModified:calculate_icons(node) if self.enabled and buffers.is_modified(node) then - return self.icon + return { self.icon } end end --- Modified highlight: modified.enable, renderer.highlight_modified and node is modified -function DecoratorModified:get_highlight(node) +function DecoratorModified:calculate_highlight(node) if not self.enabled or self.hl_pos == HL_POSITION.none or not buffers.is_modified(node) then return nil end diff --git a/lua/nvim-tree/renderer/decorator/opened.lua b/lua/nvim-tree/renderer/decorator/opened.lua index 9246fd3a8e1..50125a9f3e6 100644 --- a/lua/nvim-tree/renderer/decorator/opened.lua +++ b/lua/nvim-tree/renderer/decorator/opened.lua @@ -14,6 +14,7 @@ local DecoratorOpened = Decorator:new() --- @return DecoratorOpened function DecoratorOpened:new(opts) local o = Decorator.new(self, { + enabled = true, hl_pos = HL_POSITION[opts.renderer.highlight_opened_files] or HL_POSITION.none, icon_placement = ICON_PLACEMENT.none, }) @@ -24,7 +25,7 @@ end --- Opened highlight: renderer.highlight_opened_files and node has an open buffer --- @param node table -function DecoratorOpened:get_highlight(node) +function DecoratorOpened:calculate_highlight(node) if self.hl_pos ~= HL_POSITION.none and buffers.is_opened(node) then return "NvimTreeOpenedHL" end diff --git a/lua/nvim-tree/renderer/init.lua b/lua/nvim-tree/renderer/init.lua index 7f08e6af226..dfc45787502 100644 --- a/lua/nvim-tree/renderer/init.lua +++ b/lua/nvim-tree/renderer/init.lua @@ -20,20 +20,21 @@ local DecoratorOpened = require "nvim-tree.renderer.decorator.opened" local M = { last_highlights = {}, decorators = {}, + deco = {}, } local SIGN_GROUP = "NvimTreeRendererSigns" local namespace_id = vim.api.nvim_create_namespace "NvimTreeHighlights" -local function _draw(bufnr, lines, hl, signs) +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) M.render_hl(bufnr, hl) vim.api.nvim_buf_set_option(bufnr, "modifiable", false) vim.fn.sign_unplace(SIGN_GROUP) - for _, sign in pairs(signs) do - vim.fn.sign_place(0, SIGN_GROUP, sign.sign, bufnr, { lnum = sign.lnum, priority = sign.priority }) + for i, sign_name in pairs(sign_names) do + vim.fn.sign_place(0, SIGN_GROUP, sign_name, bufnr, { lnum = i + 1 }) end end @@ -71,7 +72,7 @@ function M.draw() local cursor = vim.api.nvim_win_get_cursor(view.get_winnr()) icon_component.reset_config() - local lines, hl, signs = Builder.new(core.get_cwd(), M.decorators) + local lines, hl, sign_names = Builder.new(core.get_cwd(), M.deco) :configure_root_label(M.config.root_folder_label) :configure_trailing_slash(M.config.add_trailing) :configure_special_files(M.config.special_files) @@ -84,7 +85,7 @@ function M.draw() :build(core.get_explorer()) :unwrap() - _draw(bufnr, lines, hl, signs) + _draw(bufnr, lines, hl, sign_names) M.last_highlights = hl @@ -106,17 +107,15 @@ function M.setup(opts) full_name.setup(opts) icon_component.setup(opts) - -- TODO change to array: precedence should follow order - -- HL cut > copied > diagnostics > bookmarked > modified > opened > git - -- Sign diagnostics > modified > git > bookmarked - M.decorators = { - bookmarks = DecoratorBookmarks:new(opts), - copied = DecoratorCopied:new(opts), - cut = DecoratorCut:new(opts), - diagnostics = DecoratorDiagnostics:new(opts), - git = DecoratorGit:new(opts), - modified = DecoratorModified:new(opts), - opened = DecoratorOpened:new(opts), + -- priority order + M.deco = { + DecoratorCut:new(opts), + DecoratorCopied:new(opts), + DecoratorDiagnostics:new(opts), + DecoratorBookmarks:new(opts), + DecoratorModified:new(opts), + DecoratorOpened:new(opts), + DecoratorGit:new(opts), } end