diff --git a/doc/nvim-tree-lua.txt b/doc/nvim-tree-lua.txt index 060227f8220..1ce871427de 100644 --- a/doc/nvim-tree-lua.txt +++ b/doc/nvim-tree-lua.txt @@ -425,6 +425,7 @@ Following is the default configuration. See |nvim-tree-opts| for details. >lua special_files = { "Cargo.toml", "Makefile", "README.md", "readme.md" }, hidden_display = "none", symlink_destination = true, + decorators = { "Git", "Opened", "Hidden", "Modified", "Bookmarks", "Diagnostics", "Copied", "Cut", }, highlight_git = "none", highlight_diagnostics = "none", highlight_opened_files = "none", @@ -842,7 +843,7 @@ Use nvim-tree in a floating window. ============================================================================== 5.3 OPTS: RENDERER *nvim-tree-opts-renderer* -Highlight precedence, additive: +Highlight precedence, additive, change via |nvim-tree.renderer.decorators| git < opened < modified < bookmarked < diagnostics < copied < cut *nvim-tree.renderer.add_trailing* @@ -927,6 +928,22 @@ Show a summary of hidden files below the tree using `NvimTreeHiddenDisplay Whether to show the destination of the symlink. Type: `boolean`, Default: `true` +*nvim-tree.renderer.decorators* +Highlighting and icons for the nodes, in increasing order of precedence. +Uses strings to specify builtin decorators otherwise specify your +`nvim_tree.api.decorator.DecoratorUser` class. + Type: `nvim_tree.api.decorator.Name[]`, Default: >lua + { + "Git", + "Opened", + "Hidden", + "Modified", + "Bookmarks", + "Diagnostics", + "Copied", + "Cut", + } +< *nvim-tree.renderer.highlight_git* Enable highlight for git attributes using `NvimTreeGit*HL` highlight groups. Requires |nvim-tree.git.enable| @@ -996,7 +1013,7 @@ Configuration options for tree indent markers. *nvim-tree.renderer.icons* Configuration options for icons. -Icon order and sign column precedence: +Icon order and sign column precedence, change via |nvim-tree.renderer.decorators| git < hidden < modified < bookmarked < diagnostics `renderer.icons.*_placement` options may be: @@ -2943,6 +2960,7 @@ highlight group is not, hard linking as follows: > |nvim-tree.prefer_startup_root| |nvim-tree.reload_on_bufenter| |nvim-tree.renderer.add_trailing| +|nvim-tree.renderer.decorators| |nvim-tree.renderer.full_name| |nvim-tree.renderer.group_empty| |nvim-tree.renderer.hidden_display| diff --git a/lua/nvim-tree.lua b/lua/nvim-tree.lua index 212d4c14bab..c26b52e163f 100644 --- a/lua/nvim-tree.lua +++ b/lua/nvim-tree.lua @@ -284,6 +284,7 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS special_files = { "Cargo.toml", "Makefile", "README.md", "readme.md" }, hidden_display = "none", symlink_destination = true, + decorators = { "Git", "Opened", "Hidden", "Modified", "Bookmarks", "Diagnostics", "Copied", "Cut", }, highlight_git = "none", highlight_diagnostics = "none", highlight_opened_files = "none", diff --git a/lua/nvim-tree/_meta/api_decorator.lua b/lua/nvim-tree/_meta/api_decorator.lua index 02a88d351ed..1c6ee014a18 100644 --- a/lua/nvim-tree/_meta/api_decorator.lua +++ b/lua/nvim-tree/_meta/api_decorator.lua @@ -1,19 +1,19 @@ ---@meta error("Cannot require a meta file") -local nvim_tree = { api = { decorator = { AbstractDecorator = {} } } } +local nvim_tree = { api = { decorator = { DecoratorUser = {} } } } ---Custom decorator ---It may: --- Add icons --- Set highlight group for the name or icons --- Override node icon ----Class must be created via nvim_tree.api.decorator.create() +---Register it via :help nvim-tree.renderer.decorators +---Create class via require("nvim-tree.api").decorator.DecoratorUser:extend() ---Mandatory constructor :new() will be called once per tree render, with no arguments. ---Constructor must call: --- :init --- :define_sign when using "signcolumn" range ----Decorator must be registered via api.decorator.register ---Highlight group range as per nvim-tree.renderer.highlight_* ---@alias nvim_tree.api.decorator.HighlightRange "none" | "icon" | "name" | "all" @@ -21,67 +21,67 @@ local nvim_tree = { api = { decorator = { AbstractDecorator = {} } } } ---Icon position as per renderer.icons.*_placement ---@alias nvim_tree.api.decorator.IconPlacement "none" | "before" | "after" | "signcolumn" | "right_align" ----Names of predefined decorators or your decorator classes ----@alias nvim_tree.api.decorator.Name "Cut" | "Copied" | "Diagnostics" | "Bookmarks" | "Modified" | "Hidden" | "Opened" | "Git" | nvim_tree.api.decorator.AbstractDecorator +---Names of builtin decorators or your decorator classes. Builtins are ordered lowest to highest priority. +---@alias nvim_tree.api.decorator.Name "Git" | "Opened" | "Hidden" | "Modified" | "Bookmarks" | "Diagnostics" | "Copied" | "Cut" | nvim_tree.api.decorator.DecoratorUser ----Abstract decorator class, your decorator will extend this. +---Your decorator will extend this class via require("nvim-tree.api").decorator.DecoratorUser:extend() --- ----@class (exact) nvim_tree.api.decorator.AbstractDecorator +---@class (exact) nvim_tree.api.decorator.DecoratorUser ---@field protected enabled boolean ---@field protected highlight_range nvim_tree.api.decorator.HighlightRange ---@field protected icon_placement nvim_tree.api.decorator.IconPlacement ---Abstract: no-args constructor must be implemented. --- -function nvim_tree.api.decorator.AbstractDecorator:new() end +function nvim_tree.api.decorator.DecoratorUser:new() end ---Abstract: optionally implement to set the node's icon --- ---@param node nvim_tree.api.Node ---@return HighlightedString? icon_node -function nvim_tree.api.decorator.AbstractDecorator:icon_node(node) end +function nvim_tree.api.decorator.DecoratorUser:icon_node(node) end ---Abstract: optionally implement to provide icons and the highlight groups for your icon_placement. --- ---@param node nvim_tree.api.Node ---@return HighlightedString[]? icons -function nvim_tree.api.decorator.AbstractDecorator:icons(node) end +function nvim_tree.api.decorator.DecoratorUser:icons(node) end ---Abstract: optionally implement to provide one highlight group to apply to your highlight_range. --- ---@param node nvim_tree.api.Node ---@return string? highlight_group -function nvim_tree.api.decorator.AbstractDecorator:highlight_group(node) end +function nvim_tree.api.decorator.DecoratorUser:highlight_group(node) end ---Must be called from your constructor. --- ----@class (exact) nvim_tree.api.decorator.AbstractDecoratorInitArgs +---@class (exact) nvim_tree.api.decorator.InitArgs ---@field enabled boolean ---@field highlight_range nvim_tree.api.decorator.HighlightRange ---@field icon_placement nvim_tree.api.decorator.IconPlacement --- ---@protected ----@param args nvim_tree.api.decorator.AbstractDecoratorInitArgs -function nvim_tree.api.decorator.AbstractDecorator:init(args) end +---@param args nvim_tree.api.decorator.InitArgs +function nvim_tree.api.decorator.DecoratorUser:init(args) end ---Define a sign. This should be called in the constructor. --- ---@protected ---@param icon HighlightedString? -function nvim_tree.api.decorator.AbstractDecorator:define_sign(icon) end +function nvim_tree.api.decorator.DecoratorUser:define_sign(icon) end -- -- Example Decorator -- ----@class (exact) MyDecorator: nvim_tree.api.decorator.AbstractDecorator +---@class (exact) MyDecorator: nvim_tree.api.decorator.DecoratorUser ---@field private my_icon nvim_tree.api.HighlightedString -local MyDecorator = require("nvim-tree.api").decorator.create() +local MyDecorator = require("nvim-tree.api").decorator.DecoratorUser:extend() ---Mandatory constructor :new() will be called once per tree render, with no arguments. function MyDecorator:new() - ---@type nvim_tree.api.decorator.AbstractDecoratorInitArgs + ---@type nvim_tree.api.decorator.InitArgs local args = { enabled = true, highlight_range = "all", @@ -131,6 +131,3 @@ function MyDecorator:highlight_group(node) return nil end end - ----Register the decorator, below Cut in priority -require("nvim-tree.api").decorator.register({ decorator = MyDecorator, below = "Cut" }) diff --git a/lua/nvim-tree/api.lua b/lua/nvim-tree/api.lua index 3b3eb667e8d..880f115911a 100644 --- a/lua/nvim-tree/api.lua +++ b/lua/nvim-tree/api.lua @@ -7,7 +7,6 @@ local events = require("nvim-tree.events") local help = require("nvim-tree.help") local keymap = require("nvim-tree.keymap") local notify = require("nvim-tree.notify") -local decorator_registry = require("nvim-tree.renderer.decorator.registry") local DirectoryNode = require("nvim-tree.node.directory") local FileLinkNode = require("nvim-tree.node.file-link") @@ -314,26 +313,8 @@ Api.commands.get = wrap(function() return require("nvim-tree.commands").get() end) ----Create a new decorator class ---- ----@return nvim_tree.api.decorator.AbstractDecorator -Api.decorator.create = function() return DecoratorUser:extend() end - ----Register a decorator class ---- ----@class RegisterOpts ----@field decorator nvim_tree.api.decorator.AbstractDecorator ----@field below nvim_tree.api.decorator.Name? ---- ----@param opts RegisterOpts -Api.decorator.register = function(opts) decorator_registry.register(opts) end - ----Unregister a decorator class ---- ----@class UnRegisterOpts ----@field decorator nvim_tree.api.decorator.AbstractDecorator ---- ----@param opts UnRegisterOpts -Api.decorator.unregister = function(opts) decorator_registry.unregister(opts) end +---User provided decorator. Extend this class via :extend() +---@type nvim_tree.api.decorator.DecoratorUser +Api.decorator.DecoratorUser = DecoratorUser --[[@as nvim_tree.api.decorator.DecoratorUser]] return Api diff --git a/lua/nvim-tree/renderer/builder.lua b/lua/nvim-tree/renderer/builder.lua index 1b0a3bc0d63..b22093f4035 100644 --- a/lua/nvim-tree/renderer/builder.lua +++ b/lua/nvim-tree/renderer/builder.lua @@ -1,14 +1,36 @@ -local decorator_registry = require("nvim-tree.renderer.decorator.registry") local notify = require("nvim-tree.notify") local utils = require("nvim-tree.utils") local view = require("nvim-tree.view") local Class = require("nvim-tree.classic") + local DirectoryNode = require("nvim-tree.node.directory") + +local DecoratorBookmarks = require("nvim-tree.renderer.decorator.bookmarks") +local DecoratorCopied = require("nvim-tree.renderer.decorator.copied") +local DecoratorCut = require("nvim-tree.renderer.decorator.cut") +local DecoratorDiagnostics = require("nvim-tree.renderer.decorator.diagnostics") +local DecoratorGit = require("nvim-tree.renderer.decorator.git") +local DecoratorHidden = require("nvim-tree.renderer.decorator.hidden") +local DecoratorModified = require("nvim-tree.renderer.decorator.modified") +local DecoratorOpened = require("nvim-tree.renderer.decorator.opened") local DecoratorUser = require("nvim-tree.renderer.decorator.user") local pad = require("nvim-tree.renderer.components.padding") +-- Builtin Decorators +---@type table +local BUILTIN_DECORATORS = { + Git = DecoratorGit, + Opened = DecoratorOpened, + Hidden = DecoratorHidden, + Modified = DecoratorModified, + Bookmarks = DecoratorBookmarks, + Diagnostics = DecoratorDiagnostics, + Copied = DecoratorCopied, + Cut = DecoratorCut, +} + ---@class (exact) AddHighlightArgs ---@field group string[] ---@field line number @@ -52,31 +74,28 @@ function Builder:new(args) self.virtual_lines = {} self.decorators = {} self.hidden_display = Builder:setup_hidden_display_function(self.explorer.opts) - self.api_nodes = nil - - ---@type DecoratorArgs - local decorator_args = { explorer = self.explorer } - - -- instantiate all the decorators - for _, d in ipairs(decorator_registry.registered) do - if d:is(DecoratorUser) then - self:build_api_nodes() - table.insert(self.decorators, d()) - else - table.insert(self.decorators, d(decorator_args)) - end - end -end ----Create and populate api_nodes if not present ----@private -function Builder:build_api_nodes() - if self.api_nodes then - return - end + -- instantiate all the builtin and user decorator instances + local builtin, user + for _, d in ipairs(self.explorer.opts.renderer.decorators) do + ---@type Decorator + builtin = BUILTIN_DECORATORS[d] - self.api_nodes = {} - self.explorer:clone(self.api_nodes) + ---@type DecoratorUser + user = d.as and d:as(DecoratorUser) + + if builtin then + table.insert(self.decorators, builtin({ explorer = self.explorer })) + elseif user then + table.insert(self.decorators, user()) + + -- clone user nodes once + if not self.api_nodes then + self.api_nodes = {} + self.explorer:clone(self.api_nodes) + end + end + end end ---Insert ranged highlight groups into self.highlights @@ -143,18 +162,18 @@ function Builder:format_line(indent_markers, arrows, icon, name, node) add_to_end(line, { icon }) for _, d in ipairs(self.decorators) do - add_to_end(line, d:icons_before(d:is(DecoratorUser) and api_node or node)) + add_to_end(line, d:icons_before(not d:is(DecoratorUser) and node or api_node)) end add_to_end(line, { name }) for _, d in ipairs(self.decorators) do - add_to_end(line, d:icons_after(d:is(DecoratorUser) and api_node or node)) + add_to_end(line, d:icons_after(not d:is(DecoratorUser) and node or api_node)) end local rights = {} for _, d in ipairs(self.decorators) do - add_to_end(rights, d:icons_right_align(d:is(DecoratorUser) and api_node or node)) + add_to_end(rights, d:icons_right_align(not d:is(DecoratorUser) and node or api_node)) end if #rights > 0 then self.extmarks[self.index] = rights @@ -173,7 +192,7 @@ function Builder:build_signs(node) local d, sign_name for i = #self.decorators, 1, -1 do d = self.decorators[i] - sign_name = d:sign_name(d:is(DecoratorUser) and api_node or node) + sign_name = d:sign_name(not d:is(DecoratorUser) and node or api_node) if sign_name then self.signs[self.index] = sign_name break @@ -217,6 +236,9 @@ end ---@return HighlightedString icon ---@return HighlightedString name function Builder:icon_name_decorated(node) + -- use the api node for user decorators + local api_node = self.api_nodes and self.api_nodes[node.uid_node] --[[@as Node]] + -- base case local icon = node:highlighted_icon() local name = node:highlighted_name() @@ -225,11 +247,11 @@ function Builder:icon_name_decorated(node) local icon_groups = {} local name_groups = {} local hl_icon, hl_name - for _, decorator in ipairs(self.decorators) do + for _, d in ipairs(self.decorators) do -- maybe overridde icon - icon = decorator:icon_node(node) or icon + icon = d:icon_node((not d:is(DecoratorUser) and node or api_node)) or icon - hl_icon, hl_name = decorator:highlight_group_icon_name(node) + hl_icon, hl_name = d:highlight_group_icon_name((not d:is(DecoratorUser) and node or api_node)) table.insert(icon_groups, hl_icon) table.insert(name_groups, hl_name) diff --git a/lua/nvim-tree/renderer/decorator/bookmarks.lua b/lua/nvim-tree/renderer/decorator/bookmarks.lua index ab8f41d9d20..1c07e87d6e8 100644 --- a/lua/nvim-tree/renderer/decorator/bookmarks.lua +++ b/lua/nvim-tree/renderer/decorator/bookmarks.lua @@ -4,7 +4,6 @@ local Decorator = require("nvim-tree.renderer.decorator") ---@field private explorer Explorer ---@field private icon HighlightedString? local DecoratorBookmarks = Decorator:extend() -DecoratorBookmarks.name = "Bookmarks" ---@class DecoratorBookmarks ---@overload fun(args: DecoratorArgs): DecoratorBookmarks diff --git a/lua/nvim-tree/renderer/decorator/copied.lua b/lua/nvim-tree/renderer/decorator/copied.lua index 8c6ad15995f..4422638535d 100644 --- a/lua/nvim-tree/renderer/decorator/copied.lua +++ b/lua/nvim-tree/renderer/decorator/copied.lua @@ -3,7 +3,6 @@ local Decorator = require("nvim-tree.renderer.decorator") ---@class (exact) DecoratorCopied: Decorator ---@field private explorer Explorer local DecoratorCopied = Decorator:extend() -DecoratorCopied.name = "Copied" ---@class DecoratorCopied ---@overload fun(args: DecoratorArgs): DecoratorCopied diff --git a/lua/nvim-tree/renderer/decorator/cut.lua b/lua/nvim-tree/renderer/decorator/cut.lua index 0f2af987fb1..6d29991231d 100644 --- a/lua/nvim-tree/renderer/decorator/cut.lua +++ b/lua/nvim-tree/renderer/decorator/cut.lua @@ -3,7 +3,6 @@ local Decorator = require("nvim-tree.renderer.decorator") ---@class (exact) DecoratorCut: Decorator ---@field private explorer Explorer local DecoratorCut = Decorator:extend() -DecoratorCut.name = "Cut" ---@class DecoratorCut ---@overload fun(args: DecoratorArgs): DecoratorCut diff --git a/lua/nvim-tree/renderer/decorator/diagnostics.lua b/lua/nvim-tree/renderer/decorator/diagnostics.lua index 6fbf087d7e1..6c72a2aa0e7 100644 --- a/lua/nvim-tree/renderer/decorator/diagnostics.lua +++ b/lua/nvim-tree/renderer/decorator/diagnostics.lua @@ -34,7 +34,6 @@ local ICON_KEYS = { ---@field private explorer Explorer ---@field private diag_icons HighlightedString[]? local DecoratorDiagnostics = Decorator:extend() -DecoratorDiagnostics.name = "Diagnostics" ---@class DecoratorDiagnostics ---@overload fun(args: DecoratorArgs): DecoratorDiagnostics diff --git a/lua/nvim-tree/renderer/decorator/git.lua b/lua/nvim-tree/renderer/decorator/git.lua index 47f22536f1f..b4bc2499c17 100644 --- a/lua/nvim-tree/renderer/decorator/git.lua +++ b/lua/nvim-tree/renderer/decorator/git.lua @@ -19,7 +19,6 @@ local DirectoryNode = require("nvim-tree.node.directory") ---@field private icons_by_status GitIconsByStatus? ---@field private icons_by_xy GitIconsByXY? local DecoratorGit = Decorator:extend() -DecoratorGit.name = "Git" ---@class DecoratorGit ---@overload fun(args: DecoratorArgs): DecoratorGit diff --git a/lua/nvim-tree/renderer/decorator/hidden.lua b/lua/nvim-tree/renderer/decorator/hidden.lua index c83a20151f7..bf7aab23303 100644 --- a/lua/nvim-tree/renderer/decorator/hidden.lua +++ b/lua/nvim-tree/renderer/decorator/hidden.lua @@ -5,7 +5,6 @@ local DirectoryNode = require("nvim-tree.node.directory") ---@field private explorer Explorer ---@field private icon HighlightedString? local DecoratorHidden = Decorator:extend() -DecoratorHidden.name = "Hidden" ---@class DecoratorHidden ---@overload fun(args: DecoratorArgs): DecoratorHidden diff --git a/lua/nvim-tree/renderer/decorator/init.lua b/lua/nvim-tree/renderer/decorator/init.lua index 12f441b3e0b..9c6a6bc1874 100644 --- a/lua/nvim-tree/renderer/decorator/init.lua +++ b/lua/nvim-tree/renderer/decorator/init.lua @@ -2,7 +2,6 @@ local Class = require("nvim-tree.classic") ---Abstract Decorator ---@class (exact) Decorator: Class ----@field name string for registry ---@field protected enabled boolean ---@field protected highlight_range DecoratorHighlightRange ---@field protected icon_placement DecoratorIconPlacement diff --git a/lua/nvim-tree/renderer/decorator/modified.lua b/lua/nvim-tree/renderer/decorator/modified.lua index e761fff7ac0..e2cad99aca8 100644 --- a/lua/nvim-tree/renderer/decorator/modified.lua +++ b/lua/nvim-tree/renderer/decorator/modified.lua @@ -7,7 +7,6 @@ local DirectoryNode = require("nvim-tree.node.directory") ---@field private explorer Explorer ---@field private icon HighlightedString? local DecoratorModified = Decorator:extend() -DecoratorModified.name = "Modified" ---@class DecoratorModified ---@overload fun(args: DecoratorArgs): DecoratorModified diff --git a/lua/nvim-tree/renderer/decorator/opened.lua b/lua/nvim-tree/renderer/decorator/opened.lua index 56996d8368d..da500c083b2 100644 --- a/lua/nvim-tree/renderer/decorator/opened.lua +++ b/lua/nvim-tree/renderer/decorator/opened.lua @@ -6,7 +6,6 @@ local Decorator = require("nvim-tree.renderer.decorator") ---@field private explorer Explorer ---@field private icon HighlightedString|nil local DecoratorOpened = Decorator:extend() -DecoratorOpened.name = "Opened" ---@class DecoratorOpened ---@overload fun(args: DecoratorArgs): DecoratorOpened diff --git a/lua/nvim-tree/renderer/decorator/registry.lua b/lua/nvim-tree/renderer/decorator/registry.lua deleted file mode 100644 index dec70621235..00000000000 --- a/lua/nvim-tree/renderer/decorator/registry.lua +++ /dev/null @@ -1,58 +0,0 @@ -local utils = require("nvim-tree.utils") - -local DecoratorBookmarks = require("nvim-tree.renderer.decorator.bookmarks") -local DecoratorCopied = require("nvim-tree.renderer.decorator.copied") -local DecoratorCut = require("nvim-tree.renderer.decorator.cut") -local DecoratorDiagnostics = require("nvim-tree.renderer.decorator.diagnostics") -local DecoratorGit = require("nvim-tree.renderer.decorator.git") -local DecoratorModified = require("nvim-tree.renderer.decorator.modified") -local DecoratorHidden = require("nvim-tree.renderer.decorator.hidden") -local DecoratorOpened = require("nvim-tree.renderer.decorator.opened") -local DecoratorUser = require("nvim-tree.renderer.decorator.user") - -local M = { - -- Globally registered decorators including user. Lowest priority first. - ---@type Decorator[] - registered = { - DecoratorGit, - DecoratorOpened, - DecoratorHidden, - DecoratorModified, - DecoratorBookmarks, - DecoratorDiagnostics, - DecoratorCopied, - DecoratorCut, - } -} - ----@param opts RegisterOpts -function M.register(opts) - if not opts or not opts.decorator then - return - end - - if vim.tbl_contains(M.registered, opts.decorator) then - return - end - - for i, d in ipairs(M.registered) do - if d:is(DecoratorUser) and d == opts.below or d.name == opts.below then - table.insert(M.registered, i, opts.decorator) - return - end - end - - -- default to highest at the top - table.insert(M.registered, opts.decorator) -end - ----@param opts UnRegisterOpts -function M.unregister(opts) - if not opts or not opts.decorator then - return - end - - utils.array_remove(M.registered, opts.decorator) -end - -return M diff --git a/lua/nvim-tree/renderer/decorator/user.lua b/lua/nvim-tree/renderer/decorator/user.lua index 98e7cbadc42..b0037e7a5be 100644 --- a/lua/nvim-tree/renderer/decorator/user.lua +++ b/lua/nvim-tree/renderer/decorator/user.lua @@ -1,11 +1,11 @@ local Decorator = require("nvim-tree.renderer.decorator") ----Exposed as nvim_tree.api.decorator.AbstractDecorator +---Exposed as nvim_tree.api.decorator.DecoratorUser ---@class (exact) DecoratorUser: Decorator local DecoratorUser = Decorator:extend() ---User calls this instead of new ----@param args nvim_tree.api.decorator.AbstractDecoratorInitArgs +---@param args nvim_tree.api.decorator.InitArgs function DecoratorUser:init(args) DecoratorUser.super.new(self, args) end